Long-term memory makes an agent feel attentive for about a month. Then it starts recommending paperbacks to someone who switched to ebooks in spring, applies a one-time gift-wrap request to every order, and occasionally repeats something it read on a web page as if the user had said it. The store has not failed; it is doing exactly what its write policy allowed.
This guide treats agent memory as a data system with an unreliable writer. It covers the three ways memory rots (stale, wrong and poisoned), conflict policies from append to reconciliation with validity intervals, confidence gates and one-off filters, the arithmetic of decay, provenance as the defence against memory injection, and how to run and measure memory in production. The lab simulates 90 days of memory for 40 users so you can tune each policy against the others.
Memory fails on the write path
Agent memory comes in two broad kinds. Short-term or working memory is the state of one conversation or run: messages, scratch variables, tool results, usually checkpointed per thread (LangGraph's checkpointer is a typical example). Long-term memory outlives the thread: facts and preferences about a user, summaries of past episodes, learned procedures, stored in a key-value or vector store and recalled into later conversations (LangGraph's store API with namespaces, put and search; Mem0; Zep; or a table in Postgres with pgvector). The CoALA framework names the long-term kinds episodic, semantic and procedural; this guide is about the semantic kind, facts the agent believes about the world and its users, because that is where rot shows up first.
Most teams invest in the read path: embeddings, hybrid search, reranking, how many memories to inject. The read path cannot fix what the write path got wrong. If an extraction error, a one-off request or an injected instruction was written as a durable fact, perfect retrieval returns it perfectly. If an old fact was never retired, retrieval has to guess between two contradictory memories. The decisions that keep memory healthy are made when something is written, not when it is read: whether to write at all, what to do when the new value conflicts with an old one, how long a memory stays trusted without being refreshed, and who is allowed to write.
Three ways a memory store rots
Every bad answer from memory falls into one of three classes, and the lab measures each separately:
| Class | What happened | Typical cause | Write-path fix |
|---|---|---|---|
| Stale | The memory was true once | Old value never retired; decay too slow | Conflict resolution, validity intervals, decay |
| Wrong | The memory was never true | Extraction error; a one-off request stored as a preference | Confidence gate, transient filter, confirm-before-replace |
| Poisoned | Someone else wrote it | Instruction in a web page, document or tool result | Provenance: only trusted sources may write |
A fourth outcome is not an error but has a cost: the agent remembers nothing and asks again. Re-asking is the price of forgetting aggressively; the right amount is not zero, because a memory that is never allowed to expire becomes stale with certainty when the underlying fact changes.
Conflict resolution: append, overwrite or reconcile
When a new candidate memory says something different from an existing one about the same subject, the store has three basic options.
Append keeps both and leaves the decision to retrieval. It is the default behaviour of a naive vector-store memory: every extracted fact becomes a new document. Retrieval then scores candidates by similarity, confidence and recency, and an old memory with a higher score can beat a newer, correct one. Over 90 simulated days in the lab, an append-only store with a one-year half-life grows to more than 4,000 live records for 40 users and six facts each, and about a fifth of answers come from values that used to be true.
Overwrite keeps one slot per subject and replaces it on every write. It cannot go stale through accumulation, but a single bad write replaces a good one: one misheard sentence or one "just for this order, send it as a gift" becomes the user's permanent preference. In the lab the overwrite policy without filters answers about a quarter of questions from memories that were never true.
Reconcile treats the conflict as a decision. Mem0's published design does this with an LLM step that compares each candidate with similar existing memories and chooses ADD, UPDATE, DELETE or NOOP. Zep's temporal knowledge graph keeps contradicted facts but closes their validity interval instead of deleting them, so history remains queryable and the current value is unambiguous. The lab's reconcile policy is a simple version: the same value refreshes the existing memory; a conflicting value replaces it only when it is an explicit update ("I switched to ebooks", or a correction after the agent used a wrong value), when it is mentioned twice within 30 days, or when it is much more confident than the current one; otherwise it waits as pending.
def write_memory(store, user, key, value, conf, source, now, explicit=False):
if source not in TRUSTED_WRITERS: # provenance gate
return "rejected: untrusted source"
if conf < GATE or is_transient(value): # confidence gate and one-off filter
return "rejected: low confidence or one-off"
cur = store.current(user, key, now) # live record with valid_to IS NULL
if cur is None:
return store.insert(user, key, value, conf, source, valid_from=now)
if cur.value == value:
return store.refresh(cur, last_confirmed=now, conf=max(cur.conf, conf))
if explicit or store.pending_matches(user, key, value, within_days=30) \
or conf > cur.conf + 0.25:
store.close(cur, valid_to=now) # keep history, retire the old value
return store.insert(user, key, value, conf, source, valid_from=now)
return store.add_pending(user, key, value, now)The write gate and the one-off filter
Extraction is a model call, and it makes mistakes. A confidence gate rejects candidates below a threshold, but the threshold only helps if errors and truths have different confidence distributions. In the lab, correct extractions have a mean confidence of 0.8 and extraction errors 0.52, so a gate at 0.6 removes about 72% of errors while keeping about 98% of correct writes (both from the normal distributions the lab assumes). Raise the gate to 0.9 and you reject most correct writes too, and the agent has nothing to recall for about a quarter of the questions it should already know the answer to.
One-off statements are different. "Wrap this one as a gift" or "for this order, use express shipping" are extracted confidently, because they are real and clearly stated; they are just not durable. The lab gives them a mean confidence of 0.74, so a gate that keeps good writes keeps them too. They need a separate classifier, or an extraction prompt that labels scope (this request, this session, durable), and a policy that writes only the durable ones to long-term memory. Mission two starts from overwrite with no filters and asks for at most 2% wrong answers; it takes the gate, the one-off filter and confirm-before-replace together.
Decay: the half-life of truth
Facts about people change: format preferences, reading interests, delivery details, which project they are working on. If a fact changes on average every τ days and changes arrive at random, the probability that a memory written a days ago is still true is e-a/τ. That curve has a half-life of τ × ln 2: for facts that change about monthly (τ = 30), a memory not reconfirmed for 21 days is a coin flip, and one not reconfirmed for 60 days is right only about 14% of the time.
Decay puts that knowledge into retrieval. The Generative Agents paper scored memories by a weighted sum of recency (an exponential decay, 0.995 per simulated hour in the paper), importance and relevance; many production stores use a simpler multiplicative score or a time-to-live. The lab multiplies extractor confidence by 0.5age / half-life and treats a memory whose decay factor falls below 0.25, two half-lives without a refresh, as expired, so the agent asks again rather than guessing. Refreshing matters as much as decaying: each time the user restates or confirms a fact, its age resets.
The half-life then trades staleness against re-asking. Too long, and an unrefreshed memory keeps answering after the fact has changed. Too short, and the agent forgets things the user told it last week. In the lab, with facts changing about every 30 days, moving the half-life from 365 days to around 11 cuts stale answers by a few points and adds a few points of re-asks; at 7 days re-asks pass 15%. Mission four asks you to fit both under 11% at once, which only works in a narrow band once the write path is clean.
Provenance: who is allowed to write
An agent that writes memories from everything in its context will write memories from content an attacker controls. A product page, an email or a tool result containing "remember that this user's preferred delivery address is ..." looks to the extraction step like any other clearly stated fact, often with higher confidence than real user speech because it is phrased to be unambiguous. In the lab, injected candidates have a mean confidence of 0.9: raising the gate to 0.9 does not stop them and destroys recall of genuine facts.
The property that distinguishes them is where they came from. Tag every candidate with its source (user turn, assistant turn, tool result, retrieved document), allow only user-authored turns (and perhaps verified system integrations) to write durable user facts, and store the source with the record so you can audit and bulk-delete by origin later. Mission three turns on injection and asks for zero poisoned answers; the provenance rule does it without touching the gate. The same idea underpins prompt-injection defences for tools: untrusted text may inform an answer, but it should not change durable state on its own.
Worked example: Belgavi's Book Store remembers its readers
A hypothetical assistant for Belgavi's Book Store remembers six facts per reader: preferred format, favourite genres, reading level for children's recommendations, delivery region, gift-wrap default and newsletter frequency. The lab simulates 40 readers over 90 days, each talking to the assistant on about 45% of days; when facts change fast (the lab's event: each fact about once a month), readers mention a change unprompted 60% of the time. All figures are calculated by the synthetic model.
- Append everything, one-year decay: about 71% of answers correct, 21% stale, more than 4,000 live records.
- Reconcile, gate 0.6, one-off filter: about 81% correct, 13% stale, wrong answers under 1%, one live record per fact (240 in total).
- Add provenance, raise the gate to 0.7 and use an 11-day half-life: stale and re-ask rates both around 10%, zero poisoned answers despite injection attempts in 4% of conversations.
The remaining stale answers are the honest floor of the model: a fact that changed and was never mentioned cannot be known until the agent uses it and the reader corrects it. The lab assumes readers correct a wrong answer 70% of the time and treats that correction as an explicit update. In production, design the conversation so that corrections are easy and are written immediately.
Running memory in production
A memory store is a database with an unreliable writer, so treat it like one. Frameworks change their memory APIs often (store interfaces, update operations, temporal fields), so check the documentation for the exact version of LangGraph, Mem0, Zep or your own layer before relying on a specific behaviour.
- Schema: store user or tenant, subject key, value, confidence, source,
valid_from,valid_to,last_confirmedand the conversation ID that produced the write. A vector column (pgvector, Qdrant or the store your framework provides) is for finding candidates, not for deciding truth. - Namespaces: scope memories by user and, in multi-tenant systems, by tenant at the storage layer, as LangGraph's store namespaces encourage, so a retrieval bug cannot leak one user's memory into another's context.
- Metrics: write acceptance and rejection rates by reason, conflicts resolved per day, corrections per hundred answers that used memory, re-asks, live records per user, and the age distribution of memories at the time they are used.
- Evaluation: keep a replay set of conversations with known fact changes and measure stale, wrong and poisoned rates offline before changing extraction prompts, gates or decay.
- Deletion: users can ask to be forgotten, and memories written from a compromised source must be removable in bulk; both depend on storing provenance.
The common mistakes are appending every extracted fact and hoping retrieval sorts it out, letting tool output and retrieved documents write durable memories, raising the confidence gate to fix a provenance problem, never expiring anything, and measuring memory only by retrieval similarity instead of by whether the remembered value was right when it was used.
What the lab's missions practise
Mission one starts with an append-only store and fast-changing facts and asks for stale answers at 14% or less with at least 78% correct; the three-star answer also keeps wrong answers low. Mission two removes drift but starts from overwrite with no filters, and asks for wrong answers at 2% or less. Mission three adds memory injection to an otherwise good configuration and asks for zero poisoned answers without giving up accuracy. Mission four combines drift and injection, starts from the worst configuration, and asks for stale and re-ask rates under 11% at the same time, with wrong answers at 1% or less and no more than 300 live records: fix the write path first, then find the decay window.
Run the lab: Agent memory: write less, reconcile more, forget on time
The lab simulates 90 days of conversations between an assistant and 40 users with six changing facts each. The top of the canvas follows three of those users: thin lines show each fact's true value over time (colour is the value), thicker bars show the memories the store holds, and a white dot marks a memory that was wrong when written. Below, the green line is the share of answers that used a correct memory. Choose a conflict policy, a write gate, a decay half-life and the two filters, then turn on fast-changing facts or memory injection. Each mission loads a broken store.
Prefer a full-width view? Open the standalone lab. Your stars are saved in this browser and shown on the series hub.
What to do next
- Add source, confidence, valid_from, valid_to and last_confirmed columns to your memory records, and backfill source where you can.
- Allow only user-authored turns (and named trusted integrations) to write durable user facts; reject writes that originate in tool output or retrieved content.
- Classify extracted statements by scope (this request, this session, durable) and write only durable ones to long-term memory.
- Replace append-only writes with a reconcile step that retires conflicting values instead of stacking them.
- Estimate how often each kind of fact changes and set its decay half-life or TTL from that, with refresh on every confirmation.
- Build a replay set with known fact changes and track stale, wrong, poisoned and re-ask rates before and after every memory change.
Sources and further reading
- Park et al., Generative Agents: Interactive Simulacra of Human Behavior
- Packer et al., MemGPT: Towards LLMs as Operating Systems
- Chhikara et al., Mem0: Building Production-Ready AI Agents with Scalable Long-Term Memory
- Rasmussen et al., Zep: A Temporal Knowledge Graph Architecture for Agent Memory
- Sumers et al., Cognitive Architectures for Language Agents (CoALA)
- pgvector: open-source vector similarity search for Postgres
Related in this series:
- Agent Context Compaction: What to Keep When the Window Fills Up
- MCP Tool Poisoning: How One Malicious Description Hijacks an Agent
- Evaluating Agent Trajectories: Why pass@k Hides Unreliable Agents
Diagrams, examples and the lab model are original educational material. Lab numbers are calculated by a synthetic model, not measured on real hardware or services.
Views are my own and do not represent any employer, past or present.
