self-review 2026-08-07: push what was learned; stop a read route writing the canonical store

Two fixes, one found by making the other.

1. HEBBIAN WRITE-BACK. This daemon learned 1,198 associations in 23h48m and
kept none of them: it syncs FROM the engram server and never pushes, and
mem_save() is unreachable in HTTP mode by design (soul.el only sets
soul_snapshot_path inside `is_genesis && safe_to_seed`, false whenever
ENGRAM_URL is set, because the server owns persistence). So the one process
that runs idle cognition -- where essentially all co-activation happens -- was
the one process that could not remember what it learned.

hebb_consolidate() now drains the runtime's write-back queue on every heartbeat
and POSTs it as ONE batch to /api/edges/batch. One request, one durable write,
not one 60MB snapshot per edge. Also drains on clean shutdown, so an exit
between beats doesn't take the last 8 minutes of learning with it.

_auth is required and its absence is silent: check_auth_ok exempts GET and
/api/neuron/state-events (which is why ise_post works keyless) but gates every
other mutation on "_auth" in the BODY -- http_serve surfaces no headers, so
there is no Bearer path. An unauthorized reply is NON-EMPTY, so the obvious
`if resp == "" return 0` check would have reported delivery of edges that were
refused, after the drain had already destroyed them. Caught before it shipped.
Gauges hebb_wb_pending/_drained/_dropped/_sent go into the heartbeat so a
consolidation path that stops delivering is visible in the stream.

2. A READ ROUTE MUST NEVER WRITE THE CANONICAL SNAPSHOT. GET /api/graph/edges
serialized this process's graph straight over $HOME/.neuron/engram/snapshot.json
-- the engram SERVER's durable store -- and read the edges back out of it. I
triggered it myself this morning fetching edges for the census above:
snapshot.json went from the server's 41,213 edges to the soul's 42,431, and the
next engram restart loaded the soul's graph as canonical. It happened to be a
superset (Knowledge 1198->1218, Memory 1238->1242, no durable type down), so
nothing was lost. That was luck. Had the soul been running a partial load --
the exact failure soul.el's safe_to_seed guard exists to catch -- one GET would
have destroyed the store, with no write-side guard able to see it coming.

The engram server fixed this same class of bug on 2026-07-21 by routing exports
to a dotted sidecar; the soul kept the original pattern. Same fix: exports go to
.soul-edges-export.json. Also stops a 60MB serialize-and-reread per GET.

Verified: boot 26 loaded 42,432 edges with hebb_max 0.4941 carried across the
restart -- the first time this daemon has ever started knowing what it learned.
This commit is contained in:
2026-08-07 08:46:53 -05:00
parent 97d22ffe44
commit 86e269fa91
12 changed files with 380 additions and 193 deletions
+6
View File
@@ -20,6 +20,7 @@ extern fn engram_compile(intent: String) -> String
extern fn distill_transcript(transcript: String) -> String
extern fn json_safe(s: String) -> String
extern fn current_engine_note(model: String) -> String
extern fn bounded_persona_floor() -> String
extern fn build_system_prompt(ctx: String, chat_mode: Bool) -> String
extern fn hist_append(hist: String, role: String, content: String) -> String
extern fn hist_trim(hist: String) -> String
@@ -28,6 +29,7 @@ extern fn clean_llm_response(s: String) -> String
extern fn conv_history_persist(hist: String) -> Void
extern fn conv_history_load() -> String
extern fn session_preload_bullets(nodes: String, max_bullets: Int, snip_len: Int) -> String
extern fn affective_context_prefix() -> String
extern fn handle_chat(body: String) -> String
extern fn handle_see(body: String) -> String
extern fn studio_tools_json() -> String
@@ -46,6 +48,10 @@ extern fn call_neuron_mcp(tool_name: String, args: String) -> String
extern fn agent_workspace_root() -> String
extern fn path_within_root(path: String, root: String) -> Bool
extern fn resolve_in_root(path: String, root: String) -> String
extern fn run_command_is_readonly(cmd: String) -> Bool
extern fn cmd_abs_escape_at(cmd: String, root: String, needle: String) -> Bool
extern fn run_command_guard(cmd: String, root: String) -> String
extern fn classify_tool_risk(tool_name: String, tool_input: String) -> String
extern fn dispatch_tool(tool_name: String, tool_input: String) -> String
extern fn is_builtin_tool(tool_name: String) -> Bool
extern fn next_bridge_id() -> String