self-review 2026-08-02: bound the WM breakthrough storm; stop punishing semantic relevance for recency
Working memory was thrashing behind a healthy-looking gauge. wm_active sat
at 22-24 while breakthroughs ran 661-903 and evictions 485-717 PER 60s tick
- roughly 825-1125 nodes cycling in 5-call lockstep.
Root cause: the breakthrough path was an anti-starvation mechanism that reset
its own counter on firing, with no budget and no refractory. A node failing
its type threshold 5 times was force-promoted at exactly 0.10 and had its
suppression_count reset to 0, so it immediately restarted the identical
climb. Since BREAKTHROUGH_WEIGHT (0.10) > WM_FLOOR (0.05), every one of them
cleared the admission floor and entered the rank contest tied at 0.10, where
the tie-break degenerated to node-array index order. Cap-evicted nodes are
skipped by retrieval reinforcement, so they never got an access_ts record and
the STI inhibition-of-return damper never applied to them. That closed the
loop: re-suppressed, completely unmarked, forever.
An anti-starvation rule that resets its own counter without a bound is not a
fairness valve, it is an oscillator.
Fixes in engram_activate Pass 2:
- ENGRAM_BREAKTHROUGH_BUDGET (WM_CAP/4 = 6) caps intrusive thoughts per call.
- ENGRAM_BREAKTHROUGH_COOLDOWN (55) via NEGATIVE suppression_count. The field
already serializes as %d and parses through eg_get_int_field, so negatives
round-trip through snapshots with no struct or format change.
- Blocked breakthroughs no longer reset the counter; it saturates so a starved
node surfaces on a later call instead of restarting from zero.
- Graded breakthrough weight by nearness to own threshold, so the rank
tie-break is cognitive rather than insertion order. Invariant preserved:
WM_FLOOR < weight < min(type_threshold).
Also: moved the additive cosine term AFTER the STI multiplier. It was applied
before, so an incumbent re-reached 30s later took t_n/(t_n+120) = 0.2x, which
cut the semantic term's ceiling from 0.20 to 0.04 - below every per-type
threshold. Meaning-match was being punished for having been recently useful.
Inhibition-of-return should rotate the structural score, not the semantic one.
Also: _eg_act_wm_evicted counted 3 of 5 eviction paths. The two carry-over
paths were silent, so the reported rate was an undercount of unknown
magnitude - while being used to diagnose an eviction pathology. All five now
increment.
Also: route_sync returned {"nodes":[],"edges":[]} when the snapshot export
failed. The soul's sync_ok check only tests for "" and "{}", so that
placeholder passed as a healthy sync: last_sync_ok_ts stamped, sync_age_ms
green, sync_empty never fired, added:0 forever. A broken sync was
indistinguishable from a quiet healthy one - the exact class this route was
added to fix. Returns a real error now.
Verified live (boot 20 vs boot 19): breakthroughs 661-903 -> 36/tick,
evictions 485-717 -> 12-46/tick against a counter that now covers more paths,
wm_active unchanged at 22-24, wm_avg_weight 0.138-0.273 -> 0.186-0.446.
Working memory is holding strong nodes instead of breakthrough-floor filler.
This commit is contained in:
@@ -5633,6 +5633,43 @@ void el_cgi_init(el_val_t name, el_val_t dharma_id, el_val_t principal,
|
||||
* exceed the floor, so naturally-promoted nodes survive multiple decay cycles.
|
||||
* Invariant maintained: BREAKTHROUGH_WEIGHT < min(type_thresholds). */
|
||||
#define ENGRAM_BREAKTHROUGH_WEIGHT 0.10
|
||||
/* ENGRAM_BREAKTHROUGH_BUDGET / ENGRAM_BREAKTHROUGH_COOLDOWN (2026-08-02
|
||||
* self-review): the breakthrough path was an unbounded, self-resetting loop.
|
||||
* Every reached node failing its type threshold incremented suppression_count;
|
||||
* on the 5th failure it was force-promoted at exactly 0.10 AND had its counter
|
||||
* reset to 0 — so it re-entered the identical cycle immediately. Because
|
||||
* BREAKTHROUGH_WEIGHT (0.10) > WM_FLOOR (0.05), all of them cleared the
|
||||
* absolute floor and entered the rank contest tied at 0.10, where all but a
|
||||
* handful were evicted by ENGRAM_WM_CAP. Evicted nodes get no access_ts
|
||||
* record (Pass 6 skips wm_weights<=0), so the short-term inhibition-of-return
|
||||
* damper at ENGRAM_STI_TS never applied to them and they were re-suppressed
|
||||
* completely unmarked. Steady state: N_suppressed/5 breakthroughs per call,
|
||||
* essentially all of them evicted the same call.
|
||||
*
|
||||
* Live telemetry 2026-08-02 (boot 19, uptime 23h48m): breakthroughs_delta
|
||||
* 661–903 and wm_evicted_delta 485–717 PER 60s heartbeat against wm_active
|
||||
* pinned at 22–24. At ~4 activate calls/minute that is ~165–225 breakthroughs
|
||||
* per call — ~825–1125 nodes cycling in 5-call lockstep. Working memory was
|
||||
* not remembering; it was thrashing, and the churn drowned genuine promotion.
|
||||
*
|
||||
* Two bounds, both required:
|
||||
* BUDGET — at most WM_CAP/4 (6) intrusive thoughts may surface per call.
|
||||
* Breakthrough is meant to be an occasional intrusive thought,
|
||||
* not a stampede; it can never again exceed a quarter of WM.
|
||||
* COOLDOWN — on breakthrough, suppression_count is set NEGATIVE rather than
|
||||
* 0, so the node needs COOLDOWN+SUPPRESSION_BREAKTHROUGH further
|
||||
* suppressions before it may surface again (~60 calls ≈ 15 min at
|
||||
* the current cadence, vs 5 calls ≈ 75s before). This is
|
||||
* inhibition-of-return applied to the breakthrough path, matching
|
||||
* the STI damper already applied to the natural path. Stored in
|
||||
* the existing int32_t field — serialized as %d and parsed via
|
||||
* eg_get_int_field, so negative values round-trip through
|
||||
* snapshots without a struct or format change.
|
||||
* When the budget or cooldown blocks a breakthrough, suppression_count is NOT
|
||||
* reset — it saturates, so a starved node surfaces on a later call rather than
|
||||
* restarting its climb. */
|
||||
#define ENGRAM_BREAKTHROUGH_BUDGET (ENGRAM_WM_CAP / 4)
|
||||
#define ENGRAM_BREAKTHROUGH_COOLDOWN 55
|
||||
/* ENGRAM_WM_CAP: hard limit on concurrent working-memory nodes (2026-06-30
|
||||
* self-review, porting fix from self-review 2026-06-26 branch). Without this,
|
||||
* broad curiosity seeds like "knowledge" promote 500+ nodes simultaneously —
|
||||
@@ -6032,7 +6069,12 @@ static int64_t _eg_embed_breaker_until = 0;
|
||||
* between) missed nearly every eviction/breakthrough event. Consumers wanting
|
||||
* rates keep the previous reading and diff. Restart legitimately resets to 0. */
|
||||
static int64_t _eg_act_breakthroughs = 0; /* forced promotions at the floor, cumulative */
|
||||
static int64_t _eg_act_wm_evicted = 0; /* Pass 4 over-cap evictions, cumulative */
|
||||
static int64_t _eg_act_wm_evicted = 0; /* ALL WM evictions, cumulative (see below) */
|
||||
/* 2026-08-02 self-review: this counted only the three Pass 4 / Pass 5 floor
|
||||
* and rank paths. The two carry-over eviction paths (base-level below τ, and
|
||||
* decayed weight below WM_FLOOR) were silent, so the reported eviction rate
|
||||
* was an undercount of unknown magnitude — which mattered precisely while
|
||||
* diagnosing the breakthrough storm. All five paths now increment. */
|
||||
|
||||
static int64_t engram_now_ms(void); /* defined in the store section below */
|
||||
|
||||
@@ -8006,6 +8048,8 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
|
||||
free(best_bg); free(best_hops); free(reached); free(seeds);
|
||||
free(fr); free(inhibition); free(cosq); return out;
|
||||
}
|
||||
/* Per-call breakthrough budget (2026-08-02) — see ENGRAM_BREAKTHROUGH_BUDGET. */
|
||||
int64_t bt_budget = ENGRAM_BREAKTHROUGH_BUDGET;
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
if (!reached[i] || best_bg[i] <= 0.0) continue;
|
||||
EngramNode* n = &g->nodes[i];
|
||||
@@ -8043,21 +8087,10 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
|
||||
if (inh > 1.0) inh = 1.0;
|
||||
double suppress = 1.0 - (1.0 - ENGRAM_INHIBITION_FACTOR) * inh;
|
||||
raw_wm *= suppress;
|
||||
/* Additive semantic-relevance term (2026-07-24, bl-b2d1c944):
|
||||
* shift-and-floor at S0 — nomic-embed scores unrelated pairs
|
||||
* 0.4–0.5, so raw cosine in a weighted sum would be a constant
|
||||
* bias swamping the decayed base-level signal. Above S0 the term
|
||||
* ramps 0 → WM_WEIGHT, breaking ties among structurally equivalent
|
||||
* candidates in favor of nodes that mean what the query means. */
|
||||
if (cosq && cosq[i] > ENGRAM_EMBED_S0) {
|
||||
raw_wm += ENGRAM_EMBED_WM_WEIGHT
|
||||
* (cosq[i] - ENGRAM_EMBED_S0) / (1.0 - ENGRAM_EMBED_S0);
|
||||
}
|
||||
/* Short-term inhibition-of-return (2026-07-25, Lebiere-Best):
|
||||
* damp by t_n/(t_n + t_s) where t_n = seconds since the most
|
||||
* recent recorded access (WM promotion / strengthen). Applied
|
||||
* after the cosine term so the WHOLE score rotates — a node that
|
||||
* just held a WM slot yields it even to semantically weaker
|
||||
* recent recorded access (WM promotion / strengthen). A node that
|
||||
* just held a WM slot yields it even to structurally stronger
|
||||
* competitors, and recovers as t_n grows. access_ts is recorded
|
||||
* at promotion, so persistent WM residents self-inhibit. Nodes
|
||||
* with no access history (never promoted) are uninhibited.
|
||||
@@ -8070,6 +8103,27 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
|
||||
if (sti_tn < 0.1) sti_tn = 0.1;
|
||||
raw_wm *= sti_tn / (sti_tn + ENGRAM_STI_TS);
|
||||
}
|
||||
/* Additive semantic-relevance term (2026-07-24, bl-b2d1c944):
|
||||
* shift-and-floor at S0 — nomic-embed scores unrelated pairs
|
||||
* 0.4–0.5, so raw cosine in a weighted sum would be a constant
|
||||
* bias swamping the decayed base-level signal. Above S0 the term
|
||||
* ramps 0 → WM_WEIGHT, breaking ties among structurally equivalent
|
||||
* candidates in favor of nodes that mean what the query means.
|
||||
*
|
||||
* MOVED AFTER the STI damper (2026-08-02 self-review). It used to
|
||||
* be added BEFORE, so the recency multiplier scaled the semantic
|
||||
* term too: an incumbent re-reached 30s later took t_n/(t_n+120)
|
||||
* = 0.2×, cutting the cosine term's ceiling from 0.20 to 0.04 —
|
||||
* below every per-type threshold (0.15–0.40). Meaning-match was
|
||||
* being punished for having been recently useful. Inhibition-of-
|
||||
* return should rotate the STRUCTURAL score (what the graph
|
||||
* dragged in), not the semantic one (what the query actually
|
||||
* means); relevance to the current query is not stale merely
|
||||
* because the node was in WM a moment ago. */
|
||||
if (cosq && cosq[i] > ENGRAM_EMBED_S0) {
|
||||
raw_wm += ENGRAM_EMBED_WM_WEIGHT
|
||||
* (cosq[i] - ENGRAM_EMBED_S0) / (1.0 - ENGRAM_EMBED_S0);
|
||||
}
|
||||
/* Threshold gate: must exceed per-type threshold to enter working
|
||||
* memory. Type threshold replaces the old flat 0.2 filter. */
|
||||
if (raw_wm >= type_threshold) {
|
||||
@@ -8077,14 +8131,41 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
|
||||
if (n->suppression_count > 0) n->suppression_count = 0;
|
||||
} else {
|
||||
/* Node didn't make it through — increment suppression counter.
|
||||
* After N consecutive suppressions: force breakthrough. */
|
||||
* After N consecutive suppressions it MAY force a breakthrough,
|
||||
* subject to the per-call budget and the negative-count cooldown
|
||||
* (2026-08-02 — see ENGRAM_BREAKTHROUGH_BUDGET/_COOLDOWN). */
|
||||
n->suppression_count++;
|
||||
if (n->suppression_count >= ENGRAM_SUPPRESSION_BREAKTHROUGH) {
|
||||
wm_weights[i] = ENGRAM_BREAKTHROUGH_WEIGHT;
|
||||
n->suppression_count = 0;
|
||||
if (n->suppression_count >= ENGRAM_SUPPRESSION_BREAKTHROUGH
|
||||
&& bt_budget > 0) {
|
||||
/* Graded breakthrough weight (2026-08-02): previously every
|
||||
* breakthrough landed on exactly ENGRAM_BREAKTHROUGH_WEIGHT,
|
||||
* so hundreds tied at 0.10 and the rank-cap tie-break at the
|
||||
* cutoff degenerated to node-array index order — i.e. whoever
|
||||
* was inserted earliest won, which is not a cognitive
|
||||
* criterion. Scale within ±10% by how close the node came to
|
||||
* its own threshold, so a near-miss outranks a node that was
|
||||
* nowhere near. Stays strictly below min(type_threshold)
|
||||
* (0.15) and strictly above ENGRAM_WM_FLOOR (0.05), which is
|
||||
* the invariant ENGRAM_BREAKTHROUGH_WEIGHT documents. */
|
||||
double near = (type_threshold > 0.0)
|
||||
? (raw_wm / type_threshold) : 0.0;
|
||||
if (near < 0.0) near = 0.0;
|
||||
if (near > 1.0) near = 1.0;
|
||||
wm_weights[i] = ENGRAM_BREAKTHROUGH_WEIGHT
|
||||
* (0.9 + 0.2 * near);
|
||||
/* Negative = cooldown. Must climb back through the cooldown
|
||||
* before it can breach again. */
|
||||
n->suppression_count = -ENGRAM_BREAKTHROUGH_COOLDOWN;
|
||||
bt_budget--;
|
||||
_eg_act_breakthroughs++;
|
||||
} else {
|
||||
wm_weights[i] = 0.0;
|
||||
/* Budget-starved or cooling down: do NOT reset the counter —
|
||||
* let it saturate so the node surfaces on a later call rather
|
||||
* than restarting its climb from zero. Cap the ceiling so the
|
||||
* int32 cannot drift unbounded over a long uptime. */
|
||||
if (n->suppression_count > ENGRAM_SUPPRESSION_BREAKTHROUGH * 4)
|
||||
n->suppression_count = ENGRAM_SUPPRESSION_BREAKTHROUGH * 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8217,6 +8298,7 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
|
||||
if (B < ENGRAM_BLL_TAU) {
|
||||
cn->working_memory_weight = 0.0;
|
||||
cn->wm_anchor = 0.0; /* keep anchor coherent with eviction */
|
||||
_eg_act_wm_evicted++; /* was uncounted before 2026-08-02 */
|
||||
} else {
|
||||
double keep = 1.0 / (1.0 + exp(-(B - ENGRAM_BLL_TAU)
|
||||
/ ENGRAM_BLL_S));
|
||||
@@ -8233,6 +8315,7 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
|
||||
if (w < ENGRAM_WM_FLOOR) {
|
||||
cn->working_memory_weight = 0.0;
|
||||
cn->wm_anchor = 0.0;
|
||||
_eg_act_wm_evicted++; /* was uncounted before 2026-08-02 */
|
||||
} else {
|
||||
cn->working_memory_weight = w;
|
||||
}
|
||||
@@ -8303,6 +8386,7 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
|
||||
}
|
||||
n->working_memory_weight = 0.0; /* evict: over global cap */
|
||||
n->wm_anchor = 0.0; /* keep anchor coherent */
|
||||
_eg_act_wm_evicted++; /* was uncounted before 2026-08-02 */
|
||||
}
|
||||
}
|
||||
/* If malloc failed, skip — WM over cap this call, no data corruption. */
|
||||
|
||||
Reference in New Issue
Block a user