M-INTEROCEPTION P1: two-threshold consolidation layer (ENGRAM_CONSOLIDATION, default OFF)
The co-activation accrual already works (hebb is an EWMA over co-firing); what
was missing is the promotion layer from design §9 that turns accrual into
durable structure. This adds it on top, entirely behind ENGRAM_CONSOLIDATION so
the OFF path is byte-identical to trunk.
CONNECTION threshold ("connection IS consolidation"): when a strongly-firing
InternalStateEvent is created (salience >= ENGRAM_CONSOL_CONN_MIN, default 0.6),
wire hebbian-associate edges from it to the top-K working-memory nodes active at
that instant (ENGRAM_CONSOL_WM_TOPK, default 5), provenance-tagged
"consolidated-from-ISE" and dedup-guarded. A sub-threshold ISE forms nothing (a
shower thought) and drifts out at the existing 48h prune.
PERMANENCE threshold (rare): engram_consolidate_permanence(node) marks a node
whose rehearsed ACT-R base-level clears ENGRAM_CONSOL_PERM_MIN durable via a
reversible metadata marker; engram_prune_telemetry then exempts it (gated, so no
trunk node is ever affected). Idempotent, no double-promote.
Thresholds are env-tunable for A/B without a rebuild.
MEASURED on a copy (throwaway HOME):
- Headline accrual curve (flag OFF, pure trunk) over N co-activations of a wired
pair — hebb_max: N=1 -> 1e-4 (=ETA), N=100 -> 0.010, N=1625 -> 0.150
(LINK_MIN, where an unwired pair consolidates), N=3000 -> 0.259; tracks the
analytic EWMA 1-0.9999^N within measurement noise (co-activation P~1).
- Strong ISE wired 2 edges to exactly the wm_top nodes (hebb-a, hebb-b); weak
ISE formed 0; edges carry the reversible provenance marker.
- Promoted node survived the 48h prune (2->1 nodes); ephemeral ISE swept.
- Flag OFF: ISE creation added 0 edges, permanence returned 0 (no-op).
ASan+UBSan clean.
This commit is contained in:
@@ -7914,6 +7914,36 @@ el_val_t engram_text_health_json(void) {
|
||||
return el_wrap_str(el_strdup(buf));
|
||||
}
|
||||
|
||||
/* ── M-INTEROCEPTION P1: two-threshold consolidation (ENGRAM_CONSOLIDATION,
|
||||
* default OFF) ───────────────────────────────────────────────────────────
|
||||
* Design §9. "Connection IS consolidation." This is a promotion LAYER on top
|
||||
* of the already-working co-activation accrual (see ENGRAM_HEBB_* above); it
|
||||
* does not change how hebb accrues. Two thresholds:
|
||||
* CONNECTION — when a strongly-firing InternalStateEvent is created, wire
|
||||
* hebbian-associate edges from it to the nodes that were in working memory
|
||||
* at that moment (its wm_top). Below the bar: no edges — a shower thought
|
||||
* that drifts out at the 48h ISE prune. (eg_consolidate_ise_connect)
|
||||
* PERMANENCE (rare) — a node whose rehearsed ACT-R base-level crosses the
|
||||
* higher bar is marked durable (metadata provenance "consolidated-from-ISE")
|
||||
* and is thereafter exempt from engram_prune_telemetry. Reversible: drop the
|
||||
* marker. Dedup-guarded (eg_edge_exists_between, no double-promote).
|
||||
* Every branch is inert unless ENGRAM_CONSOLIDATION is set → OFF path is
|
||||
* byte-identical to trunk. Thresholds env-tunable for A/B without a rebuild. */
|
||||
static int eg_consolidation_on(void){
|
||||
static int cached=-1;
|
||||
if(cached<0){ const char* s=getenv("ENGRAM_CONSOLIDATION"); cached=(s&&s[0]&&s[0]!='0')?1:0; }
|
||||
return cached;
|
||||
}
|
||||
static double eg_consol_conn_min(void){ const char* s=getenv("ENGRAM_CONSOL_CONN_MIN"); double v=s?atof(s):0.6; if(!(v>=0.0&&v<=1.0))v=0.6; return v; }
|
||||
static double eg_consol_perm_min(void){ const char* s=getenv("ENGRAM_CONSOL_PERM_MIN"); return s?atof(s):0.9; }
|
||||
static int eg_consol_wm_topk(void){ const char* s=getenv("ENGRAM_CONSOL_WM_TOPK"); int v=s?atoi(s):5; if(v<0)v=0; if(v>64)v=64; return v; }
|
||||
/* Durable marker is carried in node metadata (persisted, reversible) rather than
|
||||
* a new struct field, so it survives the store round-trip with no schema change. */
|
||||
static int eg_node_is_durable(const EngramNode* n){
|
||||
return n && n->metadata && strstr(n->metadata, "consolidated-from-ISE") != NULL;
|
||||
}
|
||||
static void eg_consolidate_ise_connect(EngramNode* ise);
|
||||
|
||||
el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label,
|
||||
el_val_t salience, el_val_t importance, el_val_t confidence,
|
||||
el_val_t tier, el_val_t tags) {
|
||||
@@ -7956,6 +7986,8 @@ el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label,
|
||||
engram_idmap_put(g, n->id, new_idx_full);
|
||||
engram_adj_on_node_added(g);
|
||||
if (engram_store_enabled()) eg_store_put_node(n);
|
||||
/* P1 CONNECTION threshold: self-gated (inert unless ENGRAM_CONSOLIDATION). */
|
||||
eg_consolidate_ise_connect(n);
|
||||
return el_wrap_str(el_strdup(n->id));
|
||||
}
|
||||
|
||||
@@ -8266,6 +8298,10 @@ el_val_t engram_prune_telemetry(el_val_t older_than_ms) {
|
||||
n->created_at < cutoff &&
|
||||
!(n->label && strcmp(n->label, "session-start") == 0) &&
|
||||
!(n->content && strstr(n->content, "self_review"));
|
||||
/* P1 PERMANENCE: a node promoted past the permanence bar is durable and
|
||||
* must not be swept by the 48h telemetry prune. Gated so the OFF path is
|
||||
* byte-identical (no trunk node ever carries the marker). */
|
||||
if (eg_consolidation_on() && prunable && eg_node_is_durable(n)) prunable = 0;
|
||||
if (prunable && removed < cap) {
|
||||
removed_ids[removed++] = n->id; /* keep id for edge sweep */
|
||||
free(n->content); free(n->node_type); free(n->label);
|
||||
@@ -8767,6 +8803,83 @@ static int eg_edge_exists_between(EngramStore* g, const char* a, const char* b)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* P1 CONNECTION threshold (design §9). When a strongly-firing ISE is created,
|
||||
* wire hebbian-associate edges from it to the wm_top nodes active at that
|
||||
* moment. Self-gated: returns immediately unless ENGRAM_CONSOLIDATION is set,
|
||||
* the node is an InternalStateEvent, and its salience clears the connection bar
|
||||
* — so a sub-threshold ISE forms nothing (a shower thought), which then drifts
|
||||
* out at the 48h prune. Edges are provenance-tagged "consolidated-from-ISE"
|
||||
* (reversible: deletable by that marker) and dedup-guarded via
|
||||
* eg_edge_exists_between. They accrue/decay through the normal hebb machinery
|
||||
* and are swept with their ISE at the 48h prune unless the ISE is promoted to
|
||||
* permanence. O(node_count) per qualifying ISE — same order as the
|
||||
* engram_prune_telemetry already run on ISE insert. */
|
||||
static void eg_consolidate_ise_connect(EngramNode* ise){
|
||||
if(!eg_consolidation_on()) return;
|
||||
if(!ise || !ise->node_type || strcmp(ise->node_type,"InternalStateEvent")!=0) return;
|
||||
if(ise->salience < eg_consol_conn_min()) return; /* below connection bar */
|
||||
int K = eg_consol_wm_topk();
|
||||
if(K<=0) return;
|
||||
if(K>64) K=64;
|
||||
EngramStore* g = engram_get();
|
||||
/* Select the top-K working-memory members by weight (excluding ISEs and
|
||||
* self). Bounded selection, no allocation. */
|
||||
int64_t best_idx[64]; double best_w[64]; int nb=0;
|
||||
for(int64_t i=0;i<g->node_count;i++){
|
||||
EngramNode* t=&g->nodes[i];
|
||||
if(t==ise) continue;
|
||||
double w=t->working_memory_weight;
|
||||
if(w<=0.0) continue;
|
||||
if(t->node_type && strcmp(t->node_type,"InternalStateEvent")==0) continue;
|
||||
if(nb<K){ best_idx[nb]=i; best_w[nb]=w; nb++; }
|
||||
else { int m=0; for(int j=1;j<nb;j++) if(best_w[j]<best_w[m]) m=j;
|
||||
if(w>best_w[m]){ best_w[m]=w; best_idx[m]=i; } }
|
||||
}
|
||||
int64_t now = engram_now_ms();
|
||||
for(int b=0;b<nb;b++){
|
||||
EngramNode* t=&g->nodes[best_idx[b]];
|
||||
if(eg_edge_exists_between(g, ise->id, t->id)) continue; /* dedup guard */
|
||||
engram_grow_edges();
|
||||
EngramEdge* ne=&g->edges[g->edge_count];
|
||||
memset(ne,0,sizeof(*ne));
|
||||
ne->id = engram_new_id();
|
||||
ne->from_id = el_strdup_persist(ise->id);
|
||||
ne->to_id = el_strdup_persist(t->id);
|
||||
ne->relation = el_strdup_persist("hebbian-associate");
|
||||
ne->metadata = el_strdup_persist("{\"origin\":\"consolidated-from-ISE\"}");
|
||||
ne->weight = ENGRAM_HEBB_LINK_W0;
|
||||
ne->hebb = ENGRAM_HEBB_ETA; /* nonzero so the trace can rehearse */
|
||||
ne->confidence= 1.0;
|
||||
ne->created_at= now; ne->updated_at= now; ne->last_fired= now;
|
||||
ne->layer_id = ENGRAM_LAYER_DEFAULT;
|
||||
g->edge_count++;
|
||||
engram_adj_on_edge_added(g, g->edge_count-1);
|
||||
eg_hebb_wb_push(ne->from_id, ne->to_id, ne->weight, ne->hebb);
|
||||
}
|
||||
}
|
||||
|
||||
/* P1 PERMANENCE threshold (design §9). Promote a node past the higher bar:
|
||||
* mark it durable (metadata provenance "consolidated-from-ISE") so the 48h
|
||||
* telemetry prune no longer sweeps it, and mirror to the durable store. The
|
||||
* caller (soul) decides WHEN to attempt promotion (rehearsal / re-activation);
|
||||
* this primitive enforces the bar on the node's ACT-R base-level and is
|
||||
* idempotent (no double-promote). Returns 1 if the node is durable after the
|
||||
* call, else 0. Reversible: clear the marker to demote. Inert unless
|
||||
* ENGRAM_CONSOLIDATION is set. */
|
||||
el_val_t engram_consolidate_permanence(el_val_t node_id){
|
||||
if(!eg_consolidation_on()) return (el_val_t)(int64_t)0;
|
||||
EngramNode* n = engram_find_node(EL_CSTR(node_id));
|
||||
if(!n) return (el_val_t)(int64_t)0;
|
||||
if(eg_node_is_durable(n)) return (el_val_t)(int64_t)1; /* already durable */
|
||||
double bl = engram_bll_base_level(n, engram_now_ms());
|
||||
if(bl < eg_consol_perm_min()) return (el_val_t)(int64_t)0; /* below permanence bar */
|
||||
free(n->metadata);
|
||||
n->metadata = el_strdup_persist("{\"origin\":\"consolidated-from-ISE\",\"durable\":true}");
|
||||
n->updated_at = engram_now_ms();
|
||||
if(engram_store_enabled()) eg_store_put_node(n);
|
||||
return (el_val_t)(int64_t)1;
|
||||
}
|
||||
|
||||
/* engram_temporal_decay — recency shaping on the activation path.
|
||||
*
|
||||
* MEASURED FAILURE (2026-08-05 self-review). Census of the live graph under
|
||||
|
||||
Reference in New Issue
Block a user