runtime: anchor the think read, so Neuron can think at all
El SDK CI - dev / build-and-test (pull_request) Failing after 13m24s
El SDK CI - dev / build-and-test (pull_request) Failing after 13m24s
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.
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user