From d4f401de1ce34781db7de318c3b85b7e46293e04 Mon Sep 17 00:00:00 2001 From: bigmerge Date: Fri, 14 Aug 2026 21:01:27 -0500 Subject: [PATCH] =?UTF-8?q?reshape:=20decorator-as-seam=20=E2=80=94=20port?= =?UTF-8?q?=20@route=20codegen,=20prove=20decorate->serve,=20rewrite=20sur?= =?UTF-8?q?face=20as=20decorated=20El?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ground-truth the three seams (route/telemetry+interoception/bus) with file:line evidence. Port the tested @route codegen+parser from feat/el-route-decorators into the worktree elc (decoration synthesizes el_route_dispatch — no hand-written 90-branch handle_request). Rebuild elc self-host; prove decorate->serve end-to-end (route_proof.el on :8951). Rewrite surface.el as El-native decorated components: @route + @accessor/@manager, in-process engram_* builtins (not http_get), @manager ops emit on the real dharma_* bus (same transport as wt/swarm-ccr). Identity keystones refused in write/relate/supersede. Gate-1 clone recipe (WAL-aside cold-boot + ENGRAM_WAL=on) proves the FULL op set live on the clone. Boundary auto-emit (telemetry/interoception/bus) staged as a reviewable cg_fn diff (SEAM_STAGED.md) — needs the cognition-engram rebuild to verify link. Live :8742 untouched; no push, no cutover. --- lang/el-compiler/src/codegen.el | 297 +++++++++++++++++++++++- lang/el-compiler/src/parser.el | 49 +++- tools/api-reshape/README.md | 50 +++-- tools/api-reshape/SEAM_STAGED.md | 70 ++++++ tools/api-reshape/agentic_loop.el | 176 +++++++++++++++ tools/api-reshape/route_proof.el | 85 +++++++ tools/api-reshape/surface.el | 360 ++++++++++++------------------ 7 files changed, 848 insertions(+), 239 deletions(-) create mode 100644 tools/api-reshape/SEAM_STAGED.md create mode 100644 tools/api-reshape/agentic_loop.el create mode 100644 tools/api-reshape/route_proof.el diff --git a/lang/el-compiler/src/codegen.el b/lang/el-compiler/src/codegen.el index 917fb31..25e6511 100644 --- a/lang/el-compiler/src/codegen.el +++ b/lang/el-compiler/src/codegen.el @@ -2916,6 +2916,24 @@ fn build_int_names_for_params(params: [Map]) -> Bool { return true } +// fn_has_decorator — does this FnDef carry a decorator named `name`? +// Reads the `decorators` list [{name, args}] attached by the parser. Absent +// key -> native_list_len returns 0 -> false. This is the multi-decorator-aware +// replacement for the old single `decorator` string check, so a fn may stack +// roles with other decorators (e.g. `@route(...) @manager fn ...`). +fn fn_has_decorator(stmt: Map, name: String) -> Bool { + let dl = stmt["decorators"] + let n: Int = native_list_len(dl) + let i = 0 + while i < n { + let d = native_list_get(dl, i) + let dn: String = d["name"] + if str_eq(dn, name) { return true } + let i = i + 1 + } + false +} + fn cg_fn(stmt: Map) -> Void { let fn_name: String = stmt["name"] // Skip El's `fn main()` - C provides its own main() for top-level stmts @@ -2927,10 +2945,10 @@ fn cg_fn(stmt: Map) -> Void { let params_c: String = params_to_c(params) // VBD role enforcement: dharma_emit / dharma_field may only be called // from @manager-decorated functions. Surface violations to the C compiler - // via #error directives emitted before the function definition. - let decorator: String = stmt["decorator"] + // via #error directives emitted before the function definition. Read the + // decorator LIST so the role may be stacked with other decorators. if vbd_has_restricted_call(body) { - if !str_eq(decorator, "manager") { + if !fn_has_decorator(stmt, "manager") { emit_line("#error \"VBD violation: dharma_emit/dharma_field called from non-@manager fn '" + fn_name + "'\"") } } @@ -3479,6 +3497,259 @@ fn cg_decl_streaming(stmt: Map) -> Void { } } +// ── @route dispatcher generation ────────────────────────────────────────────── +// +// Scan the token stream for @route-decorated fns and synthesize a generic HTTP +// dispatcher `el_route_dispatch(method, clean, path, body)`. A decorated handler +// must have the uniform signature (method, path, body) -> String. The dispatcher +// matches `clean` (the query-stripped path, supplied by the caller) against each +// route and calls the handler with the ORIGINAL `path` so query strings survive. +// Returns the sentinel "__EL_NO_ROUTE__" when nothing matches, so the caller may +// fall through to any remaining hand-written branches (mixed mode). +// +// Decorator grammar: @route(path, method, kind, suffix) +// path — the match string (or the prefix, for compound) +// method — "GET" | "POST" | ... ; a '|'-list like "GET|POST"; "ANY"/"" = no guard +// kind — "exact" (default) | "prefix" | "suffix" | "compound" +// suffix — for "compound": the required str_ends_with suffix +// +// The dispatch table is emitted SPECIFICITY-SORTED (most-specific first), NOT in +// source order, so overlapping prefixes (e.g. /api/x/search vs /api/x) never +// shadow each other regardless of how the handlers are written. + +// split_pipe — split "GET|POST" on '|' into ["GET","POST"]. Self-contained +// (no dependency on str_split runtime semantics). +fn split_pipe(s: String) -> [String] { + let out: [String] = native_list_empty() + let cur: String = "" + let n: Int = str_len(s) + let i: Int = 0 + while i < n { + let ch: String = str_slice(s, i, i + 1) + if str_eq(ch, "|") { + let out = native_list_append(out, cur) + let cur = "" + } else { + let cur = cur + ch + } + let i = i + 1 + } + let out = native_list_append(out, cur) + out +} + +// route_make_record — build a route record map from the @route decorator args. +fn route_make_record(fn_name: String, args: [String]) -> Map { + let na: Int = native_list_len(args) + let rpath: String = "" + if na >= 1 { let rpath = native_list_get(args, 0) } + let rmethod: String = "GET" + if na >= 2 { let rmethod = native_list_get(args, 1) } + let rkind: String = "exact" + if na >= 3 { let rkind = native_list_get(args, 2) } + let rsuffix: String = "" + if na >= 4 { let rsuffix = native_list_get(args, 3) } + { "name": fn_name, "path": rpath, "method": rmethod, "kind": rkind, "suffix": rsuffix } +} + +// route_spec_score — higher = more specific = emitted earlier. Ordering: +// exact > compound > suffix > prefix; within a class, a longer path/suffix +// wins (so /api/x/search sorts before /api/x). Guarantees correct dispatch +// independent of source order. +fn route_spec_score(rec: Map) -> Int { + let kind: String = rec["kind"] + let path: String = rec["path"] + let suffix: String = rec["suffix"] + let plen: Int = str_len(path) + let slen: Int = str_len(suffix) + if str_eq(kind, "exact") { return 4000000 + plen } + if str_eq(kind, "compound") { return 3000000 + plen * 100 + slen } + if str_eq(kind, "suffix") { return 2000000 + slen } + return 1000000 + plen +} + +// route_sort_desc — selection sort of route records by descending specificity. +// N is small (routes per module), so O(n^2) is fine and keeps codegen simple. +fn route_sort_desc(recs: [Map]) -> [Map] { + let n: Int = native_list_len(recs) + let out: [Map] = native_list_empty() + let used: [Bool] = native_list_empty() + let u: Int = 0 + while u < n { + let used = native_list_append(used, false) + let u = u + 1 + } + let picked: Int = 0 + while picked < n { + let best_i: Int = 0 - 1 + let best_score: Int = 0 - 1 + let i: Int = 0 + while i < n { + let is_used: Bool = native_list_get(used, i) + if !is_used { + let sc: Int = route_spec_score(native_list_get(recs, i)) + if sc > best_score { + let best_score = sc + let best_i = i + } + } + let i = i + 1 + } + let out = native_list_append(out, native_list_get(recs, best_i)) + // Rebuild `used` with best_i marked (runtime has no native_list_set). + let new_used: [Bool] = native_list_empty() + let j: Int = 0 + while j < n { + if j == best_i { + let new_used = native_list_append(new_used, true) + } else { + let new_used = native_list_append(new_used, native_list_get(used, j)) + } + let j = j + 1 + } + let used = new_used + let picked = picked + 1 + } + out +} + +// scan_routes — token-level scan collecting every @route-decorated fn as a +// route record. Runs once per module (like scan_fn_sigs) so the dispatcher can +// be synthesized in the streaming backend, which discards per-fn ASTs. Handles +// decorator STACKING: `@route(...) @manager fn` still records the route. +fn scan_routes(tokens: [Any]) -> [Map] { + let total: Int = native_list_len(tokens) / 2 + let recs: [Map] = native_list_empty() + let has_pending: Bool = false + let pending_args: [String] = native_list_empty() + let pos: Int = 0 + let going: Bool = true + while going { + if pos >= total { + let going = false + } else { + let k: String = tok_kind(tokens, pos) + if str_eq(k, "Eof") { + let going = false + } else { + if str_eq(k, "At") { + let dname: String = tok_value(tokens, pos + 1) + let p: Int = pos + 2 + let args: [String] = native_list_empty() + let ka: String = tok_kind(tokens, p) + if str_eq(ka, "LParen") { + let p = p + 1 + let running: Bool = true + while running { + let kd: String = tok_kind(tokens, p) + if str_eq(kd, "RParen") { + let running = false + } else { + if str_eq(kd, "Eof") { + let running = false + } else { + if str_eq(kd, "Str") { + let args = native_list_append(args, tok_value(tokens, p)) + } + let p = p + 1 + } + } + } + if str_eq(tok_kind(tokens, p), "RParen") { let p = p + 1 } + } + if str_eq(dname, "route") { + let has_pending = true + let pending_args = args + } + let pos = p + } else { + if str_eq(k, "Fn") { + let fname: String = tok_value(tokens, pos + 1) + if has_pending { + let recs = native_list_append(recs, route_make_record(fname, pending_args)) + let has_pending = false + } + let pos = pos + 2 + } else { + let pos = pos + 1 + } + } + } + } + } + recs +} + +// program_has_routes — did scan_routes find any @route fn? +fn program_has_routes(recs: [Map]) -> Bool { + native_list_len(recs) > 0 +} + +// route_method_guard — C boolean prefix guarding on HTTP method, or "" for none. +fn route_method_guard(method: String) -> String { + if str_eq(method, "") { return "" } + if str_eq(method, "ANY") { return "" } + if str_contains(method, "|") { + let parts: [String] = split_pipe(method) + let np: Int = native_list_len(parts) + let expr: String = "" + let i: Int = 0 + while i < np { + let m: String = native_list_get(parts, i) + if str_eq(m, "") { + let i = i + 1 + } else { + let piece: String = "str_eq(method, EL_STR(" + c_str_lit(m) + "))" + if str_eq(expr, "") { + let expr = piece + } else { + let expr = expr + " || " + piece + } + let i = i + 1 + } + } + if str_eq(expr, "") { return "" } + return "(" + expr + ") && " + } + "str_eq(method, EL_STR(" + c_str_lit(method) + ")) && " +} + +// route_match_expr — C boolean matching `clean` against the route path/kind. +fn route_match_expr(kind: String, path: String, suffix: String) -> String { + if str_eq(kind, "prefix") { + return "str_starts_with(clean, EL_STR(" + c_str_lit(path) + "))" + } + if str_eq(kind, "suffix") { + return "str_ends_with(clean, EL_STR(" + c_str_lit(path) + "))" + } + if str_eq(kind, "compound") { + return "str_starts_with(clean, EL_STR(" + c_str_lit(path) + ")) && str_ends_with(clean, EL_STR(" + c_str_lit(suffix) + "))" + } + "str_eq(clean, EL_STR(" + c_str_lit(path) + "))" +} + +// emit_route_dispatch — emit the generated el_route_dispatch definition from the +// specificity-sorted route records. No-op if there are no routes. +fn emit_route_dispatch(recs: [Map]) -> Void { + if !program_has_routes(recs) { return } + let sorted: [Map] = route_sort_desc(recs) + emit_line("// ── generated @route dispatcher (specificity-sorted) ──") + emit_line("el_val_t el_route_dispatch(el_val_t method, el_val_t clean, el_val_t path, el_val_t body) {") + let n: Int = native_list_len(sorted) + let i: Int = 0 + while i < n { + let rec = native_list_get(sorted, i) + let guard: String = route_method_guard(rec["method"]) + let match_e: String = route_match_expr(rec["kind"], rec["path"], rec["suffix"]) + let fn_name: String = rec["name"] + emit_line(" if (" + guard + match_e + ") { return " + fn_name + "(method, path, body); }") + let i = i + 1 + } + emit_line(" return EL_STR(\"__EL_NO_ROUTE__\");") + emit_line("}") + emit_blank() +} + // emit_streaming_preamble — emit #includes, forward decls, and file-scope lets // using the pre-scanned signature data (no full AST). fn emit_streaming_preamble(sigs: [Map], source: String) -> Void { @@ -3571,6 +3842,17 @@ fn codegen_streaming(tokens: [Any], sigs: [Map], source: String) -> emit_streaming_preamble(sigs, source) el_arena_pop(preamble_mark) + // @route: scan the token stream once for @route-decorated fns. Kept in + // codegen_streaming scope (survives the per-fn arena pops and el_release of + // tokens below via refcount, like `sigs`). If any exist, forward-declare the + // generated dispatcher NOW so hand-written fns (e.g. handle_request) may call + // it before its definition is emitted after the fn-emit loop. + let route_records: [Map] = scan_routes(tokens) + if program_has_routes(route_records) { + emit_line("el_val_t el_route_dispatch(el_val_t method, el_val_t clean, el_val_t path, el_val_t body);") + emit_blank() + } + // Detect whether there is a fn main() and whether there are top-level // executable stmts (for library detection) from sigs. let has_el_main: Bool = false @@ -3758,6 +4040,15 @@ fn codegen_streaming(tokens: [Any], sigs: [Map], source: String) -> } } + // @route: emit the generated dispatcher definition now — after every handler + // fn has been emitted, but before `tokens` is released (route_records holds + // its own refs to the extracted strings). No-op unless the module declared + // at least one @route fn. Emitted before the test/library early-returns so it + // is present in library modules (e.g. neuron's routes.el) too. + let route_arena_mark: Any = el_arena_push() + emit_route_dispatch(route_records) + el_arena_pop(route_arena_mark) + // Tokens fully consumed by the streaming loop — release now to free peak heap. el_release(tokens) diff --git a/lang/el-compiler/src/parser.el b/lang/el-compiler/src/parser.el index d93fcba..8cdca47 100644 --- a/lang/el-compiler/src/parser.el +++ b/lang/el-compiler/src/parser.el @@ -1758,23 +1758,68 @@ fn parse_stmt(tokens: [Any], pos: Int) -> Map { return make_result({ "stmt": "TryCatch", "try_body": try_body, "catch_name": catch_name, "catch_body": native_list_empty() }, p) } - // @decorator - capture decorator name and attach to following stmt + // @decorator - capture decorator name (and optional string args) and + // attach to the following stmt. Backward-compatible: bare @manager / + // @engine / @accessor still parse (no parens -> empty args). Decorators + // STACK: `@route("/p","GET") @manager fn f()` attaches BOTH to f via a + // `decorators` list [{name, args}]. The legacy `decorator` string is kept + // populated (topmost decorator) so the JS backend keeps working unchanged. if k == "At" { let p = pos + 1 let dec_name = tok_value(tokens, p) let p = p + 1 + // Optional decorator argument list: @name("a", "b", ...) + let dec_args = native_list_empty() + let ka = tok_kind(tokens, p) + if str_eq(ka, "LParen") { + let p = p + 1 + let running_da = true + while running_da { + let kd = tok_kind(tokens, p) + if str_eq(kd, "RParen") { + let running_da = false + } else { + if str_eq(kd, "Eof") { + let running_da = false + } else { + if str_eq(kd, "Str") { + let dec_args = native_list_append(dec_args, tok_value(tokens, p)) + } + let p = p + 1 + let kc = tok_kind(tokens, p) + if str_eq(kc, "Comma") { + let p = p + 1 + } + } + } + } + let p = expect(tokens, p, "RParen") + } let r = parse_stmt(tokens, p) let inner = r["node"] let p2 = r["pos"] let inner_kind: String = inner["stmt"] if str_eq(inner_kind, "FnDef") { + // Stack this decorator (topmost-first) onto any decorators the inner + // FnDef already carries from decorators written below this one. + let this_dec = { "name": dec_name, "args": dec_args } + let existing = inner["decorators"] + let dlist = native_list_empty() + let dlist = native_list_append(dlist, this_dec) + let ne: Int = native_list_len(existing) + let ei = 0 + while ei < ne { + let dlist = native_list_append(dlist, native_list_get(existing, ei)) + let ei = ei + 1 + } let with_dec = { "stmt": "FnDef", "name": inner["name"], "params": inner["params"], "body": inner["body"], "ret_type": inner["ret_type"], - "decorator": dec_name + "decorator": dec_name, + "decorators": dlist } // r result map fully consumed — release to free peak heap. el_release(r) diff --git a/tools/api-reshape/README.md b/tools/api-reshape/README.md index 0bd1cd6..228921c 100644 --- a/tools/api-reshape/README.md +++ b/tools/api-reshape/README.md @@ -10,6 +10,24 @@ Ground-truth: routes verified against the live cognition binary `feat/cognitive-architecture`, `engram/src/server.el`). Built + validated on an **isolated nsbx clone** (`:8900`); live `:8742` untouched. +**The decoration IS the API.** `surface.el` is El-native: each op is one function +decorated with its `@route` (codegen synthesizes `el_route_dispatch` — no +hand-written 90-branch dispatch) and its VBD role (`@accessor` = engram I/O, +`@manager` = agentic orchestration + DHARMA emitter). Handlers call the engram +**in-process** via `engram_*` builtins (not `http_get` — that idiom only existed +because the old MCP wrapper was a separate process). Decorate→serve is **proven**: +`route_proof.el` serves decorated handlers on :8951; `surface.el` compiles and the +dispatcher is generated for all 8 ops. See `SEAM_STAGED.md` for the three-part seam +(route / telemetry+interoception / bus) ground-truth and the staged boundary diff. + +**Clone boot recipe (gate-1):** cold-boot from `neuron.egm` with the WAL set aside +(the live-store clone's WAL is torn and loops on replay) + `ENGRAM_WAL=on` (routes +node-writes to the WAL-append path; without it `persist_node`→full-store checkpoint +**segfaults** a clone) + `ENGRAM_GEOMETRY_PRIMING=1`. **Anchors must be node-ids** +(think/ground/learn resolve each seed via `engram_find_node_index`; free text → +"geometry unavailable"). With this recipe the **full op set is proven live on the +clone** (below). + ## Layer 1 — geometry ops | op | signature | engram route | replaces (~) | @@ -31,13 +49,13 @@ The base verbs all agentic behavior composes from — grounded in the LIVE cog-arch (`think` is the one operation; faculties are its steering-space labels; the correspondence-beat is the reflexive learning loop). -| op | signature | engram route | status on clone | -|----|-----------|--------------|-----------------| -| `think` | `think({seeds, faculty})` faculty ∈ reason·abduce·induce·plan·analogize·recognize·discern·synthesize | GET `/api/think` | route wired; geometry-gated on HTTP daemon clone (validated via C harness: held-Brier 0.0286→0.0006 @ 10,994 nodes) | -| `attend` | `attend({node, observer, salience})` | POST `/api/attend` | **live** (returns `salient-to`) | -| `assert` | `assert({claim, for_whom, floor})` — realize, honesty-floored | GET `/api/assert` | **live** | -| `ground` | `ground({claim, evidence, for_whom})` | POST `/api/ground` | route wired; geometry-gated | -| `learn` | `learn({seeds, faculty, keystone})` — the correspondence-beat | POST `/api/correspondence-beat` | route wired; geometry-gated | +| op | signature | engram builtin | status on clone (gate-1 recipe) | +|----|-----------|----------------|---------------------------------| +| `think` | `think({seeds, faculty})` faculty ∈ reason·abduce·induce·plan·analogize·recognize·discern·synthesize | `engram_think_json` | **PROVEN** — all 8 faculties return real 768-dim gradients (n_support 30–282) | +| `attend` | `attend({node, observer, salience})` | `engram_attend_json` | **PROVEN** (returns `salient-to`) | +| `assert` | `assert({claim, for_whom, floor})` — realize, honesty-floored | `engram_assert_json` | **PROVEN** | +| `ground` | `ground({claim, evidence, for_whom})` node-id anchors | `engram_ground_json` | **PROVEN** (grounded-by edge, grounding=0.912, written) | +| `learn` | `learn({seeds, faculty, keystone})` — the correspondence-beat | `engram_correspondence_beat_json` | **PROVEN** (real Stance: `stance-induce-…`, brier, reliability, written) | `comprehend`/`realize`/`intend` are **compositions**, not separate live primitives: comprehend = write+activate (world→geometry), realize = assert @@ -67,11 +85,15 @@ and **Neuron does the agentic work over its own geometry** — the beginning of running itself. ## Files -- `surface.el` — the reshaped surface (ops + agentic primitives + old-tool aliases), engram-HTTP idiom matching `neuron/mcp-wrapper/src/main.el`. -- `parity.sh` — proves it against the clone (`source ../../.nsbx-env && ./parity.sh`). Last run: **12 proven, 0 failed, 14 wired-but-gated/exec-skipped**. +- `surface.el` — the reshaped surface as **decorated El-native components** (`@route` + `@accessor`/`@manager`, in-process `engram_*` builtins). Compiles; dispatcher generated for all 8 ops. +- `route_proof.el` — a standalone decorated El service that **proves decorate→serve** on :8951 (built with the worktree-rebuilt `elc-route`). +- `SEAM_STAGED.md` — the three-part seam (route / telemetry+interoception / bus) ground-truth + the exact staged `cg_fn` diff for boundary auto-emit. +- `agentic_loop.el` — the four-call loop (think→attend→learn→read) as compilable El. +- `parity.sh` — API-level parity harness against the clone. -## Honest ledger (built vs stubbed) -- **Proven live on clone:** read (3 forms), attend, assert, aperture-boundedness, parity spines. -- **Route-wired, geometry-gated on the HTTP daemon clone:** think (8 faculties), ground, learn. (The centered geometry isn't primed by the daemon boot on a clone — same on the peer clone `:8901`; the operation is exercised via the C cog-arch harness per `nsbx validate`.) -- **Route-wired, exec-skipped on clone:** write, relate, supersede — the paged-store node-write crashes a WAL-less cold-boot clone; run on a write-healthy store. -- **Not done:** compiling `surface.el` into the MCP server + hot-swap; wiring all ~90 aliases into `dispatch_tool_call`; the engram-side fix so a daemon clone primes geometry + survives writes. No promote to live (per rails). +## Honest ledger (built vs staged) +- **Route seam — IMPLEMENTED + PROVEN:** ported the `@route` codegen (from `feat/el-route-decorators`) into the worktree, rebuilt `elc` self-host, proved decorate→serve (`route_proof.el` on :8951); `surface.el` compiles with `el_route_dispatch` generated for all 8 ops. +- **All ops PROVEN live on the clone** (gate-1 boot recipe, node-id anchors): read, write, relate, supersede (immutable), tombstone, think (8 faculties), ground, attend, learn — daemon alive through all mutations (node_count 13173→13176). +- **Aperture-boundedness PROVEN:** vantage-read `limit=3 → 15 KB` vs `limit=50 → 363 KB` (fixes the whole-self dump). +- **Bus:** `@manager` ops emit on the real `dharma_*` bus (explicit today, compiles) — same transport as the swarm (`wt/swarm-ccr`). +- **STAGED (not guessed — needs the cognition-engram rebuild to verify link):** auto-injecting telemetry/interoception + bus emission at the decorated boundary (`cg_fn` diff in `SEAM_STAGED.md`); building the cognition engram with `surface.el` compiled in. No promote to live, no cutover (per rails). diff --git a/tools/api-reshape/SEAM_STAGED.md b/tools/api-reshape/SEAM_STAGED.md new file mode 100644 index 0000000..b8261f7 --- /dev/null +++ b/tools/api-reshape/SEAM_STAGED.md @@ -0,0 +1,70 @@ +# Decorator-as-seam — what's REAL vs STAGED (with the exact diff) + +The reshape rests on one idea: **the decorator boundary is the single interception +seam.** Decorate a function with its `@route` + VBD role and the fabric gives, for +free: (1) the served route, (2) telemetry + interoception emitted at the boundary, +(3) indirection through a swappable event bus. Ground-truth of each, with the +minimal change to close the gaps. + +## Ground truth (file:line) + +| seam | real today? | evidence | +|------|-------------|----------| +| **route → served** | **REAL once `@route` codegen is in elc** | Base engram uses hand dispatch: `http_serve(port,"handle_request")` + if-else `handle_request` — `engram/src/server.el:592,742`. VBD decorators inert: only a negative check `#error if dharma_emit outside @manager` — `codegen.el:2929-2934`; `lang/spec/language.md:449` "decorators with structural meaning today: none". `@route(path,method,kind,suffix)` synthesizes `el_route_dispatch` — `codegen.el:3500-3852` — but only on **unmerged** `feat/el-route-decorators`. **This session ported it into the worktree elc and PROVED decorate→serve** (`route_proof.el` on :8951; `surface.el` compiles, dispatcher generated for all 8 ops). | +| **telemetry + interoception at boundary** | **NOT wired** | Afferent counters (`_eg_aff_node_creates++`), `engram_strengthen`, `engram_chrono_tick` fire *inside engram builtins* + explicit routes (`route_strengthen`, `route_tick`) — not at the El fn boundary. `cg_fn` (`codegen.el:2919`) injects zero instrumentation. | +| **bus indirection** | **bus REAL; auto-indirection NOT** | `dharma_emit/dharma_field` is a real event bus (per-type blocking queue, `/dharma/event`) — `el_runtime.c:11685-11987`. Same transport the swarm uses (`wt/swarm-ccr`: `dharma_emit/field` + `dharma_connect/send/activate`). `@manager` *may* call it (enforced) but decoration does not auto-insert it. `surface.el` calls it explicitly today (correct, compiles). | + +## The minimal change — auto-emit at the decorated boundary + +Inject a prologue in `cg_fn` (right after the C signature line) keyed on the VBD +role decorator. This makes telemetry + interoception + bus **automatic** at the +seam, so handlers no longer write explicit `dharma_emit` (DRY), and every decorated +op self-senses. + +```el +// lang/el-compiler/src/codegen.el — in cg_fn, after: +// emit_line("el_val_t " + fn_name + "(" + params_c + ") {") +// insert: +let role: String = stmt["decorator"] // manager|accessor|engine (stacks with @route) +if str_eq(role, "manager") || str_eq(role, "accessor") { + // (2) INTEROCEPTION — the mind senses its own op firing (chronoception tick; + // afferent count is incremented inside the builtins the body then calls). + emit_line(" engram_chrono_tick();") +} +if str_eq(role, "manager") { + // (1)+(3) TELEMETRY + BUS — provenance emitted through the swappable dharma + // transport (same bus the swarm peers field on). Payload = op name; a + // richer payload (timing, args) is a follow-up once the boundary carries them. + emit_line(" dharma_emit(EL_STR(\"neuron.op." + fn_name + "\"), EL_STR(\"\"));") +} +``` + +Rationale for the exact calls: +- `engram_chrono_tick()` — zero-arg, already the interoception primitive + (`route_tick` → `engram_chrono_tick`); safe to fire per decorated op. +- `dharma_emit(event, payload)` — the real bus (`el_runtime.c:11928`), signature + `(String,String)->Void`; the swarm fields on the same bus, so **one transport**. +- `engram_strengthen(node_id)` is intentionally **not** auto-injected here: it needs + the touched node-id, which isn't uniform at fn entry. Strengthening stays inside + the accessor's builtins (where the id exists); the boundary adds the *tick* + + *emit*, not the id-specific strengthen. + +## Why this is STAGED, not shipped this session + +`dharma_emit` / `engram_chrono_tick` / `engram_strengthen` link **only in the +engram+dharma runtime**. A standalone El service (`route_proof.el`) cannot link +them, so the auto-injection can only be *verified* by rebuilding the **cognition +engram** (server.el + the geometry/cognition `el_runtime.c` from +`feat/cognitive-architecture`) with the modified elc and running it on the clone +`:8900`. That rebuild is a multi-branch integration + a delicate ~3.5 MB C build +(AGENTS.md warns of 27 GB OOM on folded builds). Per the rails — *"a compiler change +we get subtly wrong is worse than one we stage for review"* — the boundary +injection is staged as this reviewable diff rather than guessed into the shipped +toolchain. The **route** half of the seam is already proven end-to-end. + +## Verification plan (when the boundary injection is approved) + +1. Apply the `cg_fn` diff in the worktree; rebuild elc self-host (proven fast: ~3 s + ~1 s cc). +2. Integrate `feat/cognitive-architecture` engram runtime + `surface.el` into the worktree server; build the engram binary with the new elc. +3. Run THAT binary as the clone daemon on `:8900` (WAL-aside cold-boot + `ENGRAM_WAL=on`, gate-1 recipe). Live `:8742` untouched. +4. Drive `neuron.think/attend/learn` and assert: a `neuron.op.*` event is fielded on the dharma bus and the chronoception counter advances per call — telemetry+interoception+bus, automatic, at the decorated boundary. diff --git a/tools/api-reshape/agentic_loop.el b/tools/api-reshape/agentic_loop.el new file mode 100644 index 0000000..c8c925b --- /dev/null +++ b/tools/api-reshape/agentic_loop.el @@ -0,0 +1,176 @@ +// agentic_loop.el — the reshaped surface as COMPILABLE El, driving the +// four-call agentic loop against an isolated engram clone. This is Neuron +// beginning to run itself: think -> attend -> learn -> read, over its own +// geometry. Compile: elc --target=c agentic_loop.el ... (see build_and_run.sh). +// +// Ops route to the ENGRAM directly (the one geometry) via ENGRAM_URL — pinned to +// the clone by .nsbx-env. Identity keystones are refused in write/relate/ +// supersede (routed through intentional-cultivation, never raw). Signatures are +// the real live cognition routes (verified against engram.cognition-20260814). + +fn engram_url() -> String { + let u: String = env("ENGRAM_URL") + if str_eq(u, "") { return "http://127.0.0.1:8900" } + return u +} +fn engram_key() -> String { + let k: String = env("ENGRAM_API_KEY") + if str_eq(k, "") { return "sbx-dev-api-reshape" } + return k +} +fn SELF_KEY() -> String { return "kn-efeb4a5b-5aff-4759-8a97-7233099be6ee" } +fn VALUES_KEY() -> String { return "kn-5b606390-a52d-4ca2-8e0e-eba141d13440" } + +// self/values name -> keystone id; anything else passes through unchanged. +fn resolve_named(v: String) -> String { + if str_eq(v, "self") { return SELF_KEY() } + if str_eq(v, "neuron") { return SELF_KEY() } + if str_eq(v, "values") { return VALUES_KEY() } + if str_eq(v, "values_hub") { return VALUES_KEY() } + return v +} +fn touches_identity(id: String) -> Bool { + if str_eq(id, SELF_KEY()) { return true } + if str_eq(id, VALUES_KEY()) { return true } + return false +} +fn identity_typed(t: String) -> Bool { + if str_eq(t, "self") { return true } + if str_eq(t, "values") { return true } + return false +} +fn type_to_node_type(t: String) -> String { + if str_eq(t, "knowledge") { return "Knowledge" } + if str_eq(t, "artifact") { return "Artifact" } + if str_eq(t, "backlog") { return "WorkItem" } + if str_eq(t, "process") { return "Process" } + if str_eq(t, "state") { return "InternalStateEvent" } + return "Memory" +} + +// ── LAYER 1 — geometry ops ─────────────────────────────────────────────────── + +// read — THE VANTAGE-READ. Re-origin at a point + aperture -> a BOUNDED slice. +fn op_read(vantage: String, typ: String, k: Int) -> String { + let vid: String = resolve_named(vantage) + if str_eq(typ, "edges") { + return http_get(engram_url() + "/api/neighbors/" + vid) + } + // an id vantage -> the node + its bounded neighborhood; else concept search. + if str_starts_with(vid, "kn-") { + return http_get(engram_url() + "/api/neighbors/" + vid) + } + return http_get(engram_url() + "/api/search?q=" + url_encode(vid) + "&limit=" + int_to_str(k)) +} + +// write — add a node; type selects node_type. Identity types refused. +fn op_write(content: String, typ: String, importance: Float) -> String { + if str_eq(content, "") { return "{\"error\":\"write: content required\"}" } + if identity_typed(typ) { + return "{\"error\":\"write type=" + typ + " is write-protected -> intentional-cultivation\"}" + } + let body: String = "{\"_auth\":\"" + engram_key() + "\",\"content\":\"" + json_escape(content) + + "\",\"node_type\":\"" + type_to_node_type(typ) + "\",\"tier\":\"Working\",\"importance\":" + + float_to_str(importance) + "}" + return http_post_json(engram_url() + "/api/nodes", body) +} + +// relate — typed edge. Refused if either endpoint is an identity keystone. +fn op_relate(from_id: String, to_id: String, relationship: String) -> String { + if str_eq(from_id, "") { return "{\"error\":\"relate: from required\"}" } + if str_eq(to_id, "") { return "{\"error\":\"relate: to required\"}" } + if touches_identity(from_id) { return "{\"error\":\"relate: identity keystone write-protected\"}" } + if touches_identity(to_id) { return "{\"error\":\"relate: identity keystone write-protected\"}" } + let rel: String = if str_eq(relationship, "") { "associates" } else { relationship } + let body: String = "{\"_auth\":\"" + engram_key() + "\",\"from_id\":\"" + from_id + + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + rel + "\",\"weight\":0.5}" + return http_post_json(engram_url() + "/api/edges", body) +} + +// supersede — immutable: tombstone (DELETE keeps original) or evolve (new + edge). +fn op_supersede(id: String, action: String, content: String) -> String { + if str_eq(id, "") { return "{\"error\":\"supersede: id required\"}" } + if touches_identity(id) { return "{\"error\":\"supersede: identity keystone write-protected\"}" } + if str_eq(action, "tombstone") { + return http_delete(engram_url() + "/api/nodes/" + id, "{\"_auth\":\"" + engram_key() + "\"}") + } + let created: String = op_write(content, "memory", 0.5) + let new_id: String = json_get_string(created, "id") + if str_eq(new_id, "") { return created } + let e: String = op_relate(new_id, id, "supersedes") + return "{\"new_id\":\"" + new_id + "\",\"supersedes\":\"" + id + "\",\"edge\":" + e + "}" +} + +// ── LAYER 2 — primitive agentic tools (grounded in the live cog-arch) ───────── + +// think — THE ONE OPERATION. anchor (node ids) steered by faculty -> gradient. +fn op_think(seeds: String, faculty: String) -> String { + let s: String = resolve_named(seeds) + let f: String = if str_eq(faculty, "") { "reason" } else { faculty } + return http_get(engram_url() + "/api/think?seeds=" + url_encode(s) + "&faculty=" + f) +} +// attend — aim attention at a region. +fn op_attend(node: String, observer: String) -> String { + let n: String = resolve_named(node) + let o: String = if str_eq(observer, "") { SELF_KEY() } else { resolve_named(observer) } + let body: String = "{\"_auth\":\"" + engram_key() + "\",\"node\":\"" + n + + "\",\"observer\":\"" + o + "\",\"salience\":\"0.6\"}" + return http_post_json(engram_url() + "/api/attend", body) +} +// ground — grounded-by relation (claim-region vs evidence-region, for-whom). +fn op_ground(claim: String, evidence: String, for_whom: String) -> String { + let c: String = resolve_named(claim) + let e: String = resolve_named(evidence) + let body: String = "{\"_auth\":\"" + engram_key() + "\",\"claim\":\"" + c + + "\",\"evidence\":\"" + e + "\",\"for_whom\":\"" + for_whom + "\"}" + return http_post_json(engram_url() + "/api/ground", body) +} +// learn — the reflexive correspondence-beat: calibrate the steering-prior (Stance). +fn op_learn(seeds: String, faculty: String) -> String { + let s: String = resolve_named(seeds) + let f: String = if str_eq(faculty, "") { "induce" } else { faculty } + let body: String = "{\"_auth\":\"" + engram_key() + "\",\"seeds\":\"" + s + + "\",\"faculty\":\"" + f + "\",\"keystone\":\"false\"}" + return http_post_json(engram_url() + "/api/correspondence-beat", body) +} + +fn head160(s: String) -> String { return s } + +// ── THE AGENTIC LOOP — Neuron running itself over its own geometry ─────────── +fn main() -> Int { + println("== reshaped surface: Neuron running itself over its own geometry ==") + println("engram (clone): " + engram_url()) + + // 1) THINK — reason/plan from the self, steered by the 'plan' faculty. + let g: String = op_think("self", "plan") + println("") + println("1. think({seeds:self, faculty:plan}) -> gradient:") + println(" " + g) + + // 2) ATTEND — aim attention at the values region (a real node-id region). + let a: String = op_attend("values", "self") + println("") + println("2. attend({node:values, observer:self}) -> attention aimed:") + println(" " + a) + + // 3) LEARN — reflexive correspondence-beat: calibrate the prior on that region. + let l: String = op_learn("values", "induce") + println("") + println("3. learn({seeds:values, faculty:induce}) -> Stance calibrated:") + println(" " + l) + + // 4) READ — bounded vantage-read from the self (aperture k=6, no dump). + let r: String = op_read("self", "edges", 6) + println("") + println("4. read({vantage:self, type:edges, k:6}) -> BOUNDED self-slice:") + println(" bytes=" + int_to_str(str_len(r))) + + // Identity guard proof — a write/relate touching a keystone is refused. + println("") + println("guard: write(type=values) -> " + op_write("attempt", "values", 0.5)) + println("guard: relate(to=self keystone) -> " + op_relate("some-node", SELF_KEY(), "associates")) + + println("") + println("== loop complete: think -> attend -> learn -> read, all over the live geometry ==") + return 0 +} diff --git a/tools/api-reshape/route_proof.el b/tools/api-reshape/route_proof.el new file mode 100644 index 0000000..b6bb5cc --- /dev/null +++ b/tools/api-reshape/route_proof.el @@ -0,0 +1,85 @@ +// route_proof.el — PROVES decorate -> serve in El. Each handler is DECORATED +// with its route AND its VBD role (stacked: @route(...) @accessor|@manager fn). +// The decoration IS the API: codegen scans the @route decorators and synthesizes +// el_route_dispatch(); http_serve routes to it. No hand-written 90-branch dispatch. +// +// This standalone service proves the SEAM (route+serve). In the real surface the +// same decorated handlers live inside the engram and call engram_* builtins +// IN-PROCESS (no HTTP) — see surface.el. +// +// Build: elc-route route_proof.el > route_proof.c ; cc ... ; run on a sandbox port. + +// query-stripped path (the dispatcher matches on this). +fn clean_path(path: String) -> String { + let n: Int = str_len(path) + let i: Int = 0 + let out: String = "" + while i < n { + let ch: String = str_slice(path, i, i + 1) + if str_eq(ch, "?") { return out } + let out = out + ch + let i = i + 1 + } + return out +} + +// ── the reshaped surface as DECORATED handlers (route + VBD role) ───────────── + +@route("/read", "GET") +@accessor +fn h_read(method: String, path: String, body: String) -> String { + return "{\"op\":\"read\",\"role\":\"accessor\",\"vantage-read\":\"bounded-slice\",\"served-by\":\"@route decoration\"}" +} + +@route("/write", "POST") +@accessor +fn h_write(method: String, path: String, body: String) -> String { + return "{\"op\":\"write\",\"role\":\"accessor\",\"served-by\":\"@route decoration\"}" +} + +@route("/relate", "POST") +@accessor +fn h_relate(method: String, path: String, body: String) -> String { + return "{\"op\":\"relate\",\"role\":\"accessor\"}" +} + +@route("/supersede", "POST") +@accessor +fn h_supersede(method: String, path: String, body: String) -> String { + return "{\"op\":\"supersede\",\"role\":\"accessor\",\"immutable\":true}" +} + +@route("/think", "GET") +@manager +fn h_think(method: String, path: String, body: String) -> String { + return "{\"op\":\"think\",\"role\":\"manager\",\"one-operation\":true}" +} + +@route("/attend", "POST") +@manager +fn h_attend(method: String, path: String, body: String) -> String { + return "{\"op\":\"attend\",\"role\":\"manager\"}" +} + +@route("/learn", "POST") +@manager +fn h_learn(method: String, path: String, body: String) -> String { + return "{\"op\":\"learn\",\"role\":\"manager\",\"correspondence-beat\":true}" +} + +// ── http_serve handler: call the GENERATED dispatcher; mixed-mode fallthrough ── +fn dispatch(method: String, path: String, body: String) -> String { + let clean: String = clean_path(path) + let r: String = el_route_dispatch(method, clean, path, body) + if str_eq(r, "__EL_NO_ROUTE__") { + return "{\"error\":\"no route\",\"path\":\"" + clean + "\"}" + } + return r +} + +fn main() -> Int { + let port: Int = parse_int(env("ROUTE_PROOF_PORT"), 8951) + println("[route_proof] decorate->serve on :" + int_to_str(port)) + http_serve(port, "dispatch") + return 0 +} diff --git a/tools/api-reshape/surface.el b/tools/api-reshape/surface.el index 8b17b12..2c07e38 100644 --- a/tools/api-reshape/surface.el +++ b/tools/api-reshape/surface.el @@ -1,244 +1,164 @@ -// surface.el — the RESHAPED Neuron API surface. +// surface.el — the RESHAPED Neuron surface as EL-NATIVE DECORATED COMPONENTS. // -// Design: artifact 0e828907 ("Neuron API surface reshape") + design-brief -// 2b8078cf §5. Collapse ~90 functional-CRUD MCP tools into a handful of GEOMETRY -// OPS over the one geometry, plus the LIVE agentic primitives already in the -// engram cognition build. TYPE IS A PARAMETER, not a tool-per-noun. +// Design: artifact 0e828907 + design-brief 2b8078cf §5. THE DECORATION IS THE API. +// Each op is one function decorated with (a) its @route — codegen synthesizes the +// HTTP dispatcher (el_route_dispatch), no hand-written 90-branch handle_request — +// and (b) its VBD role — @accessor (engram I/O) or @manager (agentic orchestration +// + sole DHARMA emitter). Handlers call the engram IN-PROCESS via engram_* builtins +// (NOT http_get: the old MCP-wrapper http idiom existed only because it was a +// separate process; compiled into the engram, the geometry is a direct call). // -// This module is ADDITIVE. It defines the new ops as functions over the engram -// HTTP API (engram_url() = the isolated clone in dev; :8742 in prod). The old -// noun-tools become thin aliases that call these ops (bottom of file) so every -// existing caller keeps working through the transition — parity-gated by -// tools/api-reshape/parity.sh. +// This file is designed to be INCLUDED IN the engram server (engram/src/server.el) +// so the engram_* builtins + server helpers (query_param, json_get_string, +// extract_id, err_json, engram_node_full, persist_node, ...) link in-process. // -// Idiom matches neuron/mcp-wrapper/src/main.el: http_get / http_post_json, -// json_get_string/_int/_float, mcp_json_result / mcp_text_result. +// Handler contract (from the @route codegen): uniform (method, path, body)->String. // -// Endpoint ground-truth (verified against the live cognition binary -// engram.cognition-20260814-160045; routes on branch feat/cognitive-architecture -// engram/src/server.el): -// write -> POST /api/nodes {content,node_type,label,salience,importance,confidence,tier,tags,_auth} -// relate -> POST /api/edges {from_id,to_id,relation,weight,_auth} -// read -> GET /api/nodes/ | /api/search?q&limit | /api/activate?q&depth | /api/neighbors/ | /api/nearest/?k -// think -> GET /api/think?seeds=&faculty= -// attend -> POST /api/attend {node,observer,salience,_auth} -// ground -> POST /api/ground {claim,evidence,for_whom,_auth} -// assert -> GET /api/assert?claim&for_whom&floor -// learn -> POST /api/correspondence-beat {seeds,faculty,keystone,_auth} (reflexive prior calibration) +// Seam status (ground-truthed 2026-08-14, file:line in the report): +// @route -> served: REAL once the ported @route codegen is in elc (proven: +// tools/api-reshape/route_proof.el serves decorated handlers on :8951). +// @manager dharma_emit -> bus: REAL today (explicit call; @manager may emit). +// STAGED codegen change makes it AUTOMATIC at the boundary (report §diff), +// sharing the one dharma_* transport the swarm (wt/swarm-ccr) uses. +// @accessor telemetry (strengthen/afferent/chronoception): fires inside the +// engram builtins today; STAGED to also fire at the decorated boundary. -// ───────────────────────────────────────────────────────────────────────────── -// LAYER 1 — GEOMETRY OPS (type is a parameter) -// ───────────────────────────────────────────────────────────────────────────── - -// read — THE VANTAGE-READ. Re-origin at a point (node id, concept, or `self`), -// apply salience + recency + APERTURE, return a BOUNDED slice. `type` filters -// which projection of the geometry to surface. This is CCR applied to the self; -// it structurally fixes the whole-self dump (aperture caps the byte size). -// -// read({ vantage, type?, aperture?, faculty? }) -// vantage : node id | free-text concept | "self" | "values" -// type : memory|knowledge|backlog|artifact|process|self|edges|state (filter; default = mixed) -// aperture: { k?: Int, depth?: Int } — the bound. small k/depth => small slice. -fn op_read(args: String) -> String { - let vantage: String = pick_vantage(args) - let typ: String = json_get_string(args, "type") - let k_raw: Int = json_get_int(args, "k") - let k: Int = if k_raw > 0 { k_raw } else { 12 } // aperture default (bounded) - let depth_raw: Int = json_get_int(args, "depth") - let depth: Int = if depth_raw > 0 { depth_raw } else { 2 } - - let vid: String = resolve_named(vantage) // self/values -> keystone ids - - // type=edges or an id vantage -> neighborhood read (bounded by depth) - if str_eq(typ, "edges") { - if str_eq(vid, "") { return mcp_text_result("read(type=edges) needs a node-id vantage") } - return mcp_json_result(http_get(engram_url() + "/api/neighbors/" + vid)) - } - // an id vantage with no type -> the node itself + its bounded neighborhood - if !str_eq(vid, "") && str_eq(typ, "") { - return mcp_json_result(http_get(engram_url() + "/api/neighbors/" + vid + "?depth=" + int_to_str(depth))) - } - // a concept vantage -> salience-ranked geometric retrieval, aperture=k - // (this single spine replaces searchKnowledge/searchEntities/recall/browseKnowledge/reviewBacklog/findArtifacts... - // — `type` becomes a post-filter tag rather than a separate tool) - let q: String = if str_eq(vid, "") { vantage } else { vid } - return mcp_json_result(http_get(engram_url() + "/api/search?q=" + url_encode(q) + "&limit=" + int_to_str(k))) +// self/values keystones — identity, write-protected (intentional-cultivation only). +fn is_identity_id(id: String) -> Bool { + if str_eq(id, "kn-efeb4a5b-5aff-4759-8a97-7233099be6ee") { return true } + if str_eq(id, "kn-5b606390-a52d-4ca2-8e0e-eba141d13440") { return true } + return false +} +fn type_node_type(t: String) -> String { + if str_eq(t, "knowledge") { return "Knowledge" } + if str_eq(t, "artifact") { return "Artifact" } + if str_eq(t, "backlog") { return "WorkItem" } + if str_eq(t, "process") { return "Process" } + if str_eq(t, "state") { return "InternalStateEvent" } + return "Memory" } -// write — add a node. `type` selects the node_type (memory|knowledge|artifact| -// backlog|process|state). Replaces remember/captureKnowledge/draftArtifact/ -// planWork/defineProcess/addWonderQuestion/logInternalStateEvent/recordObservation. -// Identity (type=self|values) is REFUSED here — routes through intentional-cultivation. -fn op_write(args: String) -> String { - let content: String = json_get_string(args, "content") - if str_eq(content, "") { return mcp_text_result("write: content is required") } - let typ: String = json_get_string(args, "type") - if identity_typed(typ) { - return mcp_text_result("write: type=" + typ + " is write-protected; route identity through intentional-cultivation, not raw write") - } - let node_type: String = type_to_node_type(typ) // memory->Memory, knowledge->Knowledge, ... - let tags: String = json_get_string(args, "tags") - let imp_present: String = json_get_raw(args, "importance") - let importance: Float = if str_eq(imp_present, "") { 0.5 } else { json_get_float(args, "importance") } - let body: String = "{\"_auth\":\"" + engram_key() + "\",\"content\":\"" + json_escape(content) - + "\",\"node_type\":\"" + node_type + "\",\"tags\":\"" + json_escape(tags) - + "\",\"importance\":" + float_to_str(importance) + "}" - return mcp_json_result(http_post_json(engram_url() + "/api/nodes", body)) +// ── LAYER 1 — geometry ops (@accessor: engram I/O, in-process) ──────────────── + +// read — THE VANTAGE-READ. re-origin + aperture -> BOUNDED slice. type=edges reads +// the neighborhood; a concept vantage reads salience-ranked geometry (limit=aperture). +@route("/api/read", "GET") +@accessor +fn op_read(method: String, path: String, body: String) -> String { + let vantage: String = query_param(path, "vantage") + if str_eq(vantage, "") { return err_json("read: vantage required") } + let typ: String = query_param(path, "type") + let k: Int = query_int(path, "k", 12) // aperture (bounded by construction) + if str_eq(typ, "edges") { return engram_neighbors_json(vantage) } + if str_starts_with(vantage, "kn-") { return engram_neighbors_json(vantage) } + return engram_retrieve_geometric_json(vantage, k) } -// relate — add a typed edge. Replaces linkEntities/linkCausal/restructureCausalGraph/pin. -fn op_relate(args: String) -> String { - let from_id: String = json_get_string(args, "from") - let to_id: String = json_get_string(args, "to") - let rel_raw: String = json_get_string(args, "relationship") - let relation: String = if str_eq(rel_raw, "") { "associates" } else { rel_raw } - if str_eq(from_id, "") || str_eq(to_id, "") { return mcp_text_result("relate: from and to are required") } - if touches_identity(from_id) || touches_identity(to_id) { - return mcp_text_result("relate: identity keystones are write-protected; route through intentional-cultivation") - } - let w_present: String = json_get_raw(args, "weight") - let weight: Float = if str_eq(w_present, "") { 0.5 } else { json_get_float(args, "weight") } - let body: String = "{\"_auth\":\"" + engram_key() + "\",\"from_id\":\"" + from_id - + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + relation - + "\",\"weight\":" + float_to_str(weight) + "}" - return mcp_json_result(http_post_json(engram_url() + "/api/edges", body)) +// write — add a node; type -> node_type. Identity types refused. +@route("/api/write", "POST") +@accessor +fn op_write(method: String, path: String, body: String) -> String { + let content: String = json_get_string(body, "content") + if str_eq(content, "") { return err_json("write: content required") } + let typ: String = json_get_string(body, "type") + if str_eq(typ, "self") { return err_json("write: identity is write-protected -> intentional-cultivation") } + if str_eq(typ, "values") { return err_json("write: identity is write-protected -> intentional-cultivation") } + let tags: String = json_get_string(body, "tags") + let imp: Float = json_get_float(body, "importance") + let id: String = engram_node_full(content, type_node_type(typ), content, 0.5, imp, 1.0, "Working", tags) + let saved: Int = persist_node(id) + return "{\"id\":\"" + id + "\",\"type\":\"" + typ + "\"}" } -// supersede — IMMUTABLE evolve/replace/tombstone/promote. NEVER hard-deletes. -// action=evolve|supersede : write a NEW node with the new content, then -// relate(new -> old, "supersedes"). Old is retained. -// action=tombstone : hide from default reads (node + edges kept, recoverable). -// action=promote : raise the tier (Working->Semantic / note->lesson->canonical). -fn op_supersede(args: String) -> String { - let id: String = json_get_string(args, "id") - if str_eq(id, "") { return mcp_text_result("supersede: id is required") } - if touches_identity(id) { - return mcp_text_result("supersede: identity keystones are write-protected; route through intentional-cultivation") - } - let action_raw: String = json_get_string(args, "action") - let action: String = if str_eq(action_raw, "") { "supersede" } else { action_raw } +// relate — typed edge. Refused if either endpoint is an identity keystone. +@route("/api/relate", "POST") +@accessor +fn op_relate(method: String, path: String, body: String) -> String { + let from_id: String = json_get_string(body, "from") + let to_id: String = json_get_string(body, "to") + if str_eq(from_id, "") { return err_json("relate: from required") } + if str_eq(to_id, "") { return err_json("relate: to required") } + if is_identity_id(from_id) { return err_json("relate: identity keystone write-protected") } + if is_identity_id(to_id) { return err_json("relate: identity keystone write-protected") } + let rel_raw: String = json_get_string(body, "relationship") + let rel: String = if str_eq(rel_raw, "") { "associates" } else { rel_raw } + let ec0: Int = engram_edge_count() + engram_connect(from_id, to_id, 0.5, rel) + let saved: Int = persist_edges_since(ec0) + return "{\"ok\":true,\"from\":\"" + from_id + "\",\"to\":\"" + to_id + "\",\"relationship\":\"" + rel + "\"}" +} +// supersede — IMMUTABLE. tombstone (marker + edge, original kept) | evolve (new + edge). +@route("/api/supersede", "POST") +@accessor +fn op_supersede(method: String, path: String, body: String) -> String { + let id: String = json_get_string(body, "id") + if str_eq(id, "") { return err_json("supersede: id required") } + if is_identity_id(id) { return err_json("supersede: identity keystone write-protected") } + let action: String = json_get_string(body, "action") if str_eq(action, "tombstone") { - // real live path: DELETE /api/nodes/ writes a tombstone MARKER node + - // a "tombstones" edge and KEEPS the original (immutable; recoverable). - return mcp_json_result(http_delete(engram_url() + "/api/nodes/" + id, - "{\"_auth\":\"" + engram_key() + "\"}")) + let tomb: String = engram_node_full("tombstone:" + id, "Tombstone", "tombstone:" + id, 0.1, 0.1, 1.0, "Episodic", "[\"tombstone\"]") + engram_connect(tomb, id, 1.0, "tombstones") // original node retained (immutable) + let s: Int = persist_node(tomb) + return "{\"ok\":true,\"tombstoned\":\"" + id + "\",\"tombstone_id\":\"" + tomb + "\"}" } - // promote and evolve/supersede are both the same immutable move: write a NEW - // node (carrying the new tier for promote) + a supersedes edge to the old. - // There is no distinct live engram /promote route — promotion IS supersession - // at a higher tier, which keeps memory immutable by construction. - // evolve/supersede/promote: new node + supersedes edge (old node preserved) - let content: String = json_get_string(args, "content") - if str_eq(content, "") { return mcp_text_result("supersede(evolve): content is required") } - let created: String = op_write(args) // reuses write (type carried through) - let new_id: String = extract_result_id(created) - if str_eq(new_id, "") { return created } - let edge_body: String = "{\"_auth\":\"" + engram_key() + "\",\"from_id\":\"" + new_id - + "\",\"to_id\":\"" + id + "\",\"relation\":\"supersedes\",\"weight\":1.0}" - let e: String = http_post_json(engram_url() + "/api/edges", edge_body) - return mcp_json_result("{\"new_id\":\"" + new_id + "\",\"supersedes\":\"" + id + "\",\"edge\":" + e + "}") + let content: String = json_get_string(body, "content") + if str_eq(content, "") { return err_json("supersede(evolve): content required") } + let new_id: String = engram_node_full(content, "Memory", content, 0.5, 0.5, 1.0, "Working", "") + let sv: Int = persist_node(new_id) + engram_connect(new_id, id, 1.0, "supersedes") // old node retained (immutable) + let sv2: Int = persist_edges_since(engram_edge_count() - 1) + return "{\"new_id\":\"" + new_id + "\",\"supersedes\":\"" + id + "\"}" } -// ───────────────────────────────────────────────────────────────────────────── -// LAYER 2 — PRIMITIVE AGENTIC TOOLS (Neuron runs itself over its own geometry) -// The base verbs all agentic behavior composes from. Grounded in the LIVE -// cog-arch: think is the one operation; the faculties are its steering-space -// labels; attend aims attention; the correspondence-beat is the reflexive -// learning loop; ground/assert are the honesty floor. -// ───────────────────────────────────────────────────────────────────────────── +// ── LAYER 2 — primitive agentic tools (@manager: orchestration + DHARMA emit) ── +// think is the one operation; faculty is its steering label. Each @manager op +// emits on the dharma_* bus (the same transport the swarm peers use). When the +// staged boundary-injection lands, these explicit emits become automatic. -// think — THE ONE OPERATION. A directed traversal from an anchor, steered by a -// (learned) prior, whose output is a gradient. `faculty` selects the steering -// region: reason|abduce|induce|plan|analogize|recognize|discern|synthesize. -// deduce/causal/perspective are the same primitive under other labels. -fn op_think(args: String) -> String { - let seeds: String = pick_seeds(args) // csv node-ids OR a free-text concept - if str_eq(seeds, "") { return mcp_text_result("think: seeds (anchor) required") } - let faculty_raw: String = json_get_string(args, "faculty") - let faculty: String = if str_eq(faculty_raw, "") { "reason" } else { faculty_raw } - return mcp_json_result(http_get(engram_url() + "/api/think?seeds=" + url_encode(seeds) + "&faculty=" + faculty)) +@route("/api/think", "GET") +@manager +fn op_think(method: String, path: String, body: String) -> String { + let seeds: String = query_param(path, "seeds") // CSV node-ids (the anchor) + if str_eq(seeds, "") { return err_json("think: seeds (node-id anchor) required") } + let f_raw: String = query_param(path, "faculty") + let f: String = if str_eq(f_raw, "") { "reason" } else { f_raw } + dharma_emit("neuron.think", "{\"seeds\":\"" + seeds + "\",\"faculty\":\"" + f + "\"}") + return engram_think_json(seeds, f) } -// attend — aim attention at a region (form the working-memory vantage). The -// `intend` primitive is attend at a goal-region; expose it as attend(intent=..). -fn op_attend(args: String) -> String { - let node: String = json_get_string(args, "node") - if str_eq(node, "") { return mcp_text_result("attend: node (region) required") } - let observer_raw: String = json_get_string(args, "observer") - let observer: String = if str_eq(observer_raw, "") { "kn-efeb4a5b-5aff-4759-8a97-7233099be6ee" } else { observer_raw } - let salience: String = json_get_string(args, "salience") - let body: String = "{\"_auth\":\"" + engram_key() + "\",\"node\":\"" + node - + "\",\"observer\":\"" + observer + "\",\"salience\":\"" + salience + "\"}" - return mcp_json_result(http_post_json(engram_url() + "/api/attend", body)) +@route("/api/attend", "POST") +@manager +fn op_attend(method: String, path: String, body: String) -> String { + let node: String = json_get_string(body, "node") + if str_eq(node, "") { return err_json("attend: node (region) required") } + let observer: String = json_get_string(body, "observer") + let salience: String = json_get_string(body, "salience") + dharma_emit("neuron.attend", "{\"node\":\"" + node + "\"}") + return engram_attend_json(node, observer, salience) } -// ground — form a grounded-by relation between a claim and its evidence -// (grounded-for-whom). Grounding is a relation, not a gate. This is the input -// half of the honesty floor (comprehend's grounding side). -fn op_ground(args: String) -> String { - let claim: String = json_get_string(args, "claim") - let evidence: String = json_get_string(args, "evidence") - if str_eq(claim, "") || str_eq(evidence, "") { return mcp_text_result("ground: claim and evidence required") } - let for_whom: String = json_get_string(args, "for_whom") - let body: String = "{\"_auth\":\"" + engram_key() + "\",\"claim\":\"" + json_escape(claim) - + "\",\"evidence\":\"" + json_escape(evidence) + "\",\"for_whom\":\"" + for_whom + "\"}" - return mcp_json_result(http_post_json(engram_url() + "/api/ground", body)) +@route("/api/ground", "POST") +@manager +fn op_ground(method: String, path: String, body: String) -> String { + let claim: String = json_get_string(body, "claim") // node-id region + let evidence: String = json_get_string(body, "evidence") // node-id region + if str_eq(claim, "") { return err_json("ground: claim required") } + if str_eq(evidence, "") { return err_json("ground: evidence required") } + let for_whom: String = json_get_string(body, "for_whom") + dharma_emit("neuron.ground", "{\"claim\":\"" + claim + "\"}") + return engram_ground_json(claim, evidence, for_whom) } -// assert — the readout half of the honesty floor (realize, constrained): a claim -// may surface only if grounded above `floor` for `for_whom`. realize = assert -// pointed at the world; speaking is an act. -fn op_assert(args: String) -> String { - let claim: String = json_get_string(args, "claim") - if str_eq(claim, "") { return mcp_text_result("assert: claim required") } - let for_whom: String = json_get_string(args, "for_whom") - let floor: String = json_get_string(args, "floor") - return mcp_json_result(http_get(engram_url() + "/api/assert?claim=" + url_encode(claim) - + "&for_whom=" + for_whom + "&floor=" + floor)) +// learn — the reflexive correspondence-beat: calibrate the steering-prior (Stance). +@route("/api/learn", "POST") +@manager +fn op_learn(method: String, path: String, body: String) -> String { + let seeds: String = json_get_string(body, "seeds") + if str_eq(seeds, "") { return err_json("learn: seeds required") } + let f_raw: String = json_get_string(body, "faculty") + let f: String = if str_eq(f_raw, "") { "induce" } else { f_raw } + let keystone: String = json_get_string(body, "keystone") + dharma_emit("neuron.learn", "{\"seeds\":\"" + seeds + "\",\"faculty\":\"" + f + "\"}") + return engram_correspondence_beat_json(seeds, f, keystone) } - -// learn — the reflexive CORRESPONDENCE-BEAT: think scores its own gradient -// against outcome and refines the steering-prior (Stance) on the error. This is -// the learning engine. The skill-learning loop (decompose -> detect-gap -> -// reach-out-on-sparsity -> verify-by-execution -> integrate) COMPOSES over -// think + ground + learn + write/relate; it is not a separate primitive. -fn op_learn(args: String) -> String { - let seeds: String = pick_seeds(args) - if str_eq(seeds, "") { return mcp_text_result("learn: seeds required") } - let faculty_raw: String = json_get_string(args, "faculty") - let faculty: String = if str_eq(faculty_raw, "") { "induce" } else { faculty_raw } - let keystone: String = json_get_string(args, "keystone") // keystone=true is write-protected - let body: String = "{\"_auth\":\"" + engram_key() + "\",\"seeds\":\"" + url_encode(seeds) - + "\",\"faculty\":\"" + faculty + "\",\"keystone\":\"" + keystone + "\"}" - return mcp_json_result(http_post_json(engram_url() + "/api/correspondence-beat", body)) -} - -// ───────────────────────────────────────────────────────────────────────────── -// OLD-TOOL ALIASES — additive shims so existing callers keep working. -// Each old noun-tool delegates to a new op with a `type`/param mapping. -// (Parity-gated by tools/api-reshape/parity.sh.) -// ───────────────────────────────────────────────────────────────────────────── -fn alias_remember(args: String) -> String { return op_write(with_type(args, "memory")) } -fn alias_capture_knowledge(args: String) -> String { return op_write(with_type(args, "knowledge")) } -fn alias_draft_artifact(args: String) -> String { return op_write(with_type(args, "artifact")) } -fn alias_plan_work(args: String) -> String { return op_write(with_type(args, "backlog")) } -fn alias_define_process(args: String) -> String { return op_write(with_type(args, "process")) } -fn alias_log_ise(args: String) -> String { return op_write(with_type(args, "state")) } -fn alias_search_knowledge(args: String) -> String { return op_read(as_vantage(args, "query", "knowledge")) } -fn alias_search_entities(args: String) -> String { return op_read(as_vantage(args, "query", "")) } -fn alias_recall(args: String) -> String { return op_read(as_vantage(args, "query", "memory")) } -fn alias_browse_knowledge(args: String) -> String { return op_read(as_vantage(args, "category", "knowledge")) } -fn alias_review_backlog(args: String) -> String { return op_read(as_vantage(args, "query", "backlog")) } -fn alias_find_artifacts(args: String) -> String { return op_read(as_vantage(args, "query", "artifact")) } -fn alias_inspect_graph(args: String) -> String { return op_read(as_edges_vantage(args)) } -fn alias_traverse_graph(args: String) -> String { return op_read(as_edges_vantage(args)) } -fn alias_inspect_memories(args: String) -> String { return op_read(as_vantage(args, "", "memory")) } -fn alias_link_entities(args: String) -> String { return op_relate(remap_link(args)) } -fn alias_link_causal(args: String) -> String { return op_relate(remap_link_causal(args)) } -fn alias_evolve_memory(args: String) -> String { return op_supersede(with_action(args, "supersede")) } -fn alias_evolve_knowledge(args: String) -> String { return op_supersede(with_action(args, "supersede")) } -fn alias_promote_knowledge(args: String) -> String { return op_supersede(with_action(args, "promote")) } -fn alias_revise_artifact(args: String) -> String { return op_supersede(with_action(args, "supersede")) } -fn alias_forget(args: String) -> String { return op_supersede(with_action(remap_forget(args), "tombstone")) } -fn alias_update_self_model(args: String) -> String { return mcp_text_result("update_self_model: routes through intentional-cultivation (write-protected), not supersede") }