Python · cell 1
Long-term memory is what an agent keeps outside its context window and reads back when it needs it. A model sees only the tokens of its current call. What a user said last month, or what the agent learned on an earlier task, is gone unless something stored it and brings it back. This section builds that store. It covers what to keep, how to find it again, how to keep it up to date, and when to throw it away.
The previous section, §25.2, kept the window small. It slid a window over the messages, cleared old tool results, summarized the transcript, and wrote notes to files. Every one of those tools throws something away. Some of what leaves the window will be needed again, a week or a year later, in a session that never saw it.
Finding it again is a search. Each stored item and each question is turned into an embedding, a vector of numbers whose direction encodes the meaning of the text. The store returns the items whose vectors point closest to the question's. That is the vector search of §7.4. Two things make memory harder than searching a fixed set of documents. The store is written while it is being read, and what it holds goes out of date.
Here is the failure in its simplest form. In March a user, Sam, tells her assistant that she is vegetarian. In July she mentions, in passing, that she now eats fish. In October she asks where to go for dinner, and the assistant suggests a steakhouse. Three things can have gone wrong in the memory, and each needs a different fix.
We take the three in turn. First come the kinds of memory an agent can have. Then a precise memory store, the score that retrieves from it, and a proof that a bigger store retrieves worse. Then an idea from operating systems, moving information between the window and the store, with the cache policies behind it and a proof that one of them is optimal. Then write policies, duplicates, and forgetting, and last the published systems and their numbers.
Designing memory for an assistant is a common system design question in interviews for agent teams. Every part of this section is something an interviewer can probe.
Psychologists sort human memory into kinds. The sorting is a useful checklist for an agent, as long as we treat it as an analogy. You remember the dinner where you met someone, an event with a time and a place. You know their name and that they are vegetarian, facts drawn from many events. You know how to cook a risotto without thinking about it, a skill. All of this sits behind a small working memory that holds what you are thinking about right now.
Cognitive Architectures for Language Agents, or CoALA (Sumers et al. 2024), is a framework for describing agents built on language models. It adopted the same four kinds. Most memory systems since can be described in its terms.
| kind | what it holds | how an agent stores it | how it is written | systems in this section |
|---|---|---|---|---|
| working | the current task, recent turns, retrieved items | the context window | every turn | everything in §25.1 and §25.2 |
| episodic | what happened, and when | an append-only log of messages and attempts, embedded for search | automatically, as events occur | MemGPT's recall storage, Generative Agents' memory stream |
| semantic | facts and relations drawn from episodes | fact stores, entity summaries, knowledge graphs | by extraction, merging, or reflection | Mem0, Zep, HippoRAG, A-MEM |
| procedural | how to do things | weights; code, skills, workflows, playbooks | by training, or by distilling attempts that worked | Voyager, Agent Workflow Memory, ACE |
Each system in the last column is described later in the section, where it is used. Two facts in this table matter more than the labels.
First, semantic memory is made from episodic memory. A fact store is what you get by reading the log and deciding what it means, and each such decision can be wrong. So every derived memory should point back to the episodes it came from. That record is its provenance. It is what lets you check a memory again, correct it, or delete it along with everything built from it.
Second, procedural memory is the most dangerous to write. A wrong fact misleads one answer, while a wrong skill is reused on every later task. CoALA makes the same point. Writing new procedures is riskier than adding episodes or facts, because it can introduce bugs or let an agent work around its designers' intentions. Writing procedures into the weights is the subject of §25.5. This section covers the explicit kind.
An interviewer who asks you to design memory for an assistant expects you to name all four kinds and say which one each part of your design stores.
Here is the idea before any symbols. A memory is a short piece of text with a few numbers attached. When a question comes in, every memory gets three scores.
The store adds the three and returns the best few. Before adding, it stretches each score so that it runs from 0 to 1 over the candidates. That step is called min-max scaling. Its purpose is that no score wins just because of its units.
This is the retrieval rule of a paper on simulated characters (Park et al. 2023), which we will call Generative Agents after its title. It built a small town of characters, each driven by a language model. Each character kept a memory stream, a log of everything it observed, and retrieved from it with this rule. Here is the rule precisely.
In plain words, a memory comes back if it is about the question, if it was used recently, and if it mattered. Generative Agents set all three weights to 1. Importance came from asking the model for an integer from 1 ("brushing teeth") to 10 ("a break up"). Recency decayed by per hour of game time, counted from the last time the memory was retrieved. The subscript on keeps it apart from the discount factor of reinforcement learning.
The last clause of the definition creates a feedback loop. Retrieval resets , so a memory that is retrieved once becomes more likely to be retrieved again. That keeps the memories of an ongoing conversation at hand. It can also lock in an early mistake, as the store class below will show.
How fast does a decay of 0.995 per hour forget? The half-life is the age at which recency falls to one half. It solves . Take logarithms of both sides and divide.
So raw recency separates memories by weeks. Within a day it barely ranks anything. Min-max scaling then does something quite different to it, as the next example shows.
Why did scaling change the winner so much? Because it quietly changes the weights. The next result makes that exact.
In plain words, scaling divides each weight by how much that component varies among the candidates. In the example, relevance's effective weight went up by and importance's by relative to the raw sum, while recency's stayed about the same, since its range was nearly 1.
Now picture a candidate set that was all touched within the last day. Recency then varies by at most , so its effective weight is multiplied by . A few hours of age now count as much as the whole spread of relevance. If the candidates span six months, recency keeps its nominal weight. So the same weights mean different things for different stores. Adding one very old or very new memory can reorder all the others.
Min-max scaling is still a reasonable default, because the raw components come in incompatible units (a cosine near 0.6, an importance near 5). Just know that it is a reweighting that depends on the data, not a neutral normalization. The same problem is why §7.5 merged keyword search and embedding search by rank rather than by adding normalized scores. Its method, reciprocal rank fusion, gives each item one over a constant plus its rank in each list, and adds these up. An interviewer who hands you the score formula will ask exactly this, what the weights mean after scaling.
The first cell checks the example and the half-life.
Here is the whole store as a small class. It has the five operations a store needs, write, update, delete, retrieve, and forget. Provenance is a list that only grows, so an updated memory still remembers every episode behind it. forget keeps the entries with the highest recency times importance, one simple capacity policy among many.
Look at what forget kept. The two memories that were just retrieved now have recency 1, so they survive along with the morning coffee. The Italian restaurant and the birthday are gone. The act of reading decided what was forgotten, and the stale vegetarian fact survived because it had just been read.
Production systems rarely use this three-term sum alone. Relevance is often a hybrid of two searches fused by rank (§7.5). One is BM25, a keyword score that counts the query's words in each memory and weights rare words more. The other is the embedding similarity above.
The candidate set is first cut by metadata filters, such as this user, this project, the last 30 days, or entries about this person. Then the top few dozen candidates are reranked by a cross-encoder, a model that reads the query and the memory together and scores the pair.
Zep, a memory system built on a knowledge graph, gathers candidates three ways (Rasmussen et al. 2025). It uses cosine search, BM25, and breadth-first search over the graph. It then reranks them with methods such as reciprocal rank fusion and maximal marginal relevance, a rule we apply to memories in the next part. The vector index underneath is the one of §7.4. What is new for memory is the time dimension, and the fact that the store is written while it is read.
Implement retrieve(query, keys, ages_h, importance, k, weights=(1.0, 1.0, 1.0), decay=0.995).
query has shape (d,), and keys has shape (N, d). The keys need not be normalized.ages_h holds the hours since each memory was last accessed, and importance holds the importance ratings.Score every memory with the retrieval score defined above. Relevance is cosine similarity, and recency is decay ** ages_h. Min-max scale each component over all candidates before applying the weights, and scale a constant component to zeros. Return the indices of the k best memories as a list of ints, best first. Break ties in favor of the smaller age, then the smaller index.
A store that only grows has a problem that a fixed document collection does not. Every new memory is a new rival for every old question. A memory about something else rarely wins. A memory about the same subject often does.
Start with numbers. Suppose each rival memory has a 3% chance of scoring above the one we want. With one rival, the right memory comes first 97% of the time. With 10 independent rivals it comes first of the time, and with 100 only of the time. By the hundredth memory about Sam's food, the one that answers today's question may have 99 rivals that are nearly as close.
Now make it general. A query has cosine with the memory we want, the target. The other memories are distractors, with cosines with the query. The target is retrieved first exactly when beats every . The theorem below gives that probability, and then asks how high the strongest distractor climbs as grows.
Why the formula holds. The target ranks first when every distractor stays below , and independent distractors each do so with probability . The second solves for , with for small .
The bound on the expected maximum is the one worth deriving. It says the strongest rival grows only like the square root of . So the damage grows slowly with the size of the store. What decides it is the margin , measured in units of .
§16.1 proved the standard case. For standard normals , apply Jensen's inequality (§0.4) to the convex function , bound the maximum by the sum , and use the Gaussian moment generating function . That gives for every , and the choice gives . Bounding a maximum of positive numbers by their sum needs no independence, so neither does the result.
Now write , so that each is a standard normal. Then , and taking expectations gives .
The tail bound in the theorem takes one more line. Let be the standard normal density. For , . Substituting gives , so .
Plug in numbers, and the picture splits in two.
The simulation below builds such a cluster of same-subject memories out of real normalized vectors and checks the theorem. It also shows where the Gaussian model is off. Cosines of unit vectors are bounded, and their upper tail is lighter than a Gaussian's, so the Gaussian formula is pessimistic at large . The formula with measured directly does better.
The target cosine is 0.61. The distractors average 0.33, with a spread of 0.078 for a given question. That is a margin of about 3.6 standard deviations, nearly twice the one in the example above, so the decline is slower. The share of questions whose target ranks first falls from 0.99 with 10 distractors to 0.67 with 3,000. The direct formula tracks the simulation closely up to (0.79 against 0.78). The Gaussian model is pessimistic, as predicted (0.67 there). The mean maximum stays below the bound at every .
The theorem also says what to do about interference. Every remedy either shrinks or widens the margin.
Retrieving several memories has a trap of its own, and it comes from the same near-duplicates. If the store holds three restatements of the vegetarian fact, the top 3 for the dinner question are those three, and the fish fact is still left out.
The standard remedy is maximal marginal relevance (MMR), defined in §7.6 (Carbonell and Goldstein 1998). It picks memories one at a time and charges each candidate for being similar to what was already picked. With the set chosen so far and a trade-off weight, it takes the candidate outside that maximizes
In plain words, the first pick is the most relevant memory, and a second copy of it scores poorly because it is so close to the first. The cell runs the rule on six memories about Sam, three of them restatements. sam_rel holds their cosines with the question, and sam_sims holds the cosines between them.
At the rule is plain ranking and returns the three restatements. At 0.7 it returns the vegetarian fact, the Italian restaurant, and the fish fact. By hand, after the first pick the second restatement scores , the fish fact , and the restaurant , so the restaurant comes second. For the third pick the fish fact still scores 0.175, and the coffee memory scores , so the fish fact comes third. At 0.5 the coffee memory displaces the fish fact. This is the failure §7.6 warned about, a memory picked mainly because it is different.
Notice what MMR does not fix. Both diet facts are now in the window, so failure 2 is gone for this question. But nothing tells the model which one is current. That is failure 3, and it needs the dates that a write policy records.
So far the agent's code decided when to retrieve. MemGPT (Packer et al. 2023) is a system that hands that decision to the model, with an idea borrowed from operating systems. A computer gives each program the illusion of a huge memory. It keeps a small, fast main memory and a large, slow disk, and moves fixed-size blocks called pages between them. When a program touches something that is not in main memory, that is a page fault, and the page is brought in.
MemGPT treats the context window as main memory and external stores as disk. The model moves information between them itself, through function calls, the structured tool requests that the agent's code carries out (§24.1). Its prompt on each call, the main context, has three parts.
Outside the prompt sit two stores. Recall storage is a database of every message ever exchanged, an episodic memory. Archival storage is a database of arbitrary text that the model can read and write, a semantic memory. The model pages information in by calling search functions on these stores. Their results arrive a page at a time and are appended to the queue. It pages information out by calling functions that write to the working context or to archival storage.
When the window fills, a queue manager applies the compaction policy of §25.2, which replaces old messages with a summary. At about 70% of the window it inserts a "memory pressure" warning, so that the model can save what matters. At 100% it evicts about half the window and folds it into a new summary. What is new is that evicted messages stay searchable in recall storage. A function call can also ask for another model step right away, a "heartbeat", so the model can chain several searches before it answers.
MemGPT was tested on deep memory retrieval, a task built from chats that span several sessions, in which each question refers to a detail from an earlier session. Accuracy rose from 32.1% to 92.5% with GPT-4 and from 35.3% to 93.4% with GPT-4 Turbo. The baselines saw only a lossy summary of the earlier sessions. MemGPT had the full history and had to search it itself.
The authors of Zep, the memory system built on a knowledge graph that we met above, ran the same task and add a fair caveat (Rasmussen et al. 2025). Their system scored 94.8% with GPT-4 Turbo, a little above MemGPT. But the conversations in that task are only about 60 messages long and fit in a modern context window. So the task tests a regime that no longer stresses memory. Two harder benchmarks are covered in §25.6. LoCoMo tests very long chats over many sessions, and LongMemEval tests histories of about 115,000 tokens.
An interviewer will ask what MemGPT changes. The answer is who decides to retrieve. The model, not fixed code, chooses when to search and what to save, and that choice can fail, as the end of this part shows.
Whatever decides what to bring in, something must decide what to push out. Strip the problem to its core.
For an agent, a miss is a tool call, one more trip around the agent loop. It costs a model step, its latency, and the tokens of the fetched item. Eviction has a cost too. A provider's prompt cache stores the computed state of a prompt prefix it has seen before and bills a repeat of that prefix at a discount. As §25.2 warned, removing an item from the middle of the window breaks that prefix, so everything after the removed item is billed in full again. So misses and evictions are worth counting carefully.
The anomaly can never happen to LRU. Once the context is full, LRU with slots holds exactly the most recently used distinct items, and those are always among the most recently used. So every hit with slots is also a hit with , and misses can only fall as the context grows.
Belady's rule is the best possible, and the proof is a classic interview topic.
The idea of the proof is an exchange argument. Take any optimal schedule. Wherever it first disagrees with Belady's rule, change that one decision to Belady's, and show that the miss count does not go up. Repeat until the schedule is Belady's rule itself.
A schedule is a list of eviction decisions for a request sequence . Call it reduced if it loads an item only when that item is requested and missing, so that its loads are its misses. Any schedule can be reduced without adding loads, since an item loaded early can instead be loaded when it is next requested, or not at all. Write FF (farthest in future) for Belady's rule. This is the textbook argument in the chapter on greedy algorithms of Kleinberg and Tardos (2005), Algorithm Design.
Claim. Let be a reduced schedule that makes the same decisions as FF on the first requests. Then there is a reduced schedule that makes the same decisions as FF on the first requests and has no more misses than .
Proof of the claim. If request is a hit, or a miss on which and FF evict the same item, take . Otherwise it is a miss on which FF evicts and evicts some . Let evict , like FF. Now the contexts of and are equal except that holds where holds . From request on, let copy every decision of until the first of these events.
Every other request hits or misses identically in both. Once the contexts are identical, copies to the end. So agrees with FF through request and has at most as many loads as . Reducing adds no loads and does not change its first decisions, because those were already demand loads.
The theorem. Start from an optimal reduced schedule and apply the claim for . Each step keeps the miss count at most the optimum, and the last schedule is FF itself.
Belady's rule needs the future, so how close can an online policy get? Sleator and Tarjan (1985) answered this with the result that founded competitive analysis. It studies how far an online algorithm can fall behind an optimal one that knows the future, on the worst possible input.
In plain words, LRU can be terrible in the worst case, but give it about twice the room and it is never far from what an oracle could do with the original room. Take the two cases. With equal room, , the ratio is , a weak guarantee, and it is tight. A loop over items makes LRU miss on every request, while Belady misses only about once every requests, so over a long loop the ratio approaches . With twice the room, , the ratio is , below 2.
Take equal capacities, , and split the request sequence into phases. The first phase starts at the first request. Each phase is the longest stretch of requests that contains at most distinct items, so the next phase starts at the request that would be the th distinct item.
LRU misses at most times per phase. Suppose an item requested in a phase were evicted later in the same phase. LRU evicts only when is the least recently used of cached items and a new item arrives. Then distinct items other than (the others in the context and the newcomer) were all requested after 's last request. With that makes distinct items in one phase, which is impossible. So each distinct item of a phase misses at most once in it.
The optimum misses at least once per phase, after the first. Let be the item of the first request of phase . Look at the window from the next request through the first request of phase . Every phase but the last has exactly distinct items, so the window contains the other items of phase and the first item of phase , which is new. That is distinct items other than . Right after serving , any policy holds and at most other items, so at least one of those items misses inside the window. The windows of different phases do not overlap.
With phases, LRU makes at most misses and the optimum at least . So LRU's misses are at most times the optimum's, plus .
The proof's two halves are worth seeing on a trace. The widget races the eviction rules over two request sequences. One is a loop over 6 items with slots, the worst case behind the factor . The other is a sequence of working-set phases, like an agent that moves from one subtask to the next. The widget opens on the loop. Before you read its counts, predict how many of the 48 requests LRU will miss and how many Belady will. Then switch to the working-set phases and guess which policy suffers most at , and why LFU might be worse than FIFO there.
On the phased trace, LFU misses most at every capacity from 3 slots up. It keeps items that were popular in an earlier phase long after the agent has moved on. Memory scores that count how often a memory was used share this failure.
Real agent traces are phased like this. A coding agent reads a handful of files intensively, then moves to another part of the codebase. The right mental model of what its context should hold is the working set, the set of items used in a recent window of time (Denning 1968). In practice this means a context budget should be sized to the working set of the current phase, and old phases should be evicted whole, not kept because they were once popular.
This is one of the most common coding interview questions. LRU is also the eviction rule of many real caches. Recall that an inference server keeps a KV cache, the key and value vectors of every token it has already read, so that it never recomputes them. SGLang, an inference server, keeps a tree of cached prefixes shared between requests and evicts the least recently used leaf first (Zheng et al. 2024). The section on paged caches and prefix sharing shows the same LRU rule in vLLM's block cache.
Implement a class LRUCache(capacity) with two methods.
get(key) returns the value stored under key, or -1 if it is not cached. It counts as a use.put(key, value) inserts or overwrites the value and counts as a use. If inserting a new key would exceed capacity, it first evicts the least recently used key. Overwriting a key that is already cached never evicts anything.The capacity is at least 1.
Both must take time. Use a dict and a doubly linked list, not collections.OrderedDict or functools.
The setup defines two traces. TRACE holds 3,200 file reads by a simulated coding agent that works in phases on small sets of files, with some stray reads. CLASSIC holds the twelve requests of the worked example.
misses(trace, capacity, policy) for policy in "fifo", "lru", and "belady". Count every miss, including the first request for each item.slots_to_match(trace, h, max_capacity=60). It returns the smallest capacity at which LRU makes no more misses than Belady makes with h slots, or None if no capacity up to max_capacity does.The tests check the worked example, the agent trace, and how many extra slots LRU needs. Compare that with the bound of Sleator and Tarjan.
On this trace, LRU needs 4 slots to match what the oracle does with 2, 7 to match its 4, and 12 to match its 8. That is about 1.5 to 2 times the room. The theorem of Sleator and Tarjan promises much less than this. It never says that extra room lets LRU match the oracle. It only caps LRU's misses at times the oracle's, plus . With exactly twice the room, 8 slots against 4, it allows times the oracle's misses, and at 12 slots against 8 it allows . LRU does far better on this trace because a phased trace like this one is far from the adversarial loop that makes the bound tight.
The operating-systems analogy is useful, and it breaks in four places that matter.
Now the first failure, the memory that was never written. Storing every message verbatim is easy, and it has a real use, since MemGPT's recall storage does exactly that. But a raw log is a poor semantic memory, for three reasons. It is mostly small talk. Each fact appears many times in many phrasings, each one a distractor for the others. And nothing in it says that the July message overrides the March one. Something has to read the log and decide what to remember.
This is the design of Mem0 (Chhikara et al. 2025), a memory layer for production agents. Its extraction runs on each new pair of user and assistant messages, with a conversation summary and the 10 previous messages as context. Its update retrieves the 10 most similar memories for each candidate and lets the model choose ADD, UPDATE, DELETE, or NOOP through a tool call. Three design questions sit behind any such policy.
When to write. Writing as each message arrives makes memories available at once, but it adds extraction calls to the latency of every turn. Writing in the background, after a turn or at the end of a session, is cheaper and can look at more context. The price is that a fact stated a minute ago is not yet in memory. Many systems do both, a fast write of the raw episode and a slower pass that merges it into the store.
What to write. The usual targets are facts users state about themselves, preferences, decisions, commitments, corrections, and the outcomes of tasks. Generative Agents added a second kind of write, reflection, in which the agent writes conclusions it drew itself.
An ablation measured what this buys. Human evaluators ranked the believability of the agents' answers to interview questions. The rankings were turned into ratings with TrueSkill, a system that infers a skill score for each contestant from rankings, much as chess ratings do from games. The full architecture scored 29.89, without reflection 26.88, without reflection or planning 25.64, and with no memory, reflection, or planning 21.21. Human crowdworkers writing in character scored 22.95 (Park et al. 2023). Reflexion reflects on failed attempts rather than on events. After each failed attempt at a task, the agent writes a lesson in words and reads it on the next attempt (§24.4). §26.1 asks when such self-written lessons help.
The next exercise builds the update stage. It uses stand-ins for the model calls, so that the logic of the four operations is what gets tested.
The setup builds a tiny world of facts about Sam, each with a unit embedding in VEC. It gives you four helpers.
contradicts(a, b), a stand-in for a model that judges whether fact b contradicts fact a.merge(a, b), a stand-in for a model that folds fact b into memory a.memory(id, text, sources) and candidate(text, source), which build the dicts below.A store is a list of dicts with keys id, text, vec, and sources. A candidate is a dict with text, vec, and source.
Implement apply_writes(store, candidates, sim_threshold=0.6, dup_threshold=0.95). It processes candidates in order, so each one sees the store as changed by the earlier ones, and returns (store, ops), where ops lists (operation, id) tuples. For each candidate, the similar memories are those with cosine at least sim_threshold.
contradicts(memory_text, candidate_text)), append ("DELETE", id) for each of them in store order, remove them, then ADD the candidate.sources if it is not already there. If the cosine is at least dup_threshold, append ("NOOP", id). Otherwise set its text to merge(old_text, candidate_text) and its key to the normalized sum of the two keys, and append ("UPDATE", id).sources is the candidate's source alone, and ("ADD", id) is appended. It goes at the end of the store.Delete or invalidate. The third design question is what a contradiction should do. DELETE is the simple answer, and it destroys history. After "Sam now eats fish" replaces "Sam is vegetarian", the store can no longer answer "when did Sam stop being vegetarian?" or "what did Sam eat when we planned the Lisbon trip?".
Zep (Rasmussen et al. 2025) keeps the history. It stores every fact as an edge of a knowledge graph with two timelines. One timeline records when the fact was true in the world. The other records when the system learned it and when the system retired it. A contradiction does not delete the old edge. It closes the old edge's world timeline at the moment the new fact became true. Current questions then filter on what is true now, and historical questions can still find the old value.
Zep edits the old edge in place. It sets the old edge's to the of the fact that replaced it. Its second timeline records when each edge was created and when it was invalidated. So in Zep an invalidated edge still answers historical questions through its validity interval, and only dates the correction. That is looser than the definition above, where ends the store's belief in the record.
The setup defines FACTS, the records a store that never edits a record wrote about Sam, with times in hours. Each record is a dict with keys subject, value, valid, invalid, created, and expired, where None stands for an open end ().
value_at(facts, subject, t_q, t_known=None). A record matches when it is about subject, its validity interval contains t_q, and the store believed it at t_known, that is, created <= t_known < expired. With t_known=None the question is asked now, so only records whose expired is None are believed. Both intervals are half-open. Return the matching record's value, or None if no record matches.timeline(facts, subject), the history the store believes now. It is a list of (value, valid, invalid) tuples for the believed records about subject, sorted by valid.A small simulation makes the trade-offs concrete. A user has 100 facts (diet, city, employer, and so on), each with one of 6 values. She mentions 10 of them in each of 200 sessions. A mention restates the current value, or, a quarter of the time, changes it.
Each memory's key encodes what the memory is about, not which value it states. That is how real embeddings behave, since "Sam is vegetarian" and "Sam now eats fish" are both close to "what does Sam eat?". We then ask two questions about every fact, its current value and its value before the last change, and compare stores.
Read the table row by row.
The table has no row for the obvious alternative, putting the whole history in the context, because a simulation of stores cannot measure what a model does with a long transcript. The real measurements, in the fact box near the end of the section, go both ways. On LoCoMo, a benchmark of long multi-session chats, the full conversation in context beat Mem0's store, 72.9 against 66.9. On LongMemEval, with histories of about 115K tokens, Zep's retrieved memory beat the full history, 71.2% against 60.2%.
Theory adds the trend, as an illustration rather than a prediction. Recall from §25.1 that attention gives each item a weight from its logit, and that a relevant item with a logit gap over equal rivals gets the weight . That weight is at least one half only while . A gap of 8 keeps the relevant item in focus up to about 3,000 items and loses it beyond. The update-in-place and bi-temporal stores instead give the current value correctly whenever the last mention of a fact was extracted correctly, however many mentions came before it.
The lesson is the one the opening promised. Staleness cannot be fixed at read time by tuning a score. It is fixed at write time, by turning a log into a store that knows which facts replaced which, and by keeping the replaced ones with their dates when history matters. An interviewer who asks "how do you handle a user changing their mind?" wants this answer.
A store written over months fills with near-duplicates. "Sam prefers dark mode", "Sam likes dark themes", and "Sam turned on dark mode in her editor" are three memories that say one thing. By the theorem on distractors above, each one competes with every other memory on the subject. Deduplication finds groups of memories that say the same thing. Consolidation replaces each group by one memory whose provenance is the union of the group's.
§8.3 solved a version of this problem for training corpora. It found near-identical texts by comparing their sets of word sequences. Then it grouped the matching pairs with union-find, a structure that keeps a parent pointer for every item and merges two groups by linking their roots. Memories need semantic deduplication instead, because paraphrases share few words. So an edge joins two memories when the cosine of their keys is at least a threshold . The grouping question is the same, and so is its subtlety. Similarity is not transitive.
Connected components are the right candidate grouping, since they do not depend on the order. Once the edges are known, union-find computes them in time nearly linear in the number of edges.
Components also chain. A sequence of small steps can connect "Sam prefers dark mode in her editor" to "Sam prefers light mode in the terminal". Merging that whole component into one memory would destroy a real distinction. For a corpus this rarely matters, and §8.3 accepted it. For memory it does, because each memory is a claim about a person.
So use components to propose groups, then verify each merge. Either require every member to be within the threshold of the group's representative, or ask a model whether the merged statement is true of every member. An interviewer who asks you to dedupe memories is usually checking for exactly this, that you know a similarity graph is not an equivalence relation.
Implement dedup_groups(E, threshold), where E is an array of shape (N, d) of memory keys, not necessarily normalized. Two memories are linked when the cosine similarity of their keys is at least threshold, and the groups are the connected components of this graph. Return a list rep of length N, where rep[i] is the smallest index in memory i's group, its earliest representative.
Use union-find with its two standard speedups. Path compression makes every node visited while finding a root point straight at that root. Union by rank hangs the shorter tree under the taller one when two groups merge. Together they make each operation take nearly constant time.
The all-pairs similarity matrix in this exercise costs time and memory. That is fine for one user's few thousand memories and hopeless for a billion documents. At scale, the edges come from an approximate nearest-neighbor index instead (§7.4), each memory linked to those of its nearest neighbors above the threshold. In practice a new memory is checked against its nearest neighbors at write time, which is what the NOOP branch of a write policy already does.
A store that never forgets grows without bound. It retrieves worse as it grows, keeps facts that stopped being true, and holds personal data longer than anyone wants. So forgetting is a feature, and it comes in three kinds that should not be confused.
Soft forgetting by decay lowers a memory's chance of retrieval without removing it. The recency term of Generative Agents is one form. MemoryBank (Zhong et al. 2023), a memory system for chat companions, models each memory's retention , its chance of being kept, with an exponential curve . The curve is a simplified version of the forgetting curve that Hermann Ebbinghaus measured on himself in the 1880s. Here is the time since the memory was last recalled and is its strength. starts at 1 and grows by 1 every time the memory is recalled, and recall resets to 0.
So a memory recalled three times has , and its retention stays above one half for time units, against for a memory never recalled. Each recall makes a memory last longer, a simple model of how rehearsal strengthens human memory. Soft forgetting is reversible and costs nothing. But a decayed memory still sits in the index and still counts among the distractors.
Hard forgetting by capacity deletes entries when the store exceeds a budget. It can delete by a score such as recency times importance (as in the store class above), by least recent use, or by merging old entries into summaries. It is the paging problem again, with one difference. A page evicted from main memory still exists on disk, while an evicted memory is gone. So it suits episodic logs whose content has already been distilled into semantic memory more than it suits the facts themselves.
Forgetting by invalidation retires a fact because a newer one contradicts it, as in the bi-temporal store. The old fact stops being retrieved for current questions but stays available for historical ones.
A fourth reason to forget is different in kind, a user's request to delete what was stored about them. It must remove the item and everything derived from it (summaries, reflections, merged memories). Provenance is what makes that possible, and it is the subject of §25.6.
Here is the whole design at work on the opening example. We reuse the five memories scored by hand earlier, with times in hours. Sam's dinner question arrives in October, at hour 10,000.
In the scored example earlier, the stale vegetarian fact ranked first. Here nothing in the retrieval score changed. The fix was a record closed in July and a filter applied in October. Each of the three failures has its guard. The write in step 1 prevents "never written". The filter and the score in steps 2 and 3 prevent "written but not retrieved". The interval closed in step 1 and checked in step 2 prevents "retrieved but stale".
This walk, from the July message to the October answer with each step's failure named, is the answer an interviewer wants to "design memory for an assistant".
Facts are not the only thing worth remembering. An agent that has solved a task should remember how. Some questions also need memory organized as more than a list.
Skill libraries. A skill library stores procedures as programs that have worked, indexed for retrieval like any other memory. Voyager (Wang et al. 2023), an agent that plays Minecraft through GPT-4, is the standard example. It writes each skill as an executable JavaScript program and refines it with feedback from the game and the interpreter. A skill enters the library only after a self-verification step confirms that the task was done.
Each skill is indexed by the embedding of a description of what it does. For a new task the agent retrieves the top 5 skills and composes new programs from them. Against the strongest earlier agents it collected 3.3 times as many unique items. It also reached key milestones of the game's tech tree, the sequence of tools a player must craft in order, up to 15.3 times faster. The design choice that matters is the verification gate. A skill enters procedural memory only after it has worked.
Workflows. A workflow is a reusable routine, a description plus abstract steps, with the values specific to one example replaced by placeholders. Agent Workflow Memory (Wang et al. 2024) induces such routines from an agent's past attempts at web tasks. It can induce them offline from training examples or online from its own attempts at test time. It raised success rates over the baselines by 24.6% on Mind2Web and 51.1% on WebArena, two benchmarks of tasks on real and simulated websites. Both are relative gains.
Playbooks. A playbook is a list of itemized strategies that grows as the agent works. Agentic Context Engineering, or ACE (Zhang et al. 2025), writes one with three roles. A generator attempts tasks. A reflector draws lessons from successes and failures. A curator turns the lessons into small new entries that ordinary code merges into the playbook, without a model rewriting it. That incremental design is what avoids the context collapse of the pitfall above. ACE reported average gains of 10.6% on agent benchmarks and 8.6% on finance tasks.
Graphs. Some questions are about relations, not single facts, the same gap that motivated graph-based retrieval in §7.7. "Which of the people Sam worked with in Boston also know her brother?" needs two hops. A flat fact store answers it only if one memory happens to contain both.
HippoRAG (Gutiérrez et al. 2024) extracts a knowledge graph of (subject, relation, object) triples from passages and links synonymous entities by embedding similarity. At query time it runs Personalized PageRank, a random walk over the graph that keeps restarting at the entities named in the question. The walk spreads relevance along edges, so one retrieval step finds passages several hops away. HippoRAG improved multi-hop question answering by up to 20%. Its single retrieval step matched or beat IRCoT, a method that alternates retrieval with steps of reasoning, while being 10 to 20 times cheaper and 6 to 13 times faster.
Zep's temporal graph adds the time dimension. A-MEM (Xu et al. 2025) organizes memories as a web of linked notes in the style of a Zettelkasten, a researcher's card index where every card links to related cards. Each new note gets keywords, tags, and links to related notes, and it can update the descriptions of the notes it links to.
Notice what the Mem0 numbers say about the regime. The full conversation won on quality because a LoCoMo conversation fits in a modern window. In Mem0's setup, each full conversation came to about 26,000 tokens of prompt.
This is the choice between long context and retrieval from §7.7, in a new setting. Memory systems earn their keep when the history does not fit, when latency and cost matter, or when the store must outlive the window and be edited, scoped, and deleted. They do not win because retrieval reads better than a model that can see everything.
Zep's LongMemEval result shows the other side. There retrieval beat the full 115K-token history, although that history also fits in the window. This fits the spreading of attention over long contexts that §25.1 measured. A history can fit in the window and still be too long for the model to use well. An interviewer who asks "why not just put everything in the context?" wants both halves of this answer, with the numbers.
The table compares the ways to represent memory that this section met.
| representation | holds | good at | weak at | cost to write | deleting one fact |
|---|---|---|---|---|---|
| raw episodic log | every message, verbatim | exact recall, audit, extracting again later | grows without bound, many distractors, no notion of current | none | easy to find, but derived memories leak it |
| extracted facts in a vector store | short atomic statements with keys | fuzzy recall of simple facts, cheap reads | relations, time, and contradictions unless the write policy handles them | an extraction and an update call per turn | easy |
| temporal knowledge graph | entities, relations, validity intervals | multi-hop and "what was true when" questions | extraction cost and errors, deciding which names are the same entity | several model calls per message | invalidate edges, keep history |
| summaries and reflections | abstractions over many episodes | global questions, compact context | lose detail, errors compound across levels | periodic batch calls | must be recomputed from sources |
| skills, workflows, playbooks | procedures, code, strategies | reuse across tasks | a bad entry is reused everywhere, so it needs verification | only after verified success | version and roll back |
Everything so far keeps memory outside the network and passes it in as text. A parallel line of work builds the store into the architecture, so that the model looks memories up with its own attention.
Memorizing Transformers (Wu et al. 2022) brought the idea to Transformers. One attention layer near the top keeps the keys and values of past tokens in a large memory that gradients do not flow into. For each query it retrieves the top stored keys by exact or approximate nearest-neighbor search and attends over them. A learned gate per head mixes that result with ordinary attention over the current segment.
Perplexity, the exponential of the average next-token loss, kept improving as the memory grew to 262K tokens. The largest gains came on rare words, such as names, citations, and function names, whose first use was too far back to fit in the local context.
The block below is a small version of that layer. Queries and keys are normalized, as in the paper, which keeps old and new keys at the same scale. Local causal attention over the current segment gives an output . A second attention over the keys retrieved from memory gives . A learned gate per head, with the logistic sigmoid and a learned scalar, combines them as . After each segment, its keys and values are appended to the memory without gradients, and the oldest are dropped once the memory is full.
The gradient reaches the gate and the query projection, but never the stored keys and values. That is what lets the memory grow far beyond what could be backpropagated through. The price is staleness. Keys written early were computed by an older version of the weights, which is one reason the paper normalizes them. That trade, a memory too large to train through in exchange for keys that drift out of date, is the usual follow-up question about this design. Memory inside the model is also the bridge to §25.5, where a layer's state becomes a memory written by gradient steps at inference.