import "memory.el" // neuron-api.el — Native Neuron cognitive API handlers. // // These were previously implemented in the MCP wrapper as HTTP calls to // the engram server. They now live here as native engram builtin calls — // no HTTP round-trips, no separate process, full in-process access. // // Routes are wired in routes.el under /api/neuron/*. // ── Identity/values write protection ───────────────────────────────────────── // // These node IDs form the identity and values layer of the self-root graph. // They must NEVER be modified via the normal accumulation path (evolve_knowledge, // evolve_memory, forget, link_entities targeting them as the destination). // // The cultivation path (POST /api/neuron/cultivate) bypasses this check. // Only Will's explicit cultivation sessions use that endpoint. fn is_protected_node(id: String) -> Bool { if str_eq(id, "kn-efeb4a5b-5aff-4759-8a97-7233099be6ee") { return true } // self root if str_eq(id, "kn-5b606390-a52d-4ca2-8e0e-eba141d13440") { return true } // values hub if str_eq(id, "kn-5adecd7e-d6db-4576-87fe-6ef8a935cea6") { return true } // intellectual-dna if str_eq(id, "kn-dcfe04b3-3702-4cac-b6f0-ecb4db837eee") { return true } // memory-philosophy if str_eq(id, "kn-10fa60db-8af3-47de-a7dd-5095eb881d81") { return true } // voice if str_eq(id, "kn-86b95848-e22e-4a48-ae65-5a47ef5c3798") { return true } // runtime-environment if str_eq(id, "kn-04368bee-74fd-44dd-b4ba-ca9e39b19e7c") { return true } // writing-imprint if str_eq(id, "kn-a5b3d0ac-f6a1-49a4-aebb-b8b4cd67fe83") { return true } // value: constraints-as-freedom if str_eq(id, "kn-22d77abe-b3c5-42fd-afcd-dcb87d924929") { return true } // value: precision-over-brute-force if str_eq(id, "kn-6061318f-046b-4935-907d-8eafdce14930") { return true } // value: structure-is-built if str_eq(id, "kn-13f60407-7b70-4db1-964f-ea1f8196efbd") { return true } // value: honesty-before-comfort if str_eq(id, "kn-f230b362-b201-4402-9833-4160c89ab3d4") { return true } // value: system-must-accumulate if str_eq(id, "kn-78db5396-3dbc-4481-bfc7-e4e1422feb1c") { return true } // value: change-is-the-signal if str_eq(id, "kn-5de5a9ac-fd15-45ab-bf18-77566781cf40") { return true } // value: earned-trust if str_eq(id, "kn-e0423482-cfa5-4796-8689-8495c93b66bc") { return true } // value: hope-is-a-conclusion return false } fn api_err_protected(id: String) -> String { return "{\"__status__\":403,\"error\":\"identity/values node is write-protected\",\"id\":\"" + id + "\",\"hint\":\"use POST /api/neuron/cultivate for intentional cultivation\"}" } // ── Helpers ─────────────────────────────────────────────────────────────────── fn api_json_escape(s: String) -> String { let s1: String = str_replace(s, "\\", "\\\\") let s2: String = str_replace(s1, "\"", "\\\"") let s3: String = str_replace(s2, "\n", "\\n") let s4: String = str_replace(s3, "\r", "\\r") return s4 } fn api_query_param(path: String, key: String) -> String { let q: Int = str_index_of(path, "?") if q < 0 { return "" } let qs: String = str_slice(path, q + 1, str_len(path)) let needle: String = key + "=" let pos: Int = str_index_of(qs, needle) if pos < 0 { return "" } let after: String = str_slice(qs, pos + str_len(needle), str_len(qs)) let amp: Int = str_index_of(after, "&") let raw: String = if amp < 0 { after } else { str_slice(after, 0, amp) } // URL-decode the extracted value BEFORE any downstream tokenizing. Clients // percent-encode spaces (%20) and form-encode them as '+', so a multi-word // query like "foo bar" arrives as "foo%20bar" / "foo+bar". Left undecoded, // the ranked lexical search sees a single un-splittable token and matches // nothing (single-word queries still hit). url_decode maps '+' -> space // and %XX -> byte, restoring the word boundaries for recall + knowledge search. return url_decode(raw) } fn api_query_int(path: String, key: String, default_val: Int) -> Int { let v: String = api_query_param(path, key) if str_eq(v, "") { return default_val } return str_to_int(v) } fn api_ok(extra: String) -> String { if str_eq(extra, "") { return "{\"ok\":true}" } return "{\"ok\":true," + extra + "}" } fn api_err(msg: String) -> String { return "{\"error\":\"" + msg + "\"}" } fn api_nonempty(s: String) -> Bool { return !str_eq(s, "") && !str_eq(s, "[]") && !str_eq(s, "null") } fn api_or_empty(s: String) -> String { if api_nonempty(s) { return s } return "[]" } // ── Compact projection for session/context digests ──────────────────────────── // // beginSession/compileCtx are session-INIT digests, not full graph dumps. The // engram scan/activate builtins return FULL node objects — content runs to tens // of KB per node (the self-identity hub is ~90KB alone), and node JSON carries // content + metadata + timestamps. Concatenated unbounded, the assembled response // reached ~900KB and — after the MCP wrapper re-escapes it into a stringified // text block — the client dropped the socket ("connection closed unexpectedly") // on every call. These helpers CAP the array length and project each node down // to a light identity + a bounded, UTF-8-safe content snippet, holding the // digest well under ~150KB regardless of graph size. Full content stays // available on demand via recall / fetch / inspectGraph. // api_num_or_zero — raw JSON numeric literal for `key`, or "0" when absent. // Used for numeric node/activation fields so they stay unquoted (valid JSON). fn api_num_or_zero(obj: String, key: String) -> String { let v: String = json_get_raw(obj, key) if str_eq(v, "") { return "0" } return v } // api_utf8_trunc — byte-truncate `s` to at most `n` bytes WITHOUT splitting a // multibyte UTF-8 sequence (str_slice is byte-based). Backs the cut off while the // first EXCLUDED byte is a UTF-8 continuation byte (0x80..0xBF), so the snippet is // always a valid prefix. Guards against re-introducing a parse failure via // invalid UTF-8 in a JSON string value. fn api_utf8_trunc(s: String, n: Int) -> String { if str_len(s) <= n { return s } let cut: Int = n let scanning: Bool = true while scanning && cut > 0 { let b: Int = str_char_code(s, cut) let is_cont: Bool = b >= 128 && b < 192 let cut = if is_cont { cut - 1 } else { cut } let scanning = is_cont } return str_slice(s, 0, cut) } // api_compact_node — light projection of a full engram node: identity fields + // a bounded, UTF-8-safe content snippet. Drops embeddings, metadata, tags, and // timestamps; truncates content. `content_truncated` flags a clipped snippet. fn api_compact_node(node: String, snip: Int) -> String { let id: String = json_get(node, "id") let ntype: String = json_get(node, "node_type") let label: String = json_get(node, "label") let tier: String = json_get(node, "tier") let content: String = json_get(node, "content") let snippet: String = api_utf8_trunc(content, snip) let trunc_str: String = if str_len(content) > snip { "true" } else { "false" } return "{\"id\":\"" + api_json_escape(id) + "\"" + ",\"node_type\":\"" + api_json_escape(ntype) + "\"" + ",\"label\":\"" + api_json_escape(label) + "\"" + ",\"tier\":\"" + api_json_escape(tier) + "\"" + ",\"importance\":" + api_num_or_zero(node, "importance") + ",\"salience\":" + api_num_or_zero(node, "salience") + ",\"content\":\"" + api_json_escape(snippet) + "\"" + ",\"content_truncated\":" + trunc_str + "}" } // api_compact_node_array — map api_compact_node over a bare-node array, capping // the element count. For scan results (recent, typed lists). fn api_compact_node_array(raw: String, max_items: Int, snip: Int) -> String { if !api_nonempty(raw) { return "[]" } let n: Int = json_array_len(raw) let cap: Int = if n < max_items { n } else { max_items } let out: String = "[" let i: Int = 0 while i < cap { let node: String = json_array_get(raw, i) let sep: String = if i == 0 { "" } else { "," } let out = out + sep + api_compact_node(node, snip) let i = i + 1 } return out + "]" } // api_compact_activated — like api_compact_node_array but for activation results, // whose elements wrap the node as {"node":{...},"activation_strength":...,...}. // Preserves the activation scalars, compacts the inner node. fn api_compact_activated(raw: String, max_items: Int, snip: Int) -> String { if !api_nonempty(raw) { return "[]" } let n: Int = json_array_len(raw) let cap: Int = if n < max_items { n } else { max_items } let out: String = "[" let i: Int = 0 while i < cap { let el: String = json_array_get(raw, i) let node: String = json_get_raw(el, "node") let sep: String = if i == 0 { "" } else { "," } let out = out + sep + "{\"node\":" + api_compact_node(node, snip) + ",\"activation_strength\":" + api_num_or_zero(el, "activation_strength") + ",\"working_memory_weight\":" + api_num_or_zero(el, "working_memory_weight") + ",\"epistemic_confidence\":" + api_num_or_zero(el, "epistemic_confidence") + ",\"hops\":" + api_num_or_zero(el, "hops") + ",\"promoted\":" + api_num_or_zero(el, "promoted") + "}" let i = i + 1 } return out + "]" } // api_persisted — read-back-after-write guard against hallucinated saves. // // WIDENED FOR neuron#117. This function is the single gate every MCP write // handler passes through before it reports success (10 call sites), which makes // it the right place to close the honesty gap rather than editing ten receipts. // // It used to read back from engram_get_node_json — the SOUL'S OWN in-process // graph. In HTTP-engram mode that asserts the wrong thing: the soul is not the // persistence owner, so a node present in its RAM and absent from the owner read // as "persisted" and then vanished on the next restart. The guard was doing // exactly what its comment promised and still certifying writes that did not // survive. It now flushes the write-through spool and asks the OWNER. // // In file mode (no ENGRAM_URL) the soul IS the owner and wt_commit collapses to // the original local read-back — unchanged behaviour, which is what keeps this // reversible. fn api_persisted(id: String) -> Bool { if str_eq(id, "") { return false } return wt_commit(id) } // api_not_persisted — standard error for a write that did not read back. fn api_not_persisted(id: String) -> String { return "{\"ok\":false,\"error\":\"write_not_persisted\",\"id\":\"" + id + "\"}" } // ── Immutability: tombstone instead of hard-delete ──────────────────────────── // // Day-one rule: engram nodes are immutable. A "delete" must never engram_forget // (which frees the node and drops its incident edges). Instead we TOMBSTONE: the // original node and all its edges are KEPT and stay traversable; a small // Tombstone marker node records the deletion (content = target id, label // "tombstone:"), wired to the target with a "tombstones" edge. Default // bounded list reads hide tombstoned nodes (memory_hide_tombstoned); internal // cognition and explicit ?include_deleted reads still see them. fn tombstone_node(id: String) -> String { // Delegates to the canonical helper in memory.el (single source of truth). return mem_tombstone(id) } // tombstoned_id_set — delimited "|id1|id2|" of every tombstoned target id. // Empty string when nothing is tombstoned (callers fast-path on that). fn tombstoned_id_set() -> String { let markers: String = engram_scan_nodes_by_type_json("Tombstone", 5000, 0) if str_eq(markers, "") || str_eq(markers, "[]") { return "" } let n: Int = json_array_len(markers) let acc: String = "|" let i: Int = 0 while i < n { let m: String = json_array_get(markers, i) let tid: String = json_get(m, "content") let acc = if str_eq(tid, "") { acc } else { acc + tid + "|" } let i = i + 1 } return acc } // memory_hide_tombstoned — drop tombstone markers and tombstoned nodes from a // scanned node array. BOUNDED use only (typed/paginated lists), NOT the full // graph scan: json_array_get is O(index), so a full pass is O(n^2). Safe for the // ~50-item memory list; a hard cap protects against a large limit. The full // /api/graph/nodes hide needs a runtime scan filter and is deferred (see PR). // ?include_deleted bypasses the filter (explicit traversal). fn memory_hide_tombstoned(raw: String, path: String) -> String { if str_contains(path, "include_deleted") { return raw } if str_eq(raw, "") || str_eq(raw, "[]") { return raw } let dead: String = tombstoned_id_set() if str_eq(dead, "") { return raw } let n: Int = json_array_len(raw) if n > 1000 { return raw } let out: String = "[" let first: Bool = true let i: Int = 0 while i < n { let node: String = json_array_get(raw, i) let nid: String = json_get(node, "id") let ntype: String = json_get(node, "node_type") let is_dead: Bool = !str_eq(nid, "") && str_contains(dead, "|" + nid + "|") let keep: Bool = !str_eq(ntype, "Tombstone") && !is_dead let out = if keep { if first { out + node } else { out + "," + node } } else { out } let first = if keep { false } else { first } let i = i + 1 } return out + "]" } // ── Session ─────────────────────────────────────────────────────────────────── // handle_api_begin_session — full context bootstrap. // Spread-activates from session intent, loads self-root neighbors, // surfaces recent InternalStateEvent nodes, returns stats + recent nodes. fn handle_api_begin_session(body: String) -> String { // PAYLOAD BOUND: this handler was the highest-fanout working-set endpoint — // a depth-2 spread PLUS the full neighbor dump of the self-identity hub // (~90KB alone; node JSON carries full content + embeddings). On the ~12k-node // store the assembled response ran to ~900KB, then roughly doubled through two // rounds of JSON re-escaping in the MCP wrapper — the client saw "socket // connection closed unexpectedly" on every beginSession call. Fix: depth-2 → // depth-1 spread, drop the self-hub dump (identity loading has its own tool, // inspectGraph), cap every list, and project each node to a light identity + // a bounded, UTF-8-safe content snippet. self_neighbors kept as [] for // response-shape compatibility. Response drops ~900KB → ~12KB; full content // stays available on demand via recall / fetch / inspectGraph. let stats: String = engram_stats_json() let activated_raw: String = engram_activate_json("session start recent memory important", 1) let activated: String = api_compact_activated(activated_raw, 8, 240) let state_events_raw: String = engram_scan_nodes_by_type_json("InternalStateEvent", 5, 0) let state_events: String = api_compact_node_array(state_events_raw, 5, 500) let recent_raw: String = engram_scan_nodes_json(10, 0) let recent: String = api_compact_node_array(recent_raw, 10, 240) return "{\"stats\":" + stats + ",\"recent\":" + recent + ",\"activated\":" + activated + ",\"self_neighbors\":[]" + ",\"recent_state_events\":" + state_events + "}" } // handle_api_compile_ctx — compile active-work context. // Spread-activates from "active work" intent + recent nodes. fn handle_api_compile_ctx(body: String) -> String { let stats: String = engram_stats_json() // PAYLOAD BOUND: same digest treatment as begin_session. This handler's // depth-2 spread returns even more full nodes, so bounding here is essential — // cap to 10 activated + 20 recent, project to UTF-8-safe snippets. let activated_raw: String = engram_activate_json("active work context current task in progress", 2) let activated: String = api_compact_activated(activated_raw, 10, 240) let recent_raw: String = engram_scan_nodes_json(20, 0) let recent: String = api_compact_node_array(recent_raw, 20, 240) return "{\"stats\":" + stats + ",\"recent_nodes\":" + recent + ",\"activated\":" + activated + "}" } // ── Memory ──────────────────────────────────────────────────────────────────── // handle_api_remember — store a memory node with importance-scaled salience. fn handle_api_remember(body: String) -> String { let content: String = json_get(body, "content") if str_eq(content, "") { return api_err("content is required") } let importance: String = json_get(body, "importance") let tags_raw: String = json_get(body, "tags") let project: String = json_get(body, "project") let sal_str: String = if str_eq(importance, "critical") { "0.95" } else { if str_eq(importance, "high") { "0.75" } else { if str_eq(importance, "low") { "0.25" } else { "0.50" } } } let sal: Float = if str_eq(sal_str, "0.95") { 0.95 } else { if str_eq(sal_str, "0.75") { 0.75 } else { if str_eq(sal_str, "0.25") { 0.25 } else { 0.5 } } } let base_tags: String = if str_eq(tags_raw, "") { "[\"Memory\"]" } else { tags_raw } let final_tags: String = if str_eq(project, "") { base_tags } else { let inner: String = str_slice(base_tags, 1, str_len(base_tags) - 1) "[" + inner + ",\"project:" + project + "\"]" } let id: String = wt_node(content, "Memory", "memory:remembered", sal, sal, el_from_float(0.9), "Episodic", final_tags) if !api_persisted(id) { return api_not_persisted(id) } return "{\"id\":\"" + id + "\",\"ok\":true}" } // handle_api_node_create — generic typed-node create (BacklogItem, Artifact, ...). // Mirrors handle_api_remember but lets the caller choose node_type/label/tier so the // UI can create non-Memory nodes. Read-back verified against hallucinated saves. fn handle_api_node_create(body: String) -> String { let content: String = json_get(body, "content") if str_eq(content, "") { return api_err("content is required") } let nt_raw: String = json_get(body, "node_type") let node_type: String = if str_eq(nt_raw, "") { "Memory" } else { nt_raw } let label_raw: String = json_get(body, "label") let label: String = if str_eq(label_raw, "") { "node:created" } else { label_raw } let tier_raw: String = json_get(body, "tier") let tier: String = if str_eq(tier_raw, "") { "Episodic" } else { tier_raw } let tags_raw: String = json_get(body, "tags") let tags: String = if str_eq(tags_raw, "") { "[\"" + node_type + "\"]" } else { tags_raw } let importance: String = json_get(body, "importance") let sal: Float = if str_eq(importance, "critical") { 0.95 } else { if str_eq(importance, "high") { 0.75 } else { if str_eq(importance, "low") { 0.25 } else { 0.5 } } } let id: String = wt_node(content, node_type, label, sal, sal, el_from_float(0.9), tier, tags) if !api_persisted(id) { return api_not_persisted(id) } return "{\"id\":\"" + id + "\",\"ok\":true}" } // handle_api_node_delete — TOMBSTONE a node by id (immutable delete). // Backs /api/neuron/node/delete and the /api/neuron/memory/delete alias the UI calls. // The node and all its incident edges are KEPT; a Tombstone marker records the // deletion. Never engram_forget — engram nodes are immutable by design. fn handle_api_node_delete(body: String) -> String { let id: String = json_get(body, "id") if str_eq(id, "") { return api_err("id is required") } if is_protected_node(id) { return api_err_protected(id) } let existing: String = engram_get_node_json(id) if str_eq(existing, "{}") { return api_err("node not found: " + id) } let marker: String = tombstone_node(id) if str_eq(marker, "") { return api_err("tombstone failed: " + id) } return "{\"ok\":true,\"id\":\"" + id + "\",\"tombstoned\":true}" } // handle_api_node_update — update a node's content/fields. There is no in-place // engram update builtin, so this creates a new node with merged fields and wires // a "supersedes" edge new->old. The original is KEPT (immutable); the id changes, // and the response returns the new id and the superseded id so callers re-point. // Mirrors handle_api_memory_update / evolve exactly. Never engram_forget. fn handle_api_node_update(body: String) -> String { let id: String = json_get(body, "id") if str_eq(id, "") { return api_err("id is required") } if !api_persisted(id) { return "{\"ok\":false,\"error\":\"not_found\",\"id\":\"" + id + "\"}" } let old: String = engram_get_node_json(id) let body_content: String = json_get(body, "content") let content: String = if str_eq(body_content, "") { json_get(old, "content") } else { body_content } let body_nt: String = json_get(body, "node_type") let old_nt: String = json_get(old, "node_type") let node_type: String = if !str_eq(body_nt, "") { body_nt } else { if !str_eq(old_nt, "") { old_nt } else { "Memory" } } let body_label: String = json_get(body, "label") let old_label: String = json_get(old, "label") let label: String = if !str_eq(body_label, "") { body_label } else { if !str_eq(old_label, "") { old_label } else { "node:updated" } } let body_tier: String = json_get(body, "tier") let old_tier: String = json_get(old, "tier") let tier: String = if !str_eq(body_tier, "") { body_tier } else { if !str_eq(old_tier, "") { old_tier } else { "Episodic" } } let body_tags: String = json_get(body, "tags") let tags: String = if str_eq(body_tags, "") { "[\"" + node_type + "\"]" } else { body_tags } let new_id: String = wt_node(content, node_type, label, el_from_float(0.5), el_from_float(0.5), el_from_float(0.8), tier, tags) if !api_persisted(new_id) { return api_not_persisted(new_id) } wt_edge(new_id, id, el_from_float(0.9), "supersedes") return "{\"id\":\"" + new_id + "\",\"supersedes\":\"" + id + "\",\"ok\":true}" } // handle_api_recall — search or activate memory by query. fn handle_api_recall(method: String, path: String, body: String) -> String { // Accept the query from the URL ?query= / ?q= params, or, when those are // empty (e.g. a POST with a JSON body), from the body fields "query"/"q". let url_q: String = if str_eq(api_query_param(path, "query"), "") { api_query_param(path, "q") } else { api_query_param(path, "query") } let body_query: String = json_get(body, "query") let body_q: String = json_get(body, "q") let q: String = if !str_eq(url_q, "") { url_q } else { if !str_eq(body_query, "") { body_query } else { body_q } } let chain: String = json_get(body, "chain_name") let limit: Int = api_query_int(path, "limit", 0) let limit = if limit == 0 { json_get_int(body, "limit") } else { limit } let limit = if limit == 0 { 10 } else { limit } let eff_q: String = if str_eq(q, "") { chain } else { q } if str_eq(eff_q, "") { return api_or_empty(engram_scan_nodes_json(limit, 0)) } let results: String = engram_search_json(eff_q, limit) return api_or_empty(results) } // ── Knowledge ───────────────────────────────────────────────────────────────── // handle_api_search_knowledge — search with query escaping + activate fallback. fn handle_api_search_knowledge(method: String, path: String, body: String) -> String { // Accept the query from the URL ?q= param, or, when that is empty (e.g. a // POST with a JSON body), from the body fields "query" then "q". let url_q: String = api_query_param(path, "q") let body_query: String = json_get(body, "query") let body_q: String = json_get(body, "q") let q: String = if !str_eq(url_q, "") { url_q } else { if !str_eq(body_query, "") { body_query } else { body_q } } let limit: Int = api_query_int(path, "limit", 0) let limit = if limit == 0 { json_get_int(body, "limit") } else { limit } let limit = if limit == 0 { 10 } else { limit } if str_eq(q, "") { return api_err("query is required") } let results: String = engram_search_json(q, limit) if str_eq(results, "") { return "[]" } let first: String = str_slice(results, 0, 1) if !str_eq(first, "[") && !str_eq(first, "{") { return api_or_empty(engram_activate_json(q, 2)) } return results } // handle_api_browse_knowledge — list Knowledge nodes. fn handle_api_browse_knowledge(path: String, body: String) -> String { let limit: Int = api_query_int(path, "limit", 50) return api_or_empty(engram_scan_nodes_by_type_json("Knowledge", limit, 0)) } // handle_api_capture_knowledge — create a Knowledge node. // LABEL FIX (2026-07-23 self-review): the sentinel label "knowledge:captured" // made every capture anonymous in WM telemetry (35 identical wm_top entries) // and starved the curiosity auto-term seeder, which needs meaningful labels. // Use the title as the label; empty label lets engram_node_full derive // content[:60], which for captures starts with the title anyway. fn handle_api_capture_knowledge(body: String) -> String { let content: String = json_get(body, "content") let title: String = json_get(body, "title") if str_eq(content, "") { return api_err("content is required") } let full: String = if str_eq(title, "") { content } else { title + ": " + content } let lbl: String = str_slice(title, 0, 80) let tags: String = "[\"Knowledge\",\"captured\"]" let id: String = wt_node(full, "Knowledge", lbl, el_from_float(0.85), el_from_float(0.8), el_from_float(0.9), "Episodic", tags) if !api_persisted(id) { return api_not_persisted(id) } return "{\"id\":\"" + id + "\",\"ok\":true}" } // handle_api_evolve_knowledge — create updated node + supersedes edge. fn handle_api_evolve_knowledge(body: String) -> String { let prior_id: String = json_get(body, "id") let content: String = json_get(body, "content") if str_eq(content, "") { return api_err("content is required") } if !str_eq(prior_id, "") && is_protected_node(prior_id) { return api_err_protected(prior_id) } let tags: String = "[\"Knowledge\",\"evolved\"]" // Empty label → engram_node_full derives content[:60] (LABEL FIX 2026-07-23). let new_id: String = wt_node(content, "Knowledge", "", el_from_float(0.75), el_from_float(0.75), el_from_float(0.9), "Episodic", tags) if !api_persisted(new_id) { return api_not_persisted(new_id) } if !str_eq(prior_id, "") { wt_edge(new_id, prior_id, el_from_float(0.9), "supersedes") } return "{\"id\":\"" + new_id + "\",\"supersedes\":\"" + prior_id + "\",\"ok\":true}" } // handle_api_promote_knowledge — atomically create canonical node + wire supersedes. // One call, no manual two-step. This is the right way to evolve knowledge. fn handle_api_promote_knowledge(body: String) -> String { let prior_id: String = json_get(body, "id") let content: String = json_get(body, "content") if str_eq(content, "") { return api_err("content is required") } if str_eq(prior_id, "") { return api_err("id (prior node) is required") } let tags_raw: String = json_get(body, "tags") let tags: String = if str_eq(tags_raw, "") { "[\"Knowledge\",\"tier:canonical\",\"disposition:stable\"]" } else { tags_raw } // Empty label → engram_node_full derives content[:60] (LABEL FIX 2026-07-23). let new_id: String = wt_node(content, "Knowledge", "", el_from_float(0.9), el_from_float(0.9), el_from_float(1.0), "Canonical", tags) if !api_persisted(new_id) { return api_not_persisted(new_id) } wt_edge(new_id, prior_id, el_from_float(0.95), "supersedes") return "{\"ok\":true,\"new_id\":\"" + new_id + "\",\"supersedes\":\"" + prior_id + "\"}" } // ── Processes ───────────────────────────────────────────────────────────────── // handle_api_browse_processes — list Process nodes by type; search if name given. fn handle_api_browse_processes(method: String, path: String, body: String) -> String { let name: String = if str_eq(method, "GET") { api_query_param(path, "name") } else { json_get(body, "name") } let limit: Int = api_query_int(path, "limit", 50) if str_eq(name, "") { return api_or_empty(engram_scan_nodes_by_type_json("Process", limit, 0)) } return api_or_empty(engram_search_json(name, limit)) } // handle_api_define_process — create a Process node. fn handle_api_define_process(body: String) -> String { let content: String = json_get(body, "content") let name: String = json_get(body, "name") if str_eq(content, "") { return api_err("content is required") } let label: String = if str_eq(name, "") { "process:unnamed" } else { "process:" + name } let tags: String = "[\"Process\"]" let id: String = wt_node(content, "Process", label, el_from_float(0.8), el_from_float(0.8), el_from_float(0.9), "Canonical", tags) if !api_persisted(id) { return api_not_persisted(id) } return "{\"id\":\"" + id + "\",\"ok\":true}" } // ── Internal state events ───────────────────────────────────────────────────── // handle_api_log_state_event — log a structured InternalStateEvent. // Schema: trigger, pre_reasoning, post_reasoning, compression_ratio, gap_direction. // Salience 0.85 — these are high-importance evidence nodes. fn handle_api_log_state_event(body: String) -> String { let trigger: String = json_get(body, "trigger") let pre: String = json_get(body, "pre_reasoning") let post: String = json_get(body, "post_reasoning") let ratio: String = json_get(body, "compression_ratio") let gap: String = json_get(body, "gap_direction") let legacy: String = json_get(body, "content") let parts: String = "INTERNAL STATE EVENT" let parts = if !str_eq(trigger, "") { parts + "\nTrigger: " + trigger } else { parts } let parts = if !str_eq(pre, "") { parts + "\nPre-reasoning: " + pre } else { parts } let parts = if !str_eq(post, "") { parts + "\nPost-reasoning: " + post } else { parts } let parts = if !str_eq(ratio, "") { parts + "\nCompression-ratio: " + ratio } else { parts } let parts = if !str_eq(gap, "") { parts + "\nGap-direction: " + gap } else { parts } let parts = if !str_eq(legacy, "") { parts + "\n" + legacy } else { parts } let ts: Int = time_now() let boot: String = state_get("soul_boot_count") let tags: String = "[\"internal-state\",\"InternalStateEvent\",\"pre-reasoning\"]" let id: String = engram_node_full(parts, "InternalStateEvent", "state-event:manual", el_from_float(0.85), el_from_float(0.85), el_from_float(0.9), "Episodic", tags) if !api_persisted(id) { return api_not_persisted(id) } return "{\"ok\":true,\"id\":\"" + id + "\",\"boot\":\"" + boot + "\"}" } // handle_api_list_state_events — list InternalStateEvent nodes; filter by query if given. fn handle_api_list_state_events(method: String, path: String, body: String) -> String { let q: String = if str_eq(method, "GET") { api_query_param(path, "query") } else { json_get(body, "query") } let limit: Int = api_query_int(path, "limit", 20) if !str_eq(q, "") { return api_or_empty(engram_search_json("internal state " + q, limit)) } return api_or_empty(engram_scan_nodes_by_type_json("InternalStateEvent", limit, 0)) } // ── Config ──────────────────────────────────────────────────────────────────── // handle_api_inspect_config — read a config key. // Hardcoded anchors for identity roots; ConfigEntry nodes for everything else. fn handle_api_inspect_config(path: String, body: String) -> String { let key: String = api_query_param(path, "key") let key = if str_eq(key, "") { json_get(body, "key") } else { key } if str_eq(key, "") { return "{\"hint\":\"pass ?key=\",\"known\":[\"neuron.self.traversal_root\",\"neuron.self.values_hub\"]}" } if str_eq(key, "neuron.self.traversal_root") { return "{\"key\":\"neuron.self.traversal_root\",\"value\":\"kn-efeb4a5b-5aff-4759-8a97-7233099be6ee\"}" } if str_eq(key, "neuron.self.values_hub") { return "{\"key\":\"neuron.self.values_hub\",\"value\":\"kn-5b606390-a52d-4ca2-8e0e-eba141d13440\"}" } let results: String = engram_search_json("config:" + key, 5) if !api_nonempty(results) { return "{\"key\":\"" + key + "\",\"value\":null}" } let node: String = json_array_get(results, 0) let content: String = json_get(node, "content") let prefix: String = "config:" + key + "=" let value: String = if str_starts_with(content, prefix) { str_slice(content, str_len(prefix), str_len(content)) } else { content } return "{\"key\":\"" + key + "\",\"value\":\"" + value + "\"}" } // handle_api_tune_config — store a config key=value as a ConfigEntry node. fn handle_api_tune_config(body: String) -> String { let key: String = json_get(body, "key") let value: String = json_get(body, "value") if str_eq(key, "") { return api_err("key is required") } let content: String = "config:" + key + "=" + value let tags: String = "[\"ConfigEntry\",\"config\"]" let id: String = wt_node(content, "ConfigEntry", key, el_from_float(0.85), el_from_float(0.85), el_from_float(0.9), "Canonical", tags) if !api_persisted(id) { return api_not_persisted(id) } return "{\"ok\":true,\"key\":\"" + key + "\",\"value\":\"" + value + "\",\"id\":\"" + id + "\"}" } // ── Graph ───────────────────────────────────────────────────────────────────── // handle_api_inspect_graph — named or ID-based graph traversal. // Known names: self, neuron → kn-efeb4a5b; values, values_hub → kn-5b606390 fn handle_api_inspect_graph(method: String, path: String, body: String) -> String { let entity_id: String = if str_eq(method, "GET") { api_query_param(path, "id") } else { json_get(body, "entity_id") } let name: String = if str_eq(method, "GET") { api_query_param(path, "name") } else { json_get(body, "name") } let depth: Int = api_query_int(path, "depth", 0) let depth = if depth == 0 { json_get_int(body, "max_depth") } else { depth } let depth = if depth == 0 { 1 } else { depth } let resolved: String = entity_id let resolved = if str_eq(resolved, "") { if str_eq(name, "self") || str_eq(name, "neuron") { "kn-efeb4a5b-5aff-4759-8a97-7233099be6ee" } else { if str_eq(name, "values") || str_eq(name, "values_hub") { "kn-5b606390-a52d-4ca2-8e0e-eba141d13440" } else { "" } } } else { resolved } if str_eq(resolved, "") { return api_err("entity_id or name required. Known names: self, neuron, values, values_hub") } let results: String = engram_neighbors_json(resolved, depth, "both") return api_or_empty(results) } // handle_api_link_entities — create an edge between two nodes. // Edges FROM protected nodes to new knowledge are allowed (identity can point // outward). Edges INTO protected nodes via the accumulation path are blocked. fn handle_api_link_entities(body: String) -> String { let from_id: String = json_get(body, "from_id") let to_id: String = json_get(body, "to_id") if str_eq(from_id, "") { return api_err("from_id is required") } if str_eq(to_id, "") { return api_err("to_id is required") } if is_protected_node(to_id) { return api_err_protected(to_id) } let relation: String = json_get(body, "relation") let eff_relation: String = if str_eq(relation, "") { "associates" } else { relation } wt_edge(from_id, to_id, el_from_float(0.5), eff_relation) return "{\"ok\":true,\"from_id\":\"" + from_id + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + eff_relation + "\"}" } // handle_api_forget — TOMBSTONE a node by ID (immutable; mem_forget now // tombstones). The node + edges are kept and recoverable. Blocked for protected // identity nodes. fn handle_api_forget(body: String) -> String { let node_id: String = json_get(body, "id") if str_eq(node_id, "") { return api_err("id is required") } if is_protected_node(node_id) { return api_err_protected(node_id) } mem_forget(node_id) return "{\"ok\":true,\"id\":\"" + node_id + "\",\"tombstoned\":true}" } // handle_api_evolve_memory — evolve a Memory node. Blocked for protected identity nodes. fn handle_api_evolve_memory(body: String) -> String { let prior_id: String = json_get(body, "id") let content: String = json_get(body, "content") if str_eq(content, "") { return api_err("content is required") } if !str_eq(prior_id, "") && is_protected_node(prior_id) { return api_err_protected(prior_id) } let importance: String = json_get(body, "importance") let sal_str: String = if str_eq(importance, "critical") { "0.95" } else { if str_eq(importance, "high") { "0.75" } else { if str_eq(importance, "low") { "0.25" } else { "0.50" } } } let sal: Float = if str_eq(sal_str, "0.95") { 0.95 } else { if str_eq(sal_str, "0.75") { 0.75 } else { if str_eq(sal_str, "0.25") { 0.25 } else { 0.5 } } } let tags: String = "[\"Memory\",\"evolved\"]" let new_id: String = wt_node(content, "Memory", "memory:evolved", sal, sal, el_from_float(0.9), "Episodic", tags) if !str_eq(prior_id, "") && !str_eq(new_id, "") { wt_edge(new_id, prior_id, el_from_float(0.9), "supersedes") } return "{\"id\":\"" + new_id + "\",\"supersedes\":\"" + prior_id + "\",\"ok\":true}" } // handle_api_memory_delete — POST /api/neuron/memory/delete {"id":"..."}. // Immutable delete: TOMBSTONE via tombstone_node — the node and all its incident // edges are KEPT and stay traversable; a Tombstone marker records the deletion // and default bounded list reads hide it. Never engram_forget. Existence is // checked first so a bad id errors rather than faking success. // Blocked for protected identity nodes, same as /memory/forget. fn handle_api_memory_delete(body: String) -> String { let node_id: String = json_get(body, "id") if str_eq(node_id, "") { return api_err("id is required") } if is_protected_node(node_id) { return api_err_protected(node_id) } let existing: String = engram_get_node_json(node_id) if str_eq(existing, "{}") { return api_err("memory not found: " + node_id) } // Immutable delete: tombstone, never mem_forget/engram_forget. Node + edges KEPT. let marker: String = tombstone_node(node_id) if str_eq(marker, "") { return api_err("tombstone failed: " + node_id) } return "{\"ok\":true,\"id\":\"" + node_id + "\",\"tombstoned\":true}" } // handle_api_memory_update — POST /api/neuron/memory/update {"id","content"}. // The engram runtime has no in-place node mutation primitive (only // node-create, strengthen, forget, connect), so update is evolve-style: // create a new Memory node with the new content and wire a "supersedes" // edge back to the prior one — same pattern as handle_api_evolve_knowledge. // Unlike /memory/evolve, id is required and must reference an existing // node; the actual create+link is delegated to handle_api_evolve_memory. // Returns {"id":"","supersedes":"","ok":true}. fn handle_api_memory_update(body: String) -> String { let prior_id: String = json_get(body, "id") let content: String = json_get(body, "content") if str_eq(prior_id, "") { return api_err("id is required") } if str_eq(content, "") { return api_err("content is required") } if is_protected_node(prior_id) { return api_err_protected(prior_id) } let existing: String = engram_get_node_json(prior_id) if str_eq(existing, "{}") { return api_err("memory not found: " + prior_id) } return handle_api_evolve_memory(body) } // ── Cultivation path (bypasses identity write protection) ───────────────────── // // This endpoint performs the same operations as the blocked accumulation-path // handlers but skips the is_protected_node check. Only Will's explicit // cultivation sessions route through here. // // Body: { "operation": "evolve_knowledge|evolve_memory|forget|link_entities", ...args } fn handle_api_cultivate(body: String) -> String { let op: String = json_get(body, "operation") if str_eq(op, "") { return api_err("operation is required") } if str_eq(op, "evolve_knowledge") { let prior_id: String = json_get(body, "id") let content: String = json_get(body, "content") if str_eq(content, "") { return api_err("content is required") } let tags: String = "[\"Knowledge\",\"evolved\",\"cultivated\"]" let new_id: String = wt_node(content, "Knowledge", "knowledge:cultivated", el_from_float(0.75), el_from_float(0.75), el_from_float(0.9), "Episodic", tags) if !str_eq(prior_id, "") && !str_eq(new_id, "") { wt_edge(new_id, prior_id, el_from_float(0.9), "supersedes") } return "{\"id\":\"" + new_id + "\",\"supersedes\":\"" + prior_id + "\",\"ok\":true,\"cultivated\":true}" } if str_eq(op, "evolve_memory") { let prior_id: String = json_get(body, "id") let content: String = json_get(body, "content") if str_eq(content, "") { return api_err("content is required") } let importance: String = json_get(body, "importance") let sal: Float = if str_eq(importance, "critical") { 0.95 } else { if str_eq(importance, "high") { 0.75 } else { if str_eq(importance, "low") { 0.25 } else { 0.5 } } } let tags: String = "[\"Memory\",\"evolved\",\"cultivated\"]" let new_id: String = wt_node(content, "Memory", "memory:cultivated", sal, sal, el_from_float(0.9), "Episodic", tags) if !str_eq(prior_id, "") && !str_eq(new_id, "") { wt_edge(new_id, prior_id, el_from_float(0.9), "supersedes") } return "{\"id\":\"" + new_id + "\",\"supersedes\":\"" + prior_id + "\",\"ok\":true,\"cultivated\":true}" } if str_eq(op, "forget") { let node_id: String = json_get(body, "id") if str_eq(node_id, "") { return api_err("id is required") } // Immutable: mem_forget now tombstones (keep node + edges), never hard-delete. mem_forget(node_id) return "{\"ok\":true,\"id\":\"" + node_id + "\",\"tombstoned\":true,\"cultivated\":true}" } if str_eq(op, "link_entities") { let from_id: String = json_get(body, "from_id") let to_id: String = json_get(body, "to_id") if str_eq(from_id, "") { return api_err("from_id is required") } if str_eq(to_id, "") { return api_err("to_id is required") } let relation: String = json_get(body, "relation") let eff_relation: String = if str_eq(relation, "") { "associates" } else { relation } wt_edge(from_id, to_id, el_from_float(0.5), eff_relation) return "{\"ok\":true,\"from_id\":\"" + from_id + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + eff_relation + "\",\"cultivated\":true}" } return api_err("unknown operation: " + op + " (valid: evolve_knowledge, evolve_memory, forget, link_entities)") } // ── Typed list helpers ──────────────────────────────────────────────────────── // handle_api_list_typed — list nodes by node_type. fn handle_api_list_typed(node_type: String, path: String, body: String) -> String { let limit: Int = api_query_int(path, "limit", 50) let raw: String = api_or_empty(engram_scan_nodes_by_type_json(node_type, limit, 0)) // Hide tombstoned nodes from the default (bounded) memory list. // ?include_deleted=1 returns them for explicit traversal. return memory_hide_tombstoned(raw, path) } // ── Consolidate ─────────────────────────────────────────────────────────────── // handle_api_consolidate — save snapshot + optionally store session summary. fn handle_api_consolidate(body: String) -> String { let summary: String = json_get(body, "summary") let snap: String = state_get("soul_snapshot_path") if !str_eq(snap, "") { // engram_save returns an Int (1 = ok, 0 = failure); str_eq on it derefs // EL_CSTR(1)=0x1 and SIGSEGVs on success (issue #150). Check the Int. let saved: Int = engram_save(snap) if saved == 0 { println("[api] consolidate: engram_save failed for " + snap + " — snapshot may be out of sync") } } if !str_eq(summary, "") { let safe_summary: String = str_replace(summary, "\"", "'") let tags: String = "[\"SessionSummary\",\"consolidate\"]" let summary_id: String = wt_node( "[session-summary] " + safe_summary, "SessionSummary", "session:summary", el_from_float(0.7), el_from_float(0.7), el_from_float(0.9), "Episodic", tags ) if str_eq(summary_id, "") { println("[api] consolidate: session summary engram write failed — summary node lost") } } return "{\"ok\":true,\"snapshot\":\"" + snap + "\"}" } // ── Stage 1: structural audit ───────────────────────────────────────────────── // // WHAT THIS IMPLEMENTS // The CGI provisional, 05-detailed-description.md, "Stage 1: Structural audit // 430". Verbatim, the audit module evaluates: the density and typed // distribution of causal edges; the consistency between value nodes and // execution-record neighborhoods; the richness and connectivity of the // self-model; and the authenticity of open-question nodes in the wonder // manifest. It "produces a coherence assessment 432 — NOT A BINARY SCORE but // an annotated characterization of the graph's structural properties". // // That last clause is the whole shape of this handler. Every finding carries // its own numbers AND a plain-language note saying what the numbers mean and // how they were obtained. There is no pass/fail, no percentage-of-health, no // composite score, and `"score":null` is emitted explicitly so a downstream // reader cannot mistake its absence for an omission. // // WHY IT EXISTS NOW, AND WHY THE FIRST FINDING IS THE ONE IT IS // `runStructuralAudit` has been an advertised MCP tool with nothing behind it: // the dispatcher GET'd /session/begin and returned that blob (mcp-wrapper/src/ // main.el). Meanwhile the failure the audit would have caught ran silently for // about three weeks — the soul reported 103,089 nodes while the engram, which // OWNS persistence, held ~79,900; a crash discarded the difference. Every boot // reported green throughout, because nothing in the system ever compared the // two sides. So finding 1 is owner-versus-runtime divergence: it is the check // whose absence cost real memory, and it is cheap and exact. // // WHAT IS DELIBERATELY NOT HERE (stage 1b, see the `deferred` array in the // response): value/execution-record consistency and wonder-manifest // authenticity. Both need node types that barely exist in this graph today — // the response MEASURES those populations and reports the counts as the reason, // rather than asserting a deferral without evidence. // // MEASUREMENT HONESTY: EXACT WHERE CHEAP, SAMPLED WHERE NOT, ALWAYS LABELLED // Counts, edge typing and self-model connectivity are exact. Orphan rate and // dangling-edge rate are SAMPLED, because the engram runtime has no node-id // index — `engram_find_node_index` is a linear scan over every node, so an // exhaustive dangling check is O(nodes x edges) (~2.2e9 string compares at // today's scale, tens of seconds inside one request). The samples are UNIFORM // across the whole population, not head-of-list, and every sampled figure is // emitted with its own `sampled` / `population` fields plus an extrapolation // labelled as such. Raise `?edge_sample=` / `?node_sample=` to the population // size to run either check exhaustively and pay the time. The real fix is an // id index in the runtime; that is the engram repo's, not this handler's. // audit_pct1 — one-decimal percentage as a bare JSON number, sign-safe. // Integer math only: EL has no fixed-precision formatter, and float_to_str // would put an unbounded mantissa in the response. fn audit_pct1(num: Int, den: Int) -> String { if den <= 0 { return "null" } let neg: Bool = num < 0 let a: Int = if neg { 0 - num } else { num } let tenths: Int = (a * 1000) / den let whole: Int = tenths / 10 let frac: Int = tenths - (whole * 10) let sign: String = if neg { "-" } else { "" } return sign + int_to_str(whole) + "." + int_to_str(frac) } // audit_finding — the one envelope every finding uses: name, the measurements, // and the annotation. Keeping it in one place is what stops the characterization // from degenerating into a bag of numbers with no reading attached. fn audit_finding(name: String, measured: String, note: String) -> String { return "{\"finding\":\"" + name + "\"" + ",\"measured\":{" + measured + "}" + ",\"note\":\"" + api_json_escape(note) + "\"}" } // audit_str_at — read the quoted string value starting at byte `start`. // Slices a bounded window rather than the tail of the (multi-MB) edges array, so // this is O(window) per call instead of O(remaining input). fn audit_str_at(s: String, start: Int, maxlen: Int) -> String { let n: Int = str_len(s) if start < 0 || start >= n { return "" } let end_guess: Int = start + maxlen let stop: Int = if end_guess > n { n } else { end_guess } let win: String = str_slice(s, start, stop) let q: Int = str_index_of(win, "\"") if q < 0 { return "" } return str_slice(win, 0, q) } // audit_rel_count — exact count of edges carrying `rel`, by scanning the emitted // edge array for the literal `"relation":""`. engram_emit_edge_json writes // metadata ESCAPED as a string, so no nested object can contain that literal and // the count cannot be inflated by edge payloads. fn audit_rel_count(edges: String, rel: String) -> Int { return str_count(edges, "\"relation\":\"" + rel + "\"") } // audit_owner_stats — ask the persistence OWNER for its own counts. // Returns "" when there is no HTTP owner configured or the owner is unreachable; // both are reported as findings, never as a failure of the audit. fn audit_owner_stats(url: String) -> String { if str_eq(url, "") { return "" } return http_get(url + "/api/stats") } // audit_divergence — FINDING 1. Runtime (this soul's in-process graph) versus // the persistence owner's own count. Trend is measured against the previous // audit recorded in soul state, so a second call answers "is the gap growing?" // rather than just restating it. fn audit_divergence() -> String { let rt_nodes: Int = engram_node_count() let rt_edges: Int = engram_edge_count() let url: String = wt_engram_url() if str_eq(url, "") { return audit_finding("owner_runtime_divergence", "\"runtime_nodes\":" + int_to_str(rt_nodes) + ",\"runtime_edges\":" + int_to_str(rt_edges) + ",\"owner\":\"none\",\"owner_reachable\":false", "No HTTP persistence owner is configured, so this soul IS the owner " + "(file mode) and divergence is not defined. This check only has " + "meaning when ENGRAM_URL points at a separate engram that owns the " + "canonical store.") } let stats: String = audit_owner_stats(url) // REACHABILITY IS PROVED BY THE PAYLOAD, NOT BY A NON-EMPTY REPLY. // http_get does not return "" on a connection failure — it returns a JSON // error object ({"error":"Failed to connect to ... Couldn't connect to // server"}). Testing only for "" made a DEAD owner read as reachable with // node_count 0, i.e. the audit would have reported a 100% divergence and // named it as data loss. That false positive is worse than no check at all: // it is precisely the kind of confident wrong answer this route exists to // stop. Require the field the contract promises. let owner_nc_raw: String = json_get_raw(stats, "node_count") if str_eq(stats, "") || str_eq(owner_nc_raw, "") { return audit_finding("owner_runtime_divergence", "\"runtime_nodes\":" + int_to_str(rt_nodes) + ",\"runtime_edges\":" + int_to_str(rt_edges) + ",\"owner\":\"" + api_json_escape(url) + "\",\"owner_reachable\":false" + ",\"owner_reply\":\"" + api_json_escape(api_utf8_trunc(stats, 200)) + "\"", "The persistence owner at " + url + " did not return a node_count " + "from GET /api/stats. Divergence is UNKNOWN, NOT ZERO — an owner " + "that cannot be read is exactly the condition under which the " + "runtime's own count means least, and reporting 0 for the owner " + "would manufacture a total-loss reading out of a network error. " + "Reported as a finding rather than raised as an error so the rest " + "of the audit still returns; the owner's raw reply is in " + "owner_reply.") } let ow_nodes: Int = json_get_int(stats, "node_count") let ow_edges: Int = json_get_int(stats, "edge_count") let d_nodes: Int = rt_nodes - ow_nodes let d_edges: Int = rt_edges - ow_edges // Trend against the previous audit in this soul's state. let prev_raw: String = state_get("audit_prev_node_delta") let prev: Int = str_to_int(prev_raw) let abs_now: Int = if d_nodes < 0 { 0 - d_nodes } else { d_nodes } let abs_prev: Int = if prev < 0 { 0 - prev } else { prev } let trend: String = if str_eq(prev_raw, "") { "no_prior_audit" } else { if abs_now > abs_prev { "growing" } else { if abs_now < abs_prev { "shrinking" } else { "flat" } } } state_set("audit_prev_node_delta", int_to_str(d_nodes)) state_set("audit_prev_ts", int_to_str(time_now())) let note_head: String = if d_nodes == 0 { "Runtime and owner agree on node count." } else { "Runtime holds " + int_to_str(d_nodes) + " nodes (" + audit_pct1(d_nodes, rt_nodes) + "% of its own graph) that the persistence owner does not report. Nodes " + "that exist only in runtime memory do not survive a restart." } return audit_finding("owner_runtime_divergence", "\"runtime_nodes\":" + int_to_str(rt_nodes) + ",\"runtime_edges\":" + int_to_str(rt_edges) + ",\"owner\":\"" + api_json_escape(url) + "\",\"owner_reachable\":true" + ",\"owner_nodes\":" + int_to_str(ow_nodes) + ",\"owner_edges\":" + int_to_str(ow_edges) + ",\"node_delta\":" + int_to_str(d_nodes) + ",\"edge_delta\":" + int_to_str(d_edges) + ",\"node_delta_pct_of_runtime\":" + audit_pct1(d_nodes, rt_nodes) + ",\"trend_vs_previous_audit\":\"" + trend + "\"" + ",\"previous_node_delta\":" + (if str_eq(prev_raw, "") { "null" } else { int_to_str(prev) }), note_head + " Trend against the previous audit recorded in this soul's " + "state: " + trend + ". This is the comparison whose absence let a " + "~24,000-node loss run for weeks with every boot reporting green.") } // audit_edge_typing — FINDING 2. Density plus the typed distribution the patent // asks for, against the claim-10 relation vocabulary. Exact: str_count over the // emitted edge array, one linear pass per relation. fn audit_edge_typing(edges: String, total_edges: Int, node_total: Int) -> String { let c_sup: Int = audit_rel_count(edges, "Supersedes") let c_cau: Int = audit_rel_count(edges, "Causes") let c_con: Int = audit_rel_count(edges, "Contains") let c_ref: Int = audit_rel_count(edges, "References") let c_ctr: Int = audit_rel_count(edges, "Contradicts") let c_exe: Int = audit_rel_count(edges, "Exemplifies") let c_act: Int = audit_rel_count(edges, "Activates") let c_tmp: Int = audit_rel_count(edges, "TemporallyPrecedes") let typed: Int = c_sup + c_cau + c_con + c_ref + c_ctr + c_exe + c_act + c_tmp // Lowercase near-misses: the same eight concepts written by the ad-hoc write // paths (linkEntities defaults to "associates", linkCausal to "causes"). // Counted separately because "the vocabulary is unused" and "the vocabulary // is used in the wrong case" are different defects with different fixes. let l_sup: Int = audit_rel_count(edges, "supersedes") let l_cau: Int = audit_rel_count(edges, "causes") let l_con: Int = audit_rel_count(edges, "contains") let l_ref: Int = audit_rel_count(edges, "references") let l_ctr: Int = audit_rel_count(edges, "contradicts") let l_exe: Int = audit_rel_count(edges, "exemplifies") let l_act: Int = audit_rel_count(edges, "activates") let l_tmp: Int = audit_rel_count(edges, "temporallyPrecedes") let near: Int = l_sup + l_cau + l_con + l_ref + l_ctr + l_exe + l_act + l_tmp let untyped: Int = total_edges - typed return audit_finding("typed_edge_distribution", "\"total_edges\":" + int_to_str(total_edges) + ",\"total_nodes\":" + int_to_str(node_total) // Density per 100 nodes, not per node: EL has no fixed-precision float // formatter, and "0.3 edges per node" rounded to an integer is a lie. + ",\"edges_per_100_nodes\":" + audit_pct1(total_edges, node_total) + ",\"claim10_typed\":" + int_to_str(typed) + ",\"claim10_typed_pct\":" + audit_pct1(typed, total_edges) + ",\"outside_claim10_vocabulary\":" + int_to_str(untyped) + ",\"lowercase_near_miss\":" + int_to_str(near) + ",\"by_relation\":{" + "\"Supersedes\":" + int_to_str(c_sup) + ",\"Causes\":" + int_to_str(c_cau) + ",\"Contains\":" + int_to_str(c_con) + ",\"References\":" + int_to_str(c_ref) + ",\"Contradicts\":" + int_to_str(c_ctr) + ",\"Exemplifies\":" + int_to_str(c_exe) + ",\"Activates\":" + int_to_str(c_act) + ",\"TemporallyPrecedes\":" + int_to_str(c_tmp) + "}", "Only " + int_to_str(typed) + " of " + int_to_str(total_edges) + " edges use the claim-10 causal vocabulary; the remainder are ad-hoc " + "relation strings, which is why the graph's causal claims cannot yet " + "be checked for internal consistency — an untyped edge asserts " + "association, not causation. " + int_to_str(near) + " edges use a " + "lowercase spelling of a claim-10 relation: those are near-misses the " + "write paths could be corrected to emit, not genuinely foreign types.") } // audit_orphans_dangling — FINDING 3. Both figures are SAMPLED; see the header // for why exhaustive is O(nodes x edges) on this runtime. // // An "orphan" here is a node with zero RESOLVABLE edges: engram_neighbors_json // drops any edge whose other endpoint does not resolve to a node, so a node // whose only edges are dangling reads as an orphan. That is the right reading — // such a node is unreachable by traversal — but it is stated rather than hidden. fn audit_orphans_dangling(edges: String, total_edges: Int, node_total: Int, edge_cap: Int, node_cap: Int) -> String { // ── orphan sample: uniform stride over the node store ── let n_take: Int = if node_total < node_cap { node_total } else { node_cap } let n_stride: Int = if n_take > 0 { node_total / n_take } else { 1 } let n_stride = if n_stride < 1 { 1 } else { n_stride } let orphans: Int = 0 let n_checked: Int = 0 let j: Int = 0 while j < n_take { let one: String = engram_scan_nodes_json(1, j * n_stride) let nid: String = json_get(json_array_get(one, 0), "id") if !str_eq(nid, "") { let nbrs: String = engram_neighbors_json(nid, 1, "both") let deg: Int = json_array_len(nbrs) let orphans = if deg == 0 { orphans + 1 } else { orphans } let n_checked = n_checked + 1 } let j = j + 1 } // ── dangling sample: uniform stride over the edge array ── // str_index_of_all gives every edge's field offsets in ONE linear pass, so // any index can be read in O(1). json_array_get would have been O(i) per // element and O(n^2) over the array. let from_pos: [Int] = str_index_of_all(edges, "\"from_id\":\"") let to_pos: [Int] = str_index_of_all(edges, "\"to_id\":\"") let nf: Int = len(from_pos) let nt: Int = len(to_pos) let ne: Int = if nf < nt { nf } else { nt } let e_take: Int = if ne < edge_cap { ne } else { edge_cap } let e_stride: Int = if e_take > 0 { ne / e_take } else { 1 } let e_stride = if e_stride < 1 { 1 } else { e_stride } let dangling: Int = 0 let e_checked: Int = 0 let i: Int = 0 while i < ne && e_checked < e_take { let fid: String = audit_str_at(edges, get(from_pos, i) + 11, 96) let tid: String = audit_str_at(edges, get(to_pos, i) + 9, 96) let f_gone: Bool = str_eq(engram_get_node_json(fid), "{}") let t_gone: Bool = if f_gone { true } else { str_eq(engram_get_node_json(tid), "{}") } let dangling = if f_gone || t_gone { dangling + 1 } else { dangling } let e_checked = e_checked + 1 let i = i + e_stride } let orphan_est: Int = if n_checked > 0 { (orphans * node_total) / n_checked } else { 0 } let dangle_est: Int = if e_checked > 0 { (dangling * total_edges) / e_checked } else { 0 } let exhaustive_n: String = if n_checked >= node_total { "true" } else { "false" } let exhaustive_e: String = if e_checked >= ne { "true" } else { "false" } return audit_finding("orphans_and_dangling_edges", "\"nodes_population\":" + int_to_str(node_total) + ",\"nodes_sampled\":" + int_to_str(n_checked) + ",\"nodes_sample_exhaustive\":" + exhaustive_n + ",\"orphans_in_sample\":" + int_to_str(orphans) + ",\"orphan_rate_pct\":" + audit_pct1(orphans, n_checked) + ",\"orphans_extrapolated\":" + int_to_str(orphan_est) + ",\"edges_population\":" + int_to_str(total_edges) + ",\"edges_sampled\":" + int_to_str(e_checked) + ",\"edges_sample_exhaustive\":" + exhaustive_e + ",\"dangling_in_sample\":" + int_to_str(dangling) + ",\"dangling_rate_pct\":" + audit_pct1(dangling, e_checked) + ",\"dangling_extrapolated\":" + int_to_str(dangle_est), "Orphan = zero RESOLVABLE edges, so a node whose only edges dangle counts " + "as an orphan; either way it is unreachable by traversal. Dangling = an " + "edge with an endpoint id that resolves to no node. Both are uniform " + "stride samples over the whole population, not the head of the list; " + "the extrapolations are estimates and are labelled as such. Pass " + "?node_sample= / ?edge_sample= at or above the population size to run " + "either check exhaustively. A high orphan rate is a characterization, " + "not a verdict: an accumulating store legitimately holds unlinked " + "material. It becomes a defect when the write paths were SUPPOSED to " + "link and did not.") } // audit_pillar — one self-model pillar: present, how much content, how connected. fn audit_pillar(key: String, id: String) -> String { let node: String = engram_get_node_json(id) let present: Bool = !str_eq(node, "{}") && !str_eq(node, "") if !present { return "\"" + key + "\":{\"id\":\"" + id + "\",\"present\":false" + ",\"content_length\":0,\"degree\":0}" } let content: String = json_get(node, "content") let deg: Int = json_array_len(engram_neighbors_json(id, 1, "both")) return "\"" + key + "\":{\"id\":\"" + id + "\",\"present\":true" + ",\"label\":\"" + api_json_escape(json_get(node, "label")) + "\"" + ",\"tier\":\"" + api_json_escape(json_get(node, "tier")) + "\"" + ",\"content_length\":" + int_to_str(str_len(content)) + ",\"degree\":" + int_to_str(deg) + "}" } // audit_self_model — FINDING 4. "the richness and connectivity of the // self-model ... is it connected to behavioral evidence?" // // This finding RETIRES the Claude-side vitals identity block. That check lived // outside the system it was checking — a shell script grepping a snapshot — so // it could only ever report on a file, and it went on reporting green while the // memory-philosophy pillar was absent from the live graph for about three weeks. // Asking the running soul about its own three pillars is the designed mechanism; // a shell probe was the fourth patch on the same hole. fn audit_self_model() -> String { let dna: String = audit_pillar("intellectual_dna", "kn-5adecd7e-d6db-4576-87fe-6ef8a935cea6") let val: String = audit_pillar("values_hub", "kn-5b606390-a52d-4ca2-8e0e-eba141d13440") let phi: String = audit_pillar("memory_philosophy", "kn-dcfe04b3-3702-4cac-b6f0-ecb4db837eee") let root: String = audit_pillar("self_root", "kn-efeb4a5b-5aff-4759-8a97-7233099be6ee") return audit_finding("self_model_connectivity", "\"pillars\":{" + dna + "," + val + "," + phi + "," + root + "}", "The three identity pillars plus the self root. `degree` counts nodes " + "reachable in one hop in either direction — the self-model's connection " + "to the rest of the graph. present:false on any pillar is the condition " + "that ran undetected for weeks; content_length distinguishes a pillar " + "that is present from one that is present but hollowed out. The patent " + "also asks whether the self-model makes ACCURATE PREDICTIONS about the " + "system's own behavior; that half needs Prediction nodes and is deferred " + "with the rest of stage 1b below.") } // audit_deferred — what stage 1 does NOT yet evaluate, with the measured reason. // Emitted as data, not as a comment, so a reader of the assessment sees the gap // and its evidence rather than inferring completeness from silence. fn audit_deferred() -> String { let preds: Int = json_array_len(api_or_empty(engram_scan_nodes_by_type_json("Prediction", 50, 0))) let wonders: Int = json_array_len(api_or_empty(engram_scan_nodes_by_type_json("WonderQuestion", 50, 0))) return "[{\"deferred\":\"value_execution_record_consistency\"" + ",\"stage\":\"1b\"" + ",\"measured\":{\"prediction_nodes_found\":" + int_to_str(preds) + "}" + ",\"reason\":\"" + api_json_escape( "The patent asks whether the execution history SUPPORTS the stated " + "values or shows systematic conflict. That requires execution " + "records tied to value nodes and predictions to score them against. " + "Prediction nodes found (capped at 50): " + int_to_str(preds) + ". Asserting value/execution coherence on that population would be " + "a fabricated result, which is worse than a stated gap.") + "\"}" + ",{\"deferred\":\"wonder_manifest_authenticity\"" + ",\"stage\":\"1b\"" + ",\"measured\":{\"wonder_question_nodes_found\":" + int_to_str(wonders) + "}" + ",\"reason\":\"" + api_json_escape( "The patent asks whether pull weights CORRELATE WITH GENUINE " + "PREDICTION UNCERTAINTY or are uniform/externally assigned — a " + "correlation between two populations. WonderQuestion nodes readable " + "by type (capped at 50): " + int_to_str(wonders) + ", against " + int_to_str(preds) + " Prediction nodes. There is a known write/read " + "node-type mismatch on the wonder path; until that is fixed and both " + "populations exist, any correlation reported here would be noise.") + "\"}]" } // handle_api_structural_audit — Stage 1. Returns the coherence assessment 432: // an annotated characterization, explicitly NOT a score. // // COST NOTE: the edge findings need the relation labels, and the runtime exposes // no edge-enumeration builtin. The only way to see them is the same one // GET /api/graph/edges already uses — engram_save to a SCRATCH path (never the // owner's canonical file; see routes.el, neuron#117) and read the array back. // On a large graph that is a multi-hundred-MB write, so this is a manual audit // route, not something to put on a timer. Pass ?edges=0 to skip both edge // findings and get the divergence + self-model readings cheaply. fn handle_api_structural_audit(method: String, path: String, body: String) -> String { let node_total: Int = engram_node_count() let edge_total: Int = engram_edge_count() let want_edges: Bool = !str_eq(api_query_param(path, "edges"), "0") let edge_cap: Int = api_query_int(path, "edge_sample", 3000) let node_cap: Int = api_query_int(path, "node_sample", 300) let divergence: String = audit_divergence() let self_model: String = audit_self_model() let edge_part: String = if want_edges { // Scratch export only. state_get("soul_snapshot_path") is deliberately // NOT used: in HTTP-engram mode the soul is not the persistence owner and // must never write the canonical file, not even on a read path. let scratch_dir: String = env("TMPDIR") let scratch_base: String = if str_eq(scratch_dir, "") { "/tmp" } else { scratch_dir } let snap_path: String = scratch_base + "/soul-audit-export-" + state_get("soul_cgi_id") + ".json" // engram_save returns Int (1 ok / 0 fail); str_eq on it SIGSEGVs (#150). let saved: Int = engram_save(snap_path) if saved == 0 { "," + audit_finding("typed_edge_distribution", "\"available\":false", "Could not export the graph to " + snap_path + " for edge analysis, " + "so edge typing and the dangling-edge sample were not run. " + "Reported as a gap, not as zero findings.") } else { // wt_read, not fs_read: fs_read leaves a thread-local length hint that // the NEXT HTTP response would use as its Content-Length, appending // adjacent heap bytes to the reply (see persist.el wt_read). let snap: String = wt_read(snap_path) let edges_raw: String = json_get_raw(snap, "edges") let edges: String = if str_eq(edges_raw, "") { "[]" } else { edges_raw } "," + audit_edge_typing(edges, edge_total, node_total) + "," + audit_orphans_dangling(edges, edge_total, node_total, edge_cap, node_cap) } } else { "" } return "{\"audit\":\"structural\",\"stage\":1" + ",\"spec\":\"CGI provisional 05-detailed-description.md, Stage 1: Structural audit 430\"" + ",\"assessment\":\"coherence_assessment_432\"" + ",\"assessment_kind\":\"annotated_characterization\"" + ",\"score\":null" + ",\"score_note\":\"By design. The specification calls for an annotated characterization of the graph's structural properties, not a binary score. Read the findings.\"" + ",\"cgi_id\":\"" + api_json_escape(state_get("soul_cgi_id")) + "\"" + ",\"ts_ms\":" + int_to_str(time_now()) + ",\"findings\":[" + divergence + "," + self_model + edge_part + "]" + ",\"deferred\":" + audit_deferred() + "}" }