Decay, reflection, and reinforcement
How ranking evolves with age and usage — per-kind decay, episode consolidation into distilled facts, and usefulness-driven boosts.
Storing and retrieving memories is table stakes. What makes a memory corpus improve with time is a set of feedback loops: old noise fades from the top of results, clusters of related episodes consolidate into single durable facts, and records that prove useful in practice surface earlier. zero-memory implements all three as ranking-time mechanics on top of the same add-only store.
One invariant governs everything on this page: decay is demotion in
ranking, never invalidation of content. No memory dies of old age. Durable
kinds — gotcha, convention, decision, preference — decay slowly or
not at all, and there is no age-based auto-invalidation for any kind.
Decay by kind
A months-old episode should not outweigh a fresh convention, but until decay
existed, age barely influenced score. Now every kind has a half-life profile,
and search_memories (which also powers briefings) multiplies each
candidate's score by an exponential age factor derived from its kind:
episode— fast decay (a 30-day half-life);task/open-question— a 14-day half-life, deliberately aligned with the briefing's soft TTL (below);fact— a one-year half-life: facts go stale faster than judgments;gotcha— about three years;preference/reference— about five years;decision/convention— about ten years: the slow group, since a decision's why rarely expires on its own.
The multiplier is applied at ranking time, not as a filter: a direct query still finds an old episode — it just no longer crowds out fresher, more durable knowledge in top-k results.
Half-life profiles live in a ranking_config table in the database rather
than in application code, so ranking can be tuned without a release.
The table is readable by authenticated clients; writes go through migrations
or the service role only.
The fusion side of ranking is data too: the parameters of hybrid search —
the per-leg candidate pool size, the rank-fusion constant, and the leg
weights — live in a single-row fusion_config table under the same access
rules. Values change only after they beat the current ones on the
retrieval-eval harness, never ad hoc.
Soft TTL for open loops
Open loops surface oldest-first in briefings,
which is only safe if abandoned loops eventually stop resurfacing. Loops
older than a configurable threshold (default 14 days, via the
zm.open_loop_ttl_days database setting) drop out of the briefing's
open_loops section — but remain live memories: they still appear in
recall, in the dashboard feed, and close_loop still works on them.
Index hygiene
Decay and consolidation multiply invalidated rows over time, so the hot search indexes — the HNSW vector index and the full-text index — are partial indexes covering only non-invalidated rows. History grows; search does not feel it.
Seeing it
The insights dashboard has a "How it ages" section: median age of the live corpus, the faded share (records older than their kind's half-life, i.e. ranking weight below 0.5), and age buckets from a week to over a year.

Reflection: consolidating episodes
Related episodes accumulate around every piece of sustained work. Reflection turns each such cluster into one living fact:
- Detection runs inside the hygiene cycle (same triggers: scheduler
tick,
scan_hygiene, dashboard button). A cluster is a group of at least three episodes in one scope that share an entity and sit in a cosine band of "similar but not duplicates". The shared-entity requirement is load-bearing — without it, the similarity band alone merges unrelated episodes into one blob. - Distillation. An LLM distiller condenses the cluster into a single
consolidated fact (kind chosen from content, typically
factorconvention) withderived_fromlinks to every source episode. - Approval. The distillate is never written automatically. It enters an approve / dismiss / snooze queue on the dashboard's reflections page; only an approved distillate becomes a memory.

Source episodes are not invalidated by approval. They fade naturally
under episode decay while the distillate — fresher and more complete — wins
ranking on merit. The derived_from edges keep full
provenance traceable.
Usage reinforcement
recall results that agents mark as actually useful generate a usefulness
signal (see recall quality). Reinforcement
wires that signal into ranking:
- The hygiene cycle precomputes per-memory multipliers into a dedicated
memory_reinforcementtable. Search reads only the multipliers — the raw usage-event stream stays inaccessible to query paths. - Directly reported usefulness counts at full weight; judge-attributed usefulness counts weighted by its confidence (0.6 and up).
- Boosts follow a smoothed curve capped at 1.4 — no memory can ride reinforcement to unbounded rank.
- Facts promoted into standing rules are demoted (×0.6): a fact the agent already receives through the rules channel does not need a high retrieval rank too.
- Misled evidence demotes. The signal has a negative half: an explicit
challenge(see the false-invalidation safeguards) counts at full weight, and judge-attributed misled verdicts count weighted by confidence (0.6 and up). The demotion mirrors the boost curve and is floored at 0.5 — a memory that misled its readers loses rank but is never buried, because retirement must stay adjudicable through the review queue. - There is deliberately no demotion for surfaced-but-unused memories: only positive proof of use and explicit misled evidence move ranking, so mere silence cannot bury knowledge.
Misled evidence also feeds a review trigger: a memory whose qualifying misled signals cross a threshold within the window is queued as a stale suspect — a single-subject dispute flagged for review, never auto-invalidated. A dismissed suspicion is only re-raised by evidence newer than the dismissal.
World-fact freshness
Decay ranks by age; freshness tracks something different — when a memory was
last checked against the world it describes. Only the fast-moving layer
participates, identified deterministically by scope and kind: core-scope
memories of kind fact or reference, whose truth lives outside the store
(library versions, ecosystem practice, API behavior). Conventions and
preferences never expire on a timer — their oracle is you, not a document.
- Stale marker on recall. A fast-layer hit past its per-kind freshness
budget (180 days for a fact, 90 for a reference; a never-checked memory
counts from its own creation) carries a
stale_daysfield. It is a marker, not a verdict: the fact still stands, it just has not been re-checked lately. An agent that uses such a hit is asked to verify it against current docs in passing — and either confirm it (re-stamping the check) or write the corrected version withsupersedes, orchallengeit. - External re-verification. The scheduled hygiene sweep re-checks a small, bounded batch of due fast-layer memories against the live web, using the model provider's server-side web search. A check has three honest outcomes: current stamps the freshness ledger; unverifiable stamps it too, so the web is not asked the same unanswerable question every night; outdated raises a single-subject review dispute carrying what changed and the best supporting source — the check never supersedes a memory on its own, and an inconclusive (low-confidence) check records nothing at all. Owners whose configured provider has no server-side web search are skipped, and the skip itself is recorded — an unchecked record must look unchecked, never checked.
Design notes
- Reinforcement shipped last, behind a gate. Decay and reflection came first; the usefulness signal was allowed to accumulate for a long period before it influenced ranking, and wiring was validated on a held-out retrieval benchmark — retrieval quality did not regress, and the share of reinforced records in top-k stayed low enough to preserve an exploration floor for new, not-yet-reinforced facts.
- Pure exponential decay, no grace plateau. Long half-lives on protected kinds approximate a plateau where one is wanted, and a single curve keeps the ranking function analyzable.
- Config in the database on purpose. Ranking constants embedded in SQL function bodies demonstrably drift when functions are recreated; a seeded config table read via join makes tuning explicit, auditable, and release-free.