53. Memory Weight Model v2: Reinforcement, Wired Edges, Recency¶
Date: 2026-10-13
Status: Accepted
Context¶
Issue #167: the Percept store's staleness/promotion behavior had never been re-evaluated against real usage. The review (measured against the real ~/.milk/memory/ corpus: 31 percepts, 16 global all at w=0.800, 15 session w=0.2–0.7, 0 edges) found the model was half-dead machinery:
- Re-confirmation was punished.
RecordreturnedDuplicateErrorfor near-duplicates — a fact restated by the user was rejected as an error instead of reinforcing the percept. Net effect:wonly ever decayed; nothing strengthened memory. - Edge propagation never fired.
applyEdgeswas real code but no production path ever constructed anEdge(0 in the corpus), whileinitialWeight's comment literally said inference percepts (born 0.7) "need edge boost to promote" (≥ 0.80) — i.e. local/escalation percepts could mathematically never be promoted, by a mechanism that didn't exist. - No staleness or recency concept.
Queryranked by token hits thenw; age never mattered. Session percepts decayed −0.10/session while global percepts never decayed at all. - A zombie band existed: inference percepts fell below
get_memory's defaultmin_confidence=0.4after ~3 consolidations but weren't pruned until ~5 — kept yet invisible. - Comments contradicted the constants ("−0.03" vs 0.10; "prune W <= 0" vs W ≤ 0.20).
Decision¶
All five forces on w are now explicit (docs/guides/operations.md "Memory weight model"):
- Birth weights unchanged (user 0.9, inference 0.7, system 0.4, core 1.0).
- Reinforcement replaces duplicate rejection (the B design): a near-duplicate (Jaccard ≥ 0.60) strengthens the existing percept —
w += ReconfirmBoost (0.05), capped atReconfirmCap,updated_attouched — instead of inserting a copy.Record/RecordGlobalreturn aRecordOutcome{ID, Reinforced, Similarity, Existing, W};DuplicateErroris gone. Reinforcement never lowersw(a percept already above the cap keeps its weight). The cap is0.90 = promoteThreshold + decayPerSessiondeliberately: promotion runs after the −0.10 session decay, so a fully re-confirmed inference percept (0.7 → 0.90 ×4) survives the decay and graduates at the next consolidation — repeated confirmation is a real promotion path, while a single echo can never mint a fully trusted fact. - Edges are wired (the user chose wiring over removal, the C decision):
Store.AddEdgevalidates and persists typed edges;record_memorygainedrelation(extends|updates|derives|contradicts) +related_to(ID/prefix/unique description) so the producer agent declares semantics — milk never fabricates relations. Consolidation applies them exactly as the old code did (+0.05 to the target of positive relations; −0.10 to both endpoints ofcontradicts), now on both the session pass and the global pass. Edges are stored in the "from" percept's store file and bite endpoints in that same file (cross-store edges are inert — honest and documented). Edges are idempotent. - Global decay (D): non-core (promoted) global percepts decay −0.02 per consolidation, floored at
pruneThreshold(0.20) — old facts sink in rank but are never deleted from global by decay (eviction stays size-based;/forgetstays the delete path).corepercepts (/learn) never decay anywhere. - Recency in recall (D):
Queryorders by token hits, thenw + recencyBonuswhere the bonus is0.10 × 0.5^(age_days/30)sinceupdated_at. Ordering only —min_confidencestill gates raww, so age never hides a fact, and a more relevant old fact always outranks a fresher also-ran.
Promotion now runs after the global pass, so freshly promoted percepts aren't decayed again in the same run. The zombie band is closed on the upside by reinforcement (re-stated facts recover past min_confidence) and made honest on the downside by the recency tie-break; thresholds moved to Params (config memory block, prepopulated like Otel/Rules/Permissions) so tuning needs no rebuild.
Consequences¶
Record/RecordGlobalsignature changed to(RecordOutcome, error);DuplicateError/IsDuplicateremoved — all call sites (/learn,record_memory, dispatch write-paths) updated./learnnow reports "already known — reinforced" instead of "skipped".- Two latent issues fixed by construction: comments now match the constants, and the promotion dead-end for inference percepts is gone.
- Validation is deterministic and pinned as regression tests (
model_validation_test.go): the exact reinforcement trajectory (0.7 → 0.75 → 0.80 → 0.85 → 0.90, promotion at consolidation), recency ordering (fresh wins ties; hits dominate; raw-wgating), global decay floor + core exemption, edge wiring both passes, and a corpus-effect run (envMEMORY_CORPUSruns it against a real store copy; synthetic mirror asserts trends — exact counts are float-noise sensitive around the prune threshold). - Deferred: staleness-driven expiry for global percepts (the floor at 0.20 means very old unconfirmed facts rank last but persist — deletion policy stays
/forget+ eviction); edge inference by the model without tool parameters (agents must declare relations); per-relation boost tuning. - The NREM metaphor is exactly that — a metaphor; no offline nightly pass exists (see the SWARMDEMO copy pass). Consolidation still runs at session end (
Consolidateat exit/new paths).