self-review 2026-08-15: consolidate the strongest Hebbian candidate, not the lowest-hash one
The link-formation scan walked candidate slots ascending and stopped at ENGRAM_HEBB_LINK_PER_CALL (2). Slot index is a hash of the node id pair, so whenever more than two candidates cleared LINK_MIN in the same call, the two consolidated were the two with the lowest hash and a stronger association waited - indefinitely, since the scan restarts from slot 0 every call while the leader decays at ENGRAM_HEBB_DECAY. Measured 08-13..08-15: hebb_cand_max peaked at 0.4963, 3.3x LINK_MIN, during a ~14h stretch of continuous qualification at the 2/call cap. Same defect the 2026-08-02 review named and fixed for breakthrough weights (index order is not a cognitive criterion), never carried across to the one path that writes permanent structure - and there is no pruning path, so growth is one-way. Selection pressure matters most where the result is irreversible. No-op when <=2 candidates qualify; picks the best when more do.
This commit is contained in:
@@ -9775,11 +9775,65 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
|
||||
}
|
||||
int64_t hebb_edge_cap =
|
||||
(int64_t)((double)g->edge_count * ENGRAM_HEBB_LINK_MAX_FRAC);
|
||||
for (int s = 0; s < ENGRAM_HEBB_CAND_SLOTS
|
||||
&& formed < ENGRAM_HEBB_LINK_PER_CALL
|
||||
&& hebb_edge_total < hebb_edge_cap; s++) {
|
||||
EgHebbCand* c = &_eg_hebb_cand[s];
|
||||
if (!c->a || c->score < ENGRAM_HEBB_LINK_MIN) continue;
|
||||
/* Select the STRONGEST qualifying candidates, not the first
|
||||
* ones in hash-slot order (2026-08-15 self-review).
|
||||
*
|
||||
* This loop used to scan slots ascending and stop at
|
||||
* ENGRAM_HEBB_LINK_PER_CALL (2). Slot index is
|
||||
* engram_id_hash(lo)*1000003 ^ engram_id_hash(hi) mod 8192 —
|
||||
* i.e. arbitrary with respect to association strength. So
|
||||
* whenever more than two candidates cleared LINK_MIN in the
|
||||
* same call, the two that got consolidated were the two with
|
||||
* the LOWEST HASH, and a stronger association simply waited.
|
||||
* It waited indefinitely, not just one round: the scan
|
||||
* restarts from slot 0 every call, so a low-slot candidate
|
||||
* that re-qualifies keeps winning the same race, while the
|
||||
* leader decays at ENGRAM_HEBB_DECAY the whole time.
|
||||
*
|
||||
* Measured on this store over the 08-13→08-15 window:
|
||||
* hebb_cand_max peaked at 0.4963 (08-14 06:57) — 3.3x
|
||||
* LINK_MIN — during a ~14h stretch when candidates were
|
||||
* qualifying continuously and links were being formed at the
|
||||
* ≤2/call cap. The system was consolidating the associations
|
||||
* it happened to reach first, while the association it had
|
||||
* most strongly learned sat unconsolidated.
|
||||
*
|
||||
* This is the same defect the 2026-08-02 self-review named
|
||||
* and fixed for breakthrough weights — "the tie-break at the
|
||||
* cutoff degenerated to node-array index order, which is not
|
||||
* a cognitive criterion" — but that fix was never carried
|
||||
* across to link formation, which is the one path that writes
|
||||
* PERMANENT structure. A wrong breakthrough costs one WM slot
|
||||
* for one call; a wrong consolidation is an edge that never
|
||||
* goes away (ENGRAM_HEBB_LINK_MAX_FRAC notes there is no
|
||||
* pruning path — growth is one-way). Selection pressure
|
||||
* matters most exactly where the result is irreversible.
|
||||
*
|
||||
* Cost: PER_CALL(2) x 8192 comparisons of a double, against an
|
||||
* O(edge_count) relation scan (37k+) immediately above and an
|
||||
* O(edge_count) eg_edge_exists_between per edge formed. Noise.
|
||||
*
|
||||
* Invalid winners (node deleted, edge already present) are
|
||||
* cleared and do NOT consume one of the two slots — same as
|
||||
* the old `continue`. Clearing strictly shrinks the candidate
|
||||
* set, so the retry loop always terminates. */
|
||||
while (formed < ENGRAM_HEBB_LINK_PER_CALL
|
||||
&& hebb_edge_total < hebb_edge_cap) {
|
||||
int best_s = -1;
|
||||
double best_score = 0.0;
|
||||
for (int s = 0; s < ENGRAM_HEBB_CAND_SLOTS; s++) {
|
||||
EgHebbCand* cs = &_eg_hebb_cand[s];
|
||||
if (!cs->a) continue;
|
||||
if (cs->score < ENGRAM_HEBB_LINK_MIN) continue;
|
||||
/* strict > keeps the lowest slot on an exact tie, so
|
||||
* selection stays deterministic across runs */
|
||||
if (best_s < 0 || cs->score > best_score) {
|
||||
best_score = cs->score;
|
||||
best_s = s;
|
||||
}
|
||||
}
|
||||
if (best_s < 0) break; /* nothing qualifies this call */
|
||||
EgHebbCand* c = &_eg_hebb_cand[best_s];
|
||||
if (engram_idmap_get(g, c->a) < 0 ||
|
||||
engram_idmap_get(g, c->b) < 0) { /* node gone */
|
||||
eg_hebb_slot_clear(c); continue;
|
||||
|
||||
Reference in New Issue
Block a user