diff --git a/lang/runtime/el_runtime.c b/lang/runtime/el_runtime.c index 47917c4..b8eff06 100644 --- a/lang/runtime/el_runtime.c +++ b/lang/runtime/el_runtime.c @@ -13937,7 +13937,40 @@ static int eg_cog_is_keystone_seeds(const char* csv) { el_val_t engram_think_json(el_val_t seeds, el_val_t faculty) { GeoDescriptor* g = eg_geo_build_desc(EL_CSTR(seeds)); if (!g) return eg_geo_err("geometry unavailable"); - CogStance st; cog_stance_init(&st, NULL, EL_CSTR(faculty), g->hub_id, NULL, g); + /* RESUME THE LEARNED STANCE (2026-08-16 self-review). This built a NEUTRAL + * stance every call — all axis_gain 1.0, bias_dir NULL, reliability 0.5 — + * and never loaded the one the correspondence-beat had been persisting. + * + * That mattered because the faculty enters engram_think ONLY through the + * stance: `gain = stance->axis_gain[k]` warps the per-axis extents, and + * `stance->bias_dir` seeds the steering direction. cog_stance_init stores + * the faculty NAME but nothing reads it. So with a neutral stance, + * reason / abduce / induce / plan / analogize are the same function with + * different labels — measured, byte-identical output across all five — + * and `confidence` is pinned to the 0.5 uninformed prior, because + * GeoGradient.confidence is just stance->reliability. + * + * The machinery already existed and only this call site ignored it: + * engram_correspondence_beat_json resumes via cog_stance_from_node and + * persists via cog_stance_to_node under the id "stance--". + * Every beat's calibration was being written and then thrown away on the + * next read. Same defect as the NULL anchor directly above: a neutral + * argument collapsing a capability to a constant. + * + * Resume the same id the beat writes, so learning compounds across beats + * and cold boot. Fall back to neutral only when no stance exists yet — + * which is a genuine uninformed prior, not a discarded informed one. */ + char sid[256]; + snprintf(sid, sizeof sid, "stance-%s-%s", + EL_CSTR(faculty) ? EL_CSTR(faculty) : "reason", + g->hub_id ? g->hub_id : "region"); + CogStance st; StoreNode prev; int resumed = 0; + if (g_engram_store && store_get_node(g_engram_store, sid, &prev) == 1) { + if (cog_stance_from_node(&prev, &st) == 0) resumed = 1; + store_node_free(&prev); + } + if (!resumed) cog_stance_init(&st, sid, EL_CSTR(faculty), g->hub_id, NULL, g); + else { free(st.id); st.id = strdup(sid); } GeoGradient grad; /* ANCHOR THE READ (2026-08-16 self-review). This passed NULL, and NULL is @@ -13999,8 +14032,13 @@ el_val_t engram_think_json(el_val_t seeds, el_val_t faculty) { 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); + /* stance_resumed distinguishes an INFORMED read from an uninformed one. + * Without it, confidence 0.5 from a learned-but-unreliable stance and + * confidence 0.5 from "no stance exists" are indistinguishable — the same + * reporting gap that let the NULL anchor and the neutral stance hide. */ + snprintf(t, sizeof t, "{\"faculty\":\"%s\",\"n_support\":%d,\"magnitude\":%.6g,\"spread\":%.6g,\"confidence\":%.6g,\"stance_resumed\":%s,\"dim\":%d", + EL_CSTR(faculty), grad.n_support, grad.magnitude, grad.spread, grad.confidence, + resumed ? "true" : "false", grad.dim); jb_puts(&b, t); int emit = grad.dim < 8 ? grad.dim : 8; jb_puts(&b, ",\"direction\":"); eg_geo_emit_vec(&b, grad.direction, emit);