Archived
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:
@@ -392,12 +392,31 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
return engram_scan_nodes_json(9999, 0)
|
||||
}
|
||||
if str_eq(clean, "/api/graph/edges") {
|
||||
// TODO(reliability #8): engram_save races with awareness loop mem_save().
|
||||
// Both now use atomic write-to-temp+rename (el_runtime.c). Serialised
|
||||
// by engram_global_mu. Future: add engram_edges_json() builtin.
|
||||
let snap_path: String = env("HOME") + "/.neuron/engram/snapshot.json"
|
||||
engram_save(snap_path)
|
||||
let snap: String = fs_read(snap_path)
|
||||
// A READ ROUTE MUST NEVER WRITE THE CANONICAL SNAPSHOT.
|
||||
//
|
||||
// (2026-08-07 self-review — caught by doing it.) This route used to
|
||||
// serialize to $HOME/.neuron/engram/snapshot.json and read the edges
|
||||
// back out of it. That path is the ENGRAM SERVER's canonical store,
|
||||
// and this is the soul process. One GET here overwrote the durable
|
||||
// graph with the soul's in-memory copy. I triggered it myself this
|
||||
// morning fetching edges for a census: 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 this time — Knowledge 1198→1218, Memory 1238→1242, no
|
||||
// durable type down — so nothing was lost. That was luck, not
|
||||
// design. Had the soul been running a partial load (the exact
|
||||
// failure soul.el's safe_to_seed guard exists to catch), a single
|
||||
// GET would have destroyed the store, and no guard on the write
|
||||
// side would have seen 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 here: write the export where only an export
|
||||
// lives. It also stops a 60MB serialize-and-reread on every GET of
|
||||
// a debug endpoint.
|
||||
let export_path: String = env("HOME") + "/.neuron/engram/.soul-edges-export.json"
|
||||
engram_save(export_path)
|
||||
let snap: String = fs_read(export_path)
|
||||
let edges_raw: String = json_get_raw(snap, "edges")
|
||||
return if str_eq(edges_raw, "") { "[]" } else { edges_raw }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user