Bound beginSession/compileCtx payloads to a compact digest
Port the payload-bounding fix (PR #102, commit 872120c) onto main. The
session-init endpoints projected unbounded engram nodes (~900KB), closing
the MCP client socket on every beginSession. Cap the lists (8 activated /
10 recent for begin_session, 10/20 for compile_ctx) and project each node
to a light identity + a bounded, UTF-8-safe content snippet via
api_compact_node / api_compact_activated / api_utf8_trunc. Response drops
~900KB -> ~12KB; full content stays available via recall/fetch/inspectGraph.
Regenerate dist/soul.c (the CI-built amalgamation) and dist/neuron-api.c
from source. The soul.c regen also compiles in already-merged source the
previously-committed soul.c was stale against (agent write/edit receipt
fixes, #100/#101); verified via scripts/verify-soul-contract.sh
(PRESENCE + IMMUTABILITY PASS) and a clean CI-style cc build.
This commit is contained in:
+131
-12
@@ -87,6 +87,107 @@ fn api_or_empty(s: String) -> String {
|
||||
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.
|
||||
// After a write builtin returns an id, confirm the node is actually queryable
|
||||
// via engram_get_node_json(id) (returns "" or "null" when missing). Returns
|
||||
@@ -170,27 +271,45 @@ fn memory_hide_tombstoned(raw: String, path: String) -> String {
|
||||
// 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: String = engram_activate_json("session start recent memory important", 2)
|
||||
let self_nbrs: String = engram_neighbors_json("kn-efeb4a5b-5aff-4759-8a97-7233099be6ee", 1, "both")
|
||||
let state_events: String = engram_scan_nodes_by_type_json("InternalStateEvent", 5, 0)
|
||||
let recent: String = engram_scan_nodes_json(10, 0)
|
||||
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\":" + api_or_empty(recent)
|
||||
+ ",\"activated\":" + api_or_empty(activated)
|
||||
+ ",\"self_neighbors\":" + api_or_empty(self_nbrs)
|
||||
+ ",\"recent_state_events\":" + api_or_empty(state_events) + "}"
|
||||
+ ",\"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()
|
||||
let activated: String = engram_activate_json("active work context current task in progress", 2)
|
||||
let recent: String = engram_scan_nodes_json(20, 0)
|
||||
// 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\":" + api_or_empty(recent)
|
||||
+ ",\"activated\":" + api_or_empty(activated) + "}"
|
||||
+ ",\"recent_nodes\":" + recent
|
||||
+ ",\"activated\":" + activated + "}"
|
||||
}
|
||||
|
||||
// ── Memory ────────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user