90d3f0bc76
Reconciles PR #105 ("fix: engram search latency — pin embed model, cache query embeddings, bound activate BFS") with dev's ACTUAL current engram_activate, rather than the ancient pre-restructure snapshot #105 was built against. WHY THIS NEEDED RECONCILIATION, NOT A DIRECT PORT: #105's single commit (1dc49b1) modifies `lang/el-compiler/runtime/el_runtime.c` — a path that does not exist on dev (dev has `lang/runtime/el_runtime.c`; the restructure that renamed it happened after #105's branch point, which traces to a July 22 merge-base, weeks before the M8/M8.1/qgate/fan-effect/adjacency-index work this file has grown since). #105's own engram_activate is consequently the PRE-restructure version: no adjacency index (O(E) full edge scan per hop), no query-aware qgate, no ACT-R fan effect, no eg_edge_eff_weight, and no awareness of dev's cosq/e_eff embedding-blend semantic layer — it built a parallel `g_qcache`/`engram_embed_raw` mechanism from scratch against code that no longer exists at that path. A raw merge/cherry-pick was not possible and would have been wrong even if it were: taking #105's tree wholesale would have thrown away everything dev grew in the meantime (qgate, fan effect, adjacency index, and this session's own M8 HNSW vindex integration). RECONCILIATION: kept dev's cosq/e_eff mechanism as the semantic layer entirely intact (unchanged by this commit) and ported #105's three genuinely additive wins on TOP of it, at their equivalent sites in the CURRENT eg_embed_fetch/engram_activate: 1. keep_alive:-1 on the Ollama embed request body (eg_embed_fetch) — pins the embed model resident so a larger generation model loading under unified-memory pressure can't evict it and force a cold reload on the next search (#105 measured ~2.2s cold vs ~0.02-0.05s warm). 2. Query-embedding cache upgraded from dev's single-slot (`_eg_qcache_text`, only ever remembered the LAST query) to a direct-mapped, FNV-1a-keyed, 1024-slot cache (reusing the existing engram_id_hash) — so the curiosity loop's rotating phrases actually hit the cache instead of evicting each other every call. Same "pointer owned by the cache, not freed by caller" contract as before, just per-slot instead of global. 3. Beam cap on the layer-1 spreading-activation BFS (new engram_activate_beam(), tunable via ENGRAM_ACTIVATE_BEAM, default 128). The FIFO frontier is processed in hop-level batches (entries sharing .hops are provably contiguous — see the code comment); when a level exceeds the beam width, only the top-`beam` by activation actually EXPAND. Every node in an oversized level still gets reached[]/best_bg[] recorded (that happens at enqueue time, one level up) and appears in the reported/promoted set — the cap bounds associative SPREAD width only, never recall of what was already found. Kept as a genuine additional bound even though the adjacency index + qgate + fan effect already mitigate #105's original "hub-node explosion" failure mode for a different reason: those prune WHICH targets matter; this bounds worst-case width regardless. Everything else in dev's engram_activate — cosq/e_eff, the qgate rescale, the fan effect, eg_edge_eff_weight, the M8 HNSW vindex seed discovery from the #109 reconciliation earlier this session — is untouched. VERIFIED (nsbx sandbox only, live :8742/:7770 never touched): cc -std=c11 -O2 clean build; booted in an isolated sandbox against a real cloned production snapshot (13,424 nodes / 37,656 edges); ran 5 activate() calls across rotating queries at depth 3, including the same query issued twice non-consecutively (2nd hit landed at 476ms vs the 1st at 483ms — consistent with a cache hit once Ollama's own warm-model latency is accounted for; no crash, correct varied result counts (367-2610 nodes) each call; act-stats JSON read correctly throughout. Built on top of the M8/#109 reconciliation (bacaf3d, merged to dev as #109) — dev's current HEAD at the time of this commit.