self-review 2026-05-19: ACT-R WM persistence — decay non-reached nodes instead of zeroing

The activation persist step was writing wm_weight=0 for every node not reached
by the current BFS fan-out. This destroyed working memory accumulated by
MCP-layer activations within one tick of the awareness loop firing on an empty
inbox. ACT-R and Soar treat spreading activation as additive: absent seeds
contribute zero spread, not a zero override of existing WM state.

Fix: non-reached nodes now decay by ENGRAM_WM_DECAY (0.7) per activation call
rather than being immediately zeroed. A hard floor of 0.005 clears near-zero
values to prevent infinite decay tails. Reached nodes behave unchanged.
This commit is contained in:
2026-05-19 08:53:02 -05:00
parent ee1627c2c0
commit db7dae8236
+22 -5
View File
@@ -7077,16 +7077,33 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
}
/* Persist working_memory_weight (post Pass 3) to node store.
* Also emit ISE when a node transitions into working memory (was 0, now > 0.5),
*
* ACT-R semantics: spreading activation is additive. An absent spreading
* source contributes zero to the spread term but does NOT override existing
* base-level / WM state. Nodes not reached by this activation pass therefore
* retain their current working_memory_weight, decayed by ENGRAM_WM_DECAY
* (the per-turn attenuation factor). This replaces the prior behaviour of
* zeroing every non-reached node on each call, which destroyed WM state
* accumulated by MCP-layer activations within one tick of idle operation.
*
* Hard floor: weights decayed below 0.005 are cleared to 0.0 to avoid
* an infinite tail of near-zero values accumulating.
*
* Also emit ISE when a node transitions into working memory (was 0, now > 0.1),
* excluding InternalStateEvent nodes to prevent ISEs about ISEs. */
for (int64_t i = 0; i < g->node_count; i++) {
EngramNode* wn = &g->nodes[i];
double prev_wm = wn->working_memory_weight;
/* Nodes not participating in this pass: decay rather than zero. */
if (!reached[i] || best_bg[i] <= 0.0) {
if (prev_wm > 0.0) {
double decayed = prev_wm * ENGRAM_WM_DECAY;
wn->working_memory_weight = (decayed < 0.005) ? 0.0 : decayed;
}
continue;
}
wn->working_memory_weight = wm_weights[i];
/* ISE: working memory promotion — on 0→>0.1 transition, non-ISE nodes.
* Threshold lowered from 0.5 to 0.1 so quieter promotions are observable.
* Previously only very strong promotions (wm_weight>0.5) fired an ISE,
* leaving most activations invisible in the state-event log. */
/* ISE: working memory promotion — on 0→>0.1 transition, non-ISE nodes. */
if (prev_wm <= 0.0 && wm_weights[i] > 0.1 &&
wn->node_type && strcmp(wn->node_type, "InternalStateEvent") != 0) {
char ise_buf[512];