diff --git a/lang/runtime/el_runtime.c b/lang/runtime/el_runtime.c index a9ae6e3..d73155a 100644 --- a/lang/runtime/el_runtime.c +++ b/lang/runtime/el_runtime.c @@ -18271,3 +18271,61 @@ el_val_t el_mem_check(void) { } return 0; } + +/* ── engram_recall_json / cgi_* accessors — restored 2026-08-15 ────────────── + * + * These existed in the runtime neuron vendored (v1.0.0-20260501) and were lost + * when this runtime moved on, so a soul built against current el would fail to + * link — and, worse, the naive "fix" of pointing recall at engram_search_json + * would have SILENTLY DOWNGRADED the mind's whole retrieval surface from + * semantic to lexical, with no error at any layer. + * + * The lexical/semantic split is a real safety boundary, not redundant naming + * (neuron-api.el:613 documents it): engram_search_json stays LEXICAL because + * ~40 internal call sites pass a KEY and seven of them DELETE every record + * returned — making those semantic would delete fuzzy matches. recall is the + * SEMANTIC surface, used by the retrieval routes. + * + * The old implementation was eg_search_json_impl(q, limit, with_legs=1): embed + * the query, cosine over the corpus, then a graph leg from semantic seeds. + * In this runtime that is exactly what engram_activate() already does (it + * embeds via eg_embed_fetch, scores by cosine, then spreads activation), so + * recall delegates to it rather than re-deriving a second semantic path. + * Output shape matches engram_search_json — a flat array of node objects via + * engram_emit_node_json — because existing callers (memory.el:80, + * neuron-api.el:618) parse it as search's shape, not activate's envelope. + * ──────────────────────────────────────────────────────────────────────────── */ + +el_val_t engram_recall_json(el_val_t query, el_val_t limit) { + int64_t lim = (int64_t)limit; + if (lim <= 0) lim = 100; + + /* depth 1: the associative leg, one hop out from the semantic seeds. */ + el_val_t lst = engram_activate(query, (el_val_t)(int64_t)1); + ElList* arr = (ElList*)(uintptr_t)lst; + + JsonBuf b; jb_init(&b); + jb_putc(&b, '['); + int64_t emitted = 0; + if (arr) { + for (int64_t i = 0; i < arr->length && emitted < lim; i++) { + if (!arr->elems[i]) continue; + el_val_t node_map = el_map_get(arr->elems[i], EL_STR("node")); + el_val_t id_v = el_map_get(node_map, EL_STR("id")); + const char* id_s = EL_CSTR(id_v); + EngramNode* n = id_s ? engram_find_node(id_s) : NULL; + if (!n) continue; + if (emitted > 0) jb_putc(&b, ','); + engram_emit_node_json(&b, n, 0); + emitted++; + } + } + jb_putc(&b, ']'); + return el_wrap_str(b.buf); +} + +/* cgi_* — read-only identity accessors over the process-wide CGI registration + * set by cgi_register(). Read-only by design: there is no setter (studio.el:66). */ +el_val_t cgi_principal(void) { return EL_STR(_el_cgi_principal ? _el_cgi_principal : ""); } +el_val_t cgi_network(void) { return EL_STR(_el_cgi_network ? _el_cgi_network : ""); } +el_val_t cgi_engram(void) { return EL_STR(_el_cgi_engram ? _el_cgi_engram : ""); } diff --git a/lang/runtime/el_runtime.h b/lang/runtime/el_runtime.h index 7a445b2..c7a3c11 100644 --- a/lang/runtime/el_runtime.h +++ b/lang/runtime/el_runtime.h @@ -1017,6 +1017,15 @@ el_val_t stdout_to_file(el_val_t path); el_val_t stdout_restore(void); el_val_t el_mem_check(void); +/* Semantic retrieval surface. NOT interchangeable with engram_search_json, + * which is lexical by design — see the note at the definition. */ +el_val_t engram_recall_json(el_val_t query, el_val_t limit); + +/* CGI identity accessors (read-only). */ +el_val_t cgi_principal(void); +el_val_t cgi_network(void); +el_val_t cgi_engram(void); + #ifdef __cplusplus } #endif