From ded6ca546f40128e6b216e466c6a350bd2803ee7 Mon Sep 17 00:00:00 2001 From: Neuron Date: Sun, 16 Aug 2026 11:25:24 -0500 Subject: [PATCH] runtime: anchor the think read, so Neuron can think at all MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit engram_think_json passed NULL as the anchor. NULL is not "no opinion": engram_think re-origins at `anchor ? anchor : region->centroid`, so NULL means "read from the centroid" — and the centroid is the one point where the gradient is zero by construction. r = x - centroid = 0, so every axis projection is 0, grad is 0, and direction takes the "at rest" branch at engram_cognition.c:137. Measured consequence: EVERY faculty returned an identical null result, differing only in its label — {"direction":[0,0,0,0,0,0,0,0],"spread":0,"magnitude":1,"confidence":0.5} magnitude 1 is membership evaluated at the centroid, spread 0 is its distance to itself, confidence 0.5 is the stance fallback. The geometry was never at fault: /api/drift computes real values (centroid_sep 0.104, core_disp 0.045) over the very same 87 members. Neuron could not think because the read was always taken from the region's own centre. The seeds choose WHICH region; they must also supply the VANTAGE. Anchor at the first resolvable embedded seed — the same seed eg_geo_build_desc infers dim from, so the two can never disagree. One seed still yields a real gradient because the descriptor expands to that seed's neighbourhood, so the seed's position is distinct from the neighbourhood centroid. The vector is COPIED, never borrowed: g->nodes is realloc'd in place on append, so a borrowed EngramNode* dangles across any concurrent write. Verified against a clone of the production store (13,616 nodes / 37,865 edges): self anchor n_support 87 magnitude 0.00282 spread 18.79 values hub n_support 28 magnitude 0.00318 spread 17.72 with distinct unit direction vectors. Previously both returned the zero vector with magnitude 1 and spread 0. STILL OPEN, now isolated by this fix: all five faculties return identical numbers and confidence stays 0.5, because cog_stance_init is passed NULL for the stance and the faculty enters the computation only through the stance's axis_gain[] and bias_dir. The faculty label is inert until a stance is loaded — which is what learn()'s correspondence-beat calibrates. Same shape as this bug: a neutral parameter collapsing a capability to a constant. --- lang/runtime/el_runtime.c | 60 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/lang/runtime/el_runtime.c b/lang/runtime/el_runtime.c index 199fa90..3971fad 100644 --- a/lang/runtime/el_runtime.c +++ b/lang/runtime/el_runtime.c @@ -13775,7 +13775,65 @@ el_val_t engram_think_json(el_val_t seeds, el_val_t faculty) { if (!g) return eg_geo_err("geometry unavailable"); CogStance st; cog_stance_init(&st, NULL, EL_CSTR(faculty), g->hub_id, NULL, g); GeoGradient grad; - if (engram_think(g, NULL, &st, &grad) != 0) { cog_stance_free(&st); engram_geo_free(g); return eg_geo_err("think failed"); } + + /* ANCHOR THE READ (2026-08-16 self-review). This passed NULL, and NULL is + * not "no opinion" — engram_think re-origins at `anchor ? anchor : + * region->centroid`, so NULL means "read from the centroid", and the + * centroid is the ONE point where the gradient is zero by construction: + * r = x - centroid = 0, so every axis projection is 0, grad is 0, and + * direction takes the "at rest" branch. Measured consequence: EVERY + * faculty — reason, abduce, induce, plan, analogize — returned an + * identical null result, differing only in its label: + * {"direction":[0,0,...],"spread":0,"magnitude":1,"confidence":0.5} + * magnitude 1 is membership evaluated at the centroid, spread 0 is its + * distance to itself, and confidence 0.5 is the stance fallback. The + * geometry was never the problem — /api/drift computes real values + * (centroid_sep 0.104, core_disp 0.045) over the very same 87 members. + * Neuron could not think because the read was always taken from the + * region's own centre. + * + * The seeds choose WHICH region; they must also supply the VANTAGE it is + * read from. Anchor at the first resolvable embedded seed — the same seed + * eg_geo_build_desc infers `dim` from, so the two never disagree. A single + * seed still yields a real gradient because the descriptor expands to the + * seed's neighbourhood (87 members for the self anchor), so the seed's own + * position is distinct from the neighbourhood centroid. + * + * COPY the vector, never borrow it: g->nodes is realloc'd in place on + * append, so a borrowed EngramNode* is a dangling pointer across any + * concurrent write. 768 floats is 3 KB. */ + float* anchor = NULL; + { + EngramStore* eg = engram_get(); + const char* csv = EL_CSTR(seeds); + if (eg && csv) { + const char* p = csv; + while (*p && !anchor) { + while (*p == ' ' || *p == ',') p++; + const char* s = p; + while (*p && *p != ',') p++; + const char* e = p; while (e > s && e[-1] == ' ') e--; + if (e > s) { + char* id = strndup(s, (size_t)(e - s)); + if (id) { + int64_t idx = engram_find_node_index(id); + if (idx >= 0 && idx < eg->node_count) { + EngramNode* n = &eg->nodes[idx]; + if (n->emb && n->emb_dim == g->dim) { + anchor = malloc(sizeof(float) * (size_t)g->dim); + if (anchor) memcpy(anchor, n->emb, + sizeof(float) * (size_t)g->dim); + } + } + free(id); + } + } + } + } + } + + if (engram_think(g, anchor, &st, &grad) != 0) { free(anchor); cog_stance_free(&st); engram_geo_free(g); return eg_geo_err("think failed"); } + free(anchor); JsonBuf b; jb_init(&b); char t[256]; snprintf(t, sizeof t, "{\"faculty\":\"%s\",\"n_support\":%d,\"magnitude\":%.6g,\"spread\":%.6g,\"confidence\":%.6g,\"dim\":%d", EL_CSTR(faculty), grad.n_support, grad.magnitude, grad.spread, grad.confidence, grad.dim);