Intent
Keep stale or low-value memory from dominating recall while allowing useful memory to remain reachable through repeated, evidenced use.
The problem
An append-only memory corpus grows without bound. Pure recency ranking buries durable facts; pure similarity keeps obsolete facts permanently competitive; hard TTL deletes knowledge on an arbitrary date. Naive reinforcement creates a different failure: frequently retrieved errors become stronger merely because the system keeps seeing them.
The pattern
Separate lifecycle strength from truth and relevance:
Diagram source
%% caption: three quantities kept apart — epistemic confidence, retrieval strength and retention policy — with time and use moving only the second
flowchart LR
A["Memory + evidence"] --> B["Epistemic confidence / trust"]
A --> C["Retrieval strength"]
A --> D["Validity and retention policy"]
E["Time passes"] --> C
F["Useful evidenced recall"] --> C
G["Correction or supersession"] --> B
G --> D
B --> H["Recall policy"]
C --> H
D --> HDecay only the dimension it is meant to control:
- Retrieval-strength decay lowers default reachability.
- Validity expiry marks a fact stale or out of date.
- Retention expiry authorizes deletion after policy checks.
- Epistemic confidence changes only when evidence changes.
Reinforcement should require a meaningful signal: successful task use, independent corroboration, explicit pinning, or operator feedback. Mere retrieval is at most a usefulness signal.
Protect verified, pinned, legally retained, and correction/tombstone records from ordinary pruning. Keep a reactivation path and enough audit history to explain why strength changed.
Say which kind of forgetting each step performs.
Memory research separates a trace that is gone from one that is intact
but no longer found, and the distinction maps onto code exactly: a
filter on reads is a retrieval failure, and a delete is forgetting. The
two need different guarantees. A retrieval failure should be reversible
and readable by id; forgetting should be a deliberate act with a record.
Counterparts keeps them apart.
Its nightly prune archives an episodic row below strength 0.02 after 90
lived days of dwell, and an archived row leaves every recall read but
stays readable by its own id. Removal is a separate owner verb with its
own append-only removal_record. roampal-core shows the collapse.
delete_memory sets status=archived,
restore() has no caller outside tests, and
cleanup_archived() hard-deletes every archived row on the
first access to the profile after a server start. An archive that reads
as retrieval failure becomes forgetting at the next restart.
Why it works
The corpus can forget operationally without pretending old means false. Durable truths remain recoverable, temporary details fade, and popularity cannot silently promote a claim into verified knowledge. The separate dimensions also make ranking and deletion policies testable.
Tradeoffs
Decay rates are domain policy, not universal constants. A short half-life is useful for transient tool state and harmful for stable preferences or safety constraints. Reinforcement creates feedback loops if the same ranker controls both exposure and strength. Soft decay still consumes storage; hard pruning loses reversibility and may violate audit or correction requirements.
For small bounded corpora, explicit archive and review may be simpler and safer than continuous scoring.
Cost to adopt
Build: a strength field separate from confidence, a decay function, and bounded reinforcement with a recorded reason.
Forces elsewhere: ranking becomes time-dependent, so results are no longer reproducible across runs and any benchmark must pin a clock. Reinforcement driven by retrieval creates a feedback loop that needs an explicit damper.
Ongoing: decay rates are per memory kind, and getting them wrong is invisible — memory that faded too fast produces no error, just a worse answer.
Skip it if your store is small. Decay solves a crowding problem you may not have.
Seen in the atlas
Read these first
- Daimon — per-type linear decay with an escalating boost for overdue open questions, applied to proactive recall and lidded by trust class.
- Helm — retrieval only slows loss,
log1p(access_count)subtracted from stale weeks, and only never-corroborated rows decay at all. - NOOA Memory — retrieval
bumps a
strengthcounter that slows Ebbinghaus decay and leavesconfidenceuntouched, and injected memories are not reinforced. - Graphify — a 30-day half-life on outcomes decides a contested verdict, while the corroboration band comes from a count of distinct results.
- memory-lancedb-pro — three injections never confirmed as used suppress a memory from auto-recall for thirty minutes, a negative signal fed only by the automatic path.
Every instance
Daimon applies its decay,
and every factor can be read off the scoring file. Proactive
recall multiplies FTS5 relevance by effective_weight, which
is importance/10 × tiered recency × per-type linear decay,
with rates from 0.005 a day for a strong belief to 0.05 for an active
topic (plugin/daimon_briefing/scoring.py). One type is
exempt from the usual conclusion: an open question past its fourteen-day
expected lifespan gets an escalating boost,
1 + overdue_days**1.5 / 100, capped at 3.0. For an open
loop, staleness means unresolved, not irrelevant, and
burying it is exactly wrong. Any system with a per-type decay table
should ask which of its types this applies to; most have at least
one.
Three smaller guards in the same file generalize. A
first_seen stamp more than 300 seconds in the future —
further than ordinary clock skew explains — is treated as
neutral rather than maximally fresh, so a teammate's
mis-stamped item cannot outrank local work — a bug that only exists once
memory crosses machines. Decay is floored at 0.1 rather than allowed to
reach zero, so ordering may bury an item but arithmetic never erases it.
And the finished weight is lidded by trust class after every
accumulation term — 0.7 for an inferred item, 3.0 for a verified
verbatim quote — so no combination of importance, recency and escalation
lifts an inferred claim into the band a verified one occupies.
Mnemopi shows the design this
page would ask for and then does not run it. weibull.ts
gives each of fourteen memory types a Weibull k (shape) and
eta (scale, in hours) — profile at
k=0.3/eta=8760, preference at 0.4/4380, fact
at 0.8/720, observation at 0.9/480 — and a k
below 1 is the heavy tail a single rate cannot express: a profile fact
that fades slowly and keeps fading slowly. It is the instrument
the Helm report asks for when it says
a preference should not fall down the ranking for being old while an
event should. But at the pinned commit the table's only importer is a
test. Recall ages every type alike: scoreCandidate in
beam/recall.ts multiplies the base score by
0.7 + 0.3 · exp(−age / 72 h), and the polyphonic temporal
voice scores exp(−age_days / 7) × importance; neither reads
the memory's type. Twenty-eight hand-set parameters, one of them —
commitment at k=1.0, exactly exponential — wrong on its own
terms, and none of them reaches a ranking. Before crediting a system
with per-type decay, find the read path that calls the curve.
Redis Agent Memory
Server has a composite retention policy worth copying and ships it
switched off. select_ids_for_forgetting combines TTL and
inactivity so a recently-used memory survives its nominal age
unless it exceeds a hard-age multiple (default 12×),
honours pinning and per-type allowlists, and prunes to a budget using a
recency composite with two half-lives — 7 days on last
access, 30 on creation. Separating "recently used" decay from "recently
learned" decay is the refinement to take from it. The periodic job that
runs it returns without scanning unless forgetting_enabled
is set, and that setting defaults to False, with the age,
inactivity and budget thresholds all defaulting to None
(V0/agent_memory_server/config.py:485-490).
Mercury makes the same
distinction in the schema rather than the policy:
confidence, importance, and
durability are three independent fields. How much a memory
matters and how long it should last are different questions, and one
column cannot answer both. Mercury also demotes on a clock: the
heartbeat's prune moves every active or
durable memory whose last_seen_at is more than
30 days old into a subconscious scope that ordinary recall
does not search, and recall promotes one back when too few conscious
matches clear a health threshold. The clock is not use — retrieval
stamps last_used_at, and only a merge or a promotion resets
last_seen_at — so a memory recalled every day still demotes
on schedule.
OpenViking computes hotness
as
sigmoid(log1p(active_count)) * exp(-ln2 · age / half_life)
and states plainly that it blends into search ranking. It never
touches correctness — the right side of the line. Its remaining risk is
the one this pattern names: active_count increments on
retrieval, so frequency is self-reinforcing, and a uniform 7-day
half-life applies to every memory kind.
Holographic is the
counterexample, and it is worth studying because each piece looks
reasonable alone. fact_feedback moves a single
trust_score by +0.05 or −0.10; that same score is
multiplied into relevance and gates retrieval at a
min_trust floor of 0.3. From a default of 0.5, three
unhelpful ratings put a fact below every default retrieval path, with no
tombstone, no record that suppression occurred, and nothing that
surfaces the fact for the rating that would bring it back. Feedback
became suppression because reachability and belief were the same
number.
Helix AGI documents finding
this loop and cutting it. A belief's mass originally included its
relation count; related beliefs get co-injected, and co-injection
creates more relations, so the comment in
memory/belief_store.py records the cycle it produced —
"relations → mass ↑ → gravity ↑ → co-injection → more
relations" — and the fix, which is to leave cluster gravity to
emerge from spatial density instead of inflating an individual score.
The general lesson is narrow and reusable: a reinforcement
signal that is itself caused by reachability is a feedback loop, not a
measurement, and the way to find one is to ask which of your
inputs the previous ranking already decided.
Verel is the reference for the
separation itself. The safest implementation shape is to keep
reinforcement as append-only events and derive the score, so a rule can
be changed and recomputed rather than baked irreversibly into a column.
Atomic Agent writes the events
and stops short of the derivation: applyVote stores the
clamped new score in the target row's vote_score column in
the same transaction that inserts the vote_events row, the
consolidator multiplies every stored score by a decay factor in place,
and vote_events is a feedback log FIFO-capped at
eventLogMaxRows, 50,000 by default
(src/memory/voting/vote-store.ts). The score cannot be
rebuilt from the log.
Memora sits on the correct side
of the line and still shows the hazard:
calculate_importance(created_at, base_importance, access_count)
is a ranking signal rather than a confidence, but retrieval increments
access_count, which raises the score, which makes future
retrieval more likely — the self-amplifying loop with no counterweight
visible.
LoongFlow wires a feedback
controller to reinforcement collapse and gives it the wrong sign. Its
evolutionary memory samples parents from a Boltzmann distribution,
exp((score − max) / T), at a temperature set from the
population's measured diversity — and the target temperature is
base_temp × 2 × diversity, clamped to
[0.5, 2.0] and blended 80/20 with the current value
(evolution/boltzmann.py:80-87). A population converging on
the same few solutions therefore gets a lower temperature and
greedier selection, which converges it further; only the clamp and a 0.2
chance per call of a uniformly random pick keep variety in. It is a
narrow instance — recall there feeds a search loop, not belief — and the
transferable point is the test nobody wrote: a controller meant to
loosen selection as diversity falls needs an assertion on its direction,
and here no test exercises the diversity function at all.
NOOA Memory closes the
reinforcement loop rather than noting it. Retrieval bumps a
strength counter that slows Ebbinghaus decay and leaves
confidence untouched — rehearsal is not belief — and its
paper adds the part that matters: "injected memories are not
reinforced, so what the harness surfaces does not distort the usage
signal". Spontaneous injection runs at a fixed cadence of about
one per turn and does not count as use. A system whose ranker reinforces
whatever the ranker chose to show is measuring its own decisions and
calling it usage; separating deliberate reads from injections is the
cheap fix, and it is a one-line distinction at the call site.
Helm keeps the two signals apart
correctly and then damages a third. Retrieval cannot raise confidence —
it only slows loss: log1p(access_count) is subtracted from
the count of stale weeks before the 0.9^weeks multiplier
applies, so a fact the agent keeps reaching for holds what it has and
gains nothing. Only never-corroborated rows decay at all, and rows
sourced from the persona document are exempt outright. That is the
separation this page asks for, at the cost of one logarithm.
The flaw is in the bookkeeping. The same pass advances
last_seen by the stale weeks it just consumed, purely so
the next nightly run does not re-apply an identical step. It works, and
it means last_seen no longer records when the fact was last
seen — including for the unsure query that orders the
weakly-evidenced list by exactly that column. If a decay pass needs to
remember what it has already done, give it last_decayed and
leave the observation timestamp alone.
Helm also shows the reinforcement signal being collected where nobody
would look for it: access_count is incremented inside the
recall verb, so the background loop that calls recall to score whether
its own thought is worth interrupting the owner about reinforces every
fact it touches on the way past. A read that writes should be documented
as one — Helm's tool registry declares
"side_effects": "none" for it.
Graphify uses decay for
something most instances here do not: deciding a
contradiction. Each outcome contributes
sign × weight where the sign is +1 for
useful and −1 for dead_end or
corrected, and the weight halves every 30 days. A source
carrying both signs is classified contested by the presence
of both — the classification is not a vote — and only then does the
accumulated score decide whether it reads as "useful", "dead end" or
"even". So a fresh dead end outweighs a months-old success without any
conflict-resolution logic existing at all: the half-life is the
resolution rule.
The reason it stays honest is that decay here never touches the
corroboration band. A decayed score cannot demote preferred
to tentative, because that classification comes from a
count of distinct results, not from the score. Age changes which
contested source sorts first; it cannot make a corroborated source
uncorroborated. That is the separation this pattern keeps asking for,
achieved by computing the two things from different columns rather than
by discipline.
One implementation detail worth stealing outright: the score is
rounded to nine digits before comparison, with a comment explaining that
C's pow can differ in the last ULP across platforms and
that the rounding is what keeps sort order and the contested verdict
stable. A decay function whose output feeds a classification
needs to be deterministic across machines in a way one that only feeds a
ranking does not.
memory-lancedb-pro
adds the signal this pattern usually lacks: a negative one that
is not just the absence of a positive. computeTier1Patch
counts injections that were never confirmed as used; three of them
suppress the memory from auto-recall for thirty minutes, and twenty-four
hours without an injection resets the counter on the stated reasoning
that "this memory is being needed again". The threshold is deliberately
not configurable and the comment explains the distinction — three
strikes is "a behavioral design choice that should hold across
deployments" while the windows around it are operational tuning. Only
the automatic injection path feeds the counters, so a memory the user
fetches deliberately never accrues strikes.
ClawMem is the cautionary
instance, and the measurement is committed. Its composite score blended
recency decay, content-type half-lives, co-activation reinforcement,
confidence, quality and revision count — and an offline eval against
hand-labelled gold found raw cosine ranking 16 of 19 judged targets
first (MRR 0.912) where the composite managed 1 of 19 (0.307), with the
composite's own minScore filtering 14 of 19 out. The
project's response was to demote every metadata signal to an
exact-tie-break on the direct routes. The generalisable argument is in
the file header: a decay-and-reinforcement blend is only safe where its
terms are smaller than the margin separating a right answer from a wrong
one.
The mirror of that rule is a decay signal, and it comes from
a paper rather than a repository. Weighted Memory Tree
(arXiv:2608.20631, 21
August 2026) revises a node's retention score from two events. One is
the recorded execution outcome of an action,
ω ∈ {success, failure} — outcome, not similarity. The other
is selection-based decay: a memory that was
eligible for the prompt and not chosen decays, while a chosen
one has its missed-selection count reset.
Set that beside NOOA's rule above and the selection event becomes two-sided. NOOA says do not let the ranker reinforce what the ranker chose to show. This says the ranker's rejections are the signal you were missing — a memory that kept being considered and passed over has been measured against its competition on live queries, which is a stronger statement about its worth than any decay constant fitted to the clock. Wall-clock decay asks how old a memory is; missed-selection decay asks how often it lost.
Two caveats belong with it. The signal is only as good as the candidate set: a memory that never becomes eligible never decays by this route, so it needs a floor from somewhere else. And it inherits the failure NOOA names in the other direction — if the eligibility filter is itself the ranker, the system is again grading its own decisions, and not-selected has to mean lost a comparison rather than was never a candidate.
This is a design to borrow, not a result to cite. The paper releases no code and no data, so nothing here was checked against an implementation; its reported gains are recorded on the benchmarks page as unverifiable. Its folding is worth the same note: completed branches fold into summaries while the system retains "access to folded context", and resumed branches reopen, so the compaction is reversible by construction — the property the corpus usually finds missing on the other side of a fold.
openvurp shows the
clock-plus-recall rule with one constant set where it cannot do its job.
A row fades only if it is older than 45 days, unrecalled for 45 days,
and recalled fewer than twice in its life; every
search that returns a row bumps its
access_count, and retrieval runs on every turn. So two
incidental appearances in a top-five — not two uses — make a memory
permanent, and a memory that was wrong twice can never fade. The same
store exempts the lesson, identity and
pact categories by name, and the fade itself is wired to
the platform's store alone, so the per-agent stores it was built for
never run it. Archive on fade is done right: every removed row is
appended to .faded/faded.jsonl with a timestamp before the
delete.
Claude
Self-Reflect reinforces on one clock and ages on two. Its
base curve is conservative by construction —
score * ((1 - 0.3) + 0.3 * 2^(-age_days / 90)), so at most
30% of a search score is time-dependent and a year-old perfect match
still scores above 0.7. Reinforcement moves the half-life rather than
the score: effective_half_life = base * 2^reinforcement,
where reinforcement is a sum of session outcomes, each weighted by a
30-day half-life, clamped to ±2, swinging the half-life between roughly
22 and 360 days. The second axis is the unusual one: an hourly pass
labels each conversation shipped or unreleased
with a releases_behind count walked from git tags, and
retrieval multiplies effective age by 25% per release behind, floored at
a quarter of the half-life — so a memory ages by how much has shipped
since, not only by how long ago it was. Git is consulted only by the
refresh; the read path does an indexed lookup and fails open to
neutral.
Membrane is where the sweep and the marker collide. Its decay is exponential per type — one hour for episodic, thirty days for a captured semantic observation, one day for the facts capture and consolidation derive — with a reinforcement gain field, a pinned flag and a three-value deletion policy, and the shape is right. The gain is zero on every record the tree creates, so reinforcing resets the decay clock and restores no salience. But the decay pass computes elapsed time from a timestamp only reinforcement advances, and applies the result to the already decayed stored salience, so after repeated sweeps the exponent grows with the square of the sweep count; at the shipped defaults an episodic record sits at 0.00098 after four hourly sweeps instead of 0.0625, and the half-life constant stops meaning what it says. No test applies the decay pass twice to the same record, which is why the suite reports green. The second collision is worse: retraction marks a record by setting salience to zero, and the prune pass deletes any unpinned auto-prune record at or below its floor — default zero — so the retraction marker is also the deletion trigger, and the audit rows cascade away with the record. If you drive pruning off a numeric floor, retraction has to set something else.
mini-AGI decays weights
rather than records, and its finding is about which signal to decay
on. An expert is deleted when the router has not chosen it
inside a trailing survival window — not when its gate is low, and the
source says why the obvious field is the wrong one: "The gate is not
merely uninformative about whether an expert will be wanted again, it is
anti-predictive: the smallest gates belong to the busiest experts. One
that behaves as a sink - chosen constantly, contributing little per
character - reads as dead on a gate test, while a high-gate expert
nothing has asked for in hundreds of thousands of texts reads as
alive." That is the strength-versus- importance confusion this page
separates, found empirically in a system that then removed the term. It
also meets the checklist item about not reinforcing what the system
chose to inject: training forwards give an under-used expert an
exploration bonus, and the prune clock counts only what the router would
have admitted without it, so a forced trial never resets the clock. A
newborn is exempt for a full survival window, so it cannot be judged
before it has competed. The limit is the one this atlas draws everywhere
for weights — staleness here is a fact about routing, not about truth,
so a rare memory and a dead one are the same signal, and
prune()'s docstring says so: "an expert which is
genuinely rare rather than dead is deleted, and deletion is
permanent."
Living Mind Cortex
inverts the pattern. Each memory is also a node in a RAM-only
heat-equation substrate that recall reheats and every tick cools, and a
node that stays below the freeze temperature for eight ticks is written
back to Postgres as a new semantic row at importance 0.90,
tagged identity (cortex/engine.py:482-541).
The service never wires edges between nodes, so heat never diffuses and
neglect alone decides promotion: the runtime's 30-second heartbeat lines
become permanent identity memories that the only prune exempts.
Reinforcement lives in a recall side effect that lowers confidence 5
percent per read, so the most-used rows are the least trusted.
ZenBrain ships the
decay functions and applies none of them to what it stores.
@zensation/algorithms exports FSRS retrievability,
Ebbinghaus curves, Hebbian strengthening and decay, and emotion-weighted
decay multipliers, each a pure function over state the caller holds. The
MemoryCoordinator stores facts with FSRS difficulty,
stability and next-review time and reads them only to fill a
spaced-repetition queue; recall ranks by cosine or lexical score alone,
and consolidate() reports pruned as a constant
0 (packages/core/src/coordinator.ts:403-444). The one decay
that runs is exp(-0.05 × minutes) on an in-process working
memory that dies with the process. A library of decay arithmetic is not
a decaying store: the question to ask of any design here is which read
path multiplies by the curve.
Cortex Hub shows what
reinforcement does when its credit is not attributed. A
quality-gate result from any agent increments completion or fallback on
the ten active knowledge documents whose updated_at falls
in the last hour, across every project
(apps/dashboard-api/src/routes/knowledge.ts:999-1023), and
every search sets updated_at on each over-fetched
candidate, so the credited set is whatever anyone searched. The same
search bumps the timestamp the ranker reads for recency, so exposure
refreshes its own recency term. The counters then decide a ranking term,
a deprecated flag that hides a document from the session
brief, and which documents an LLM rewrites.
Kimetsu binds
reinforcement to the revision the model was shown. A cited
memory gains a full point of usefulness after a successful run and a
retrieved-but-uncited one a tenth, decayed on a half-life from
last_useful_at. The citation event carries the claim
revision or exposure id, and apply_memory_cited credits
nothing when that revision is no longer current
(projector.rs:375-420). A text correction freezes the old
revision's counts in memory_revisions and resets the live
row's confidence, use count, citations and query routes, so a rewritten
claim starts without the old one's reputation
(projector.rs:2748-2828).
Engram MCP (edg-l)
decays on a clock of project activity rather than the calendar.
elapsed is the number of distinct days on which the
memory's project received a store after the memory was last accessed,
derived from created_at in the UPDATE itself
rather than kept as a counter (src/db/memories.rs:826-860;
src/db/activity.rs). Rows tagged hook do not
advance it, so a session of automatic capture ages nothing, and a
project nobody stores into does not decay however long it sits. The
ranking scorers read the same store-day axis, and the usage boost is
capped at 0.1 under a 1.0 ceiling, the value a pinned memory holds
(src/decay.rs:18-69).
Tigerless
agent-memory keeps the evidence for forgetting in the cache it tells
users to delete. Each sleep adds 0.5 to a memory's weight per
read since the last sleep, at most three, and subtracts 0.1 after 30
days without access. Weight multiplies the recall score and admits a
memory to the injected MEMORY.md index at 0.75
(manage.py:371-391, memory_md.py:10-31). A
memory at the 0.05 floor with no lifetime hits becomes a delete proposal
the host model may accept, three per pass
(manage.py:609-623). Reads and hits live in
access_log inside index.db, whose docstring
calls every table a cache, and mem rebuild drops the file.
Usage that decides removal is state; keep it beside the truth,
not in the projection.
dsh-auto-memory
forgets by hiding, and one read is a permanent pardon. With
staleAfterDays set, a memory never read and not updated
within the window drops out of the injected index while its file stays
listable. A single memory_read revives it, and
isStale exempts any memory with a non-zero read count from
then on (src/store.ts:222-229). Pinned memories never go
stale. The setting defaults to 0, and the eviction suite asserts that
the stale zero-read memory is the only one hidden among four classes,
with every file kept.
Cortex
(gambletan) decays on last access and never records an
access. Episode half-life is 72 hours plus 144 times base
salience, and consolidation archives an episode below 0.05 effective
salience (cortex-core/src/episode.rs:153-221,
cortex-core/src/consolidation.rs:288-318).
EpisodeStore::touch, which would refresh
last_accessed and raise access_boost, has no
caller outside a test, and retrieval writes nothing back. A default
episode therefore leaves recall about 478 hours after ingest, however
often it was returned. Reinforcement needs a writer on the read
path, or decay is a retention timer.
Elephantasm has
two decays that read different clocks. The Dreamer's stored
decay_score reads creation time, with
access_count=0 marked TODO, and moves a memory to
DECAYING above 0.7 at importance below 0.3, about 52 days
at the 30-day half-life
(app/services/dreamer/light_sleep.py:112-207). The pack
compiler recomputes decay from updated_at as last access
(app/services/memory_pack_compiler.py:436), and the
Dreamer's own decay writes refresh updated_at through the
ORM's onupdate. The curator touching a row therefore reads
as use at retrieval. Its DECAYING to ARCHIVED
branch never fires, because the gatherer loads ACTIVE rows
only (app/services/dreamer/gather.py:101). Use one
clock for decay, and let only a read move it.
Hermes Fleet
Memory lets a near-duplicate write decide another card's
lifetime. Episodic cards expire 90 days after their last write
unless pinned, and a cron cleaner archives and deletes them
(server/cleaner.py:58-111). A write within 0.95 cosine of
an existing episodic card overwrites it, and the payload's
pinned and expires_at come from the new write
(client/fleet_memory.py:584, :649,
:657). Repetition therefore extends a note's life, and an
unpinned repetition of a pinned note returns it to the 90-day clock.
Reinforcement should only lengthen a life; an exemption a person
set belongs to the record, not to the latest write.
Implementation checklist
- Store retrieval strength separately from confidence and trust.
- Assign decay policy by memory kind, scope, and validity, not one global rate.
- Record the reason and actor for every reinforcement.
- Do not reinforce a memory the system itself chose to inject.
- Consider decaying one that was eligible and repeatedly not chosen, and be sure not chosen means it lost a comparison rather than never entering one.
- Bound reinforcement and prevent one retrieval loop from self-amplifying.
- Protect rejected-value tombstones and correction history from decay.
- Mark stale before deleting when reversibility matters.
- Expose last-evaluated time and effective strength to operators.
Tests to require
- Stable facts remain reachable after long simulated time.
- Expired transient state stops entering normal context.
- Repeated retrieval cannot increase epistemic confidence.
- Reinforcement saturates and cannot form an unbounded feedback loop.
- Pinned, verified, rejected, and audit records survive ordinary pruning.
- Clock jumps, backfills, and timestamp errors fail safely.
- Rebuilds reproduce effective strength from durable state or audit events.