What Should Your Agent Forget? Give Every Memory an Expiry Rule
Agent memory systems have a write verb and a recall verb, but no verb for "stop believing this." Here's how to decide at write time what makes each memory stop being true — and why expiry should downgrade a fact rather than delete it.
A memory an agent wrote in March is still being recalled in September, phrased with exactly the same confidence it had on the day it was written. Nothing failed. Retrieval worked perfectly. That is the problem.
Most memory systems are built with two verbs: write and recall. There is no third verb for stop believing this. So a note like "the finance team approves refunds over $500" survives a reorg, a policy change, and two people leaving, and keeps getting injected into the prompt as a settled fact — because the retriever has no way to know it aged.
Adding a created_at field does not fix this. The model sees a date and shrugs; a timestamp is not a policy. What's missing is a rule, decided at write time, about what makes this particular memory stop being true.
💾 Not all memories decay the same way
Lumping everything into one store with one TTL produces the worst of both options: preferences get forgotten too fast, and status facts linger too long. Sort by decay behavior instead:
Durable facts about a person. "Prefers a written summary over a call." "Works in Central European Time." These change slowly and, crucially, change silently — nobody sends an announcement. They shouldn't expire on a clock, but they should weaken when contradicted.
Facts about a system or an organization. "The staging database is db-3." "Refunds over $500 need finance." These are true until someone changes them, and when someone does change them, the agent is usually the last to hear. These need re-verification, not deletion.
State and status. "The migration ticket is blocked on legal." "The customer is waiting on a callback." These are stale within days, sometimes hours. Treated as durable, they are actively harmful — an agent confidently telling someone about a block that cleared last week is worse than an agent that says nothing.
Episodic records. "On the 12th, we tried approach A and it failed because of a rate limit." These never expire, because they're claims about a past event, not about the present. Their date is part of the content.
The mistake is not storing the wrong things. It's storing all four in the same shape.
Tag the expiry rule at write time
The decision about how a memory dies belongs to the moment it is written, when the context that produced it is still available. At recall time, all you have is a sentence with no provenance.
A minimal shape:
{
"text": "Refunds above $500 route to the finance team for approval",
"kind": "org_fact",
"written": "2026-03-04",
"expiry": { "mode": "verify_after", "days": 90 },
"source": "tool_call:policy_lookup",
"supersedes": null
}
Three fields carry the weight:
kinddecides the default expiry mode, so the writer doesn't have to reason about it every time.expiry.modeis one ofnever,verify_after, orhard_expire. Status memories gethard_expire. Org facts getverify_after. Episodic records getnever.sourcedecides how much a contradiction costs. A memory derived from a tool call should lose to a fresh tool call instantly. A memory the user stated directly should not be quietly overwritten by an inference.
⚠️ Expiry should downgrade, not delete
The tempting implementation is a cron job that deletes anything past its date. Don't. Deleting a stale memory throws away the most useful thing it knows: there used to be a rule here.
Downgrade instead. An expired memory keeps its place in the store but changes how it enters the prompt:
Fresh: Refunds above $500 route to the finance team for approval.
Expired: As of 2026-03-04, refunds above $500 routed to finance for approval. This has not been re-verified — confirm before acting on it.
The difference in agent behavior is large. A confident stale fact gets acted on. A dated, flagged one triggers a lookup, and that lookup is exactly the event that refreshes the memory. Expiry becomes a prompt to re-verify rather than an erasure.
This also fails safe. If your expiry classification was wrong and something got flagged too early, the cost is one extra tool call. If deletion was wrong, the cost is that the agent now knows nothing and doesn't know it once did.
The supersede rule
Contradiction is the other way memories should die, and it's the one most systems handle worst — by writing the new memory alongside the old and letting the retriever return both. Now the model has two incompatible facts and picks one by embedding proximity, which is to say, arbitrarily.
Make superseding explicit at write time. Before writing a memory about a subject, recall existing memories about that same subject. If the new one contradicts an old one, write the new one with a supersedes pointer and mark the old one inactive rather than deleting it. Retrieval only returns active memories; the chain stays available for debugging, which matters more than it sounds like when you're trying to work out why an agent believed something.
The check that keeps this honest: can a memory ever be wrong in a way the system will notice? If the answer is no, you don't have a memory system. You have an append-only log of things the agent once believed, being read back as scripture.
🔍 A short audit
Run these against your own store:
- Pick five memories at random. For each, say out loud what would have to happen in the world for it to become false. If you can't answer, its
kindis wrong. - Find your oldest recalled-in-the-last-week memory. Is it still true?
- Search for two memories that contradict each other. If both are active, your write path has no supersede step.
- Check whether an expired memory enters the prompt differently from a fresh one. If the string is identical, expiry isn't implemented — it's just metadata.
The agents that stay useful over months are not the ones that remember the most. They're the ones that know which of their memories they should check before using.