Vibe Engines
YouTube
AI System Design

Design an Agent Memory System

Learn AI system design by building a long-term memory system for an LLM agent or chatbot step by step.

The numbers to beatshort-termcontext window (transient)long-termpersistent storemovewrite ↔ recall

The whole design, in writing

Learn AI system design by building a long-term memory system for an LLM agent or chatbot step by step. An interactive guide covering why stuffing the whole history into context fails, short-term vs long-term memory, deciding what to remember, assembling working context under a token budget, storing memories in vector + structured stores, recalling relevant memories, consolidation and forgetting, and the unhappy paths (staleness, contradictions, privacy, poisoning).

Every step of the build above, written out: the problem each piece solves, the option that was taken and the ones that were not, the numbers, and how it fails in production.

The big idea

Why do agents need a memory system?

An LLM is stateless — it only knows what's in its context window right now. So a chatbot forgets your name the moment the conversation scrolls past the window, and remembers nothing from yesterday's session. For an agent or assistant meant to know you over weeks of conversations, that's fatal: no continuity, no personalization, no learning from past interactions.

Usermany sessions
New in this step: User.

An agent memory system gives a stateless model persistent memory: it decides what to remember from each conversation, stores it outside the context window, and recalls the relevant bits into the prompt when they matter — plus consolidates and forgets so memory stays coherent and bounded. It's RAG, but the corpus is the user's own history.

What the new pieces do

Userclient
Talks to the agent across many turns and many sessions over weeks — and expects it to remember who they are and what they said.

Step 1 · Why not just keep everything?

Stuffing the history fails

The naive fix: append the entire conversation history to every prompt. It preserves everything — so why does it break down fast?

Usermany sessions
The system as it stands at this step.

Append the whole conversation history to every prompt. Why does this fail?

  1. Not punctuation. The history grows unbounded, so it eventually exceeds the context window, and even before that it makes every call slower and more expensive — and it still can't carry anything across separate sessions.

  2. A bigger window only delays the wall and makes every call costlier and slower; history still grows without bound and long context degrades attention. And a window, however large, doesn't persist across sessions at all.

  3. Unbounded history eventually overflows any window, inflates token cost and latency on every turn, and dilutes the model's attention — and once a session ends, the window is gone, so there's no cross-session persistence at all. You need to store distilled memory outside the window and recall selectively.

Appending everything fails three ways: history eventually exceeds the context window; even before that it makes every call slower and costlier (and dilutes attention over long context); and it provides no cross-session persistence — when the session ends, the window is gone. The answer isn't a bigger window; it's to distill memory, store it outside the window, and recall selectively what's relevant now.

Why this piece earns its place

The version of this that actually ships is rarely the naive one — it is a rolling window that drops the oldest turns, and that is where the quiet bug lives. Truncation is a correctness decision wearing the costume of a buffer policy: the turn you evict is often the one that set a constraint ("never email the client directly"), and nothing errors when it falls off the front. The agent simply stops honoring an instruction it was given forty turns ago, while the transcript the user is looking at still contains it — so the bug reproduces for nobody. There is a cost detail worth knowing too. An append-only prefix is the one shape prompt caching rewards, because the leading tokens are byte-identical turn after turn. The moment you summarize or reorder anything near the front, every cached prefix is invalidated and each turn pays full price again, so a naive trim can push the per-call bill up. And skip all of this when sessions are genuinely short and one-off: a rolling window is the right answer for a tool nobody comes back to.

Step 2 · Two kinds of memory

Short-term vs long-term

Human memory isn't one thing, and neither is an agent's. What are the two layers, and what does each do?

Usermany sessionsAgentLLM app
New in this step: Agent.

Split memory in two. Short-term / working memory = the context window: the recent turns and the system prompt the model sees right now — small and transient. Long-term memory = a persistent store outside the window that survives across turns and sessions. The system's job is to move the salient parts of working memory into long-term storage (write), and pull the relevant parts of long-term storage back into working memory when needed (recall). Everything else is how you do those two moves well.

Why this piece earns its place

Drawing the line in two immediately raises the first real operational question: when does something said this turn become recallable? If the writer runs at session end, and the builder only ever sees long-term memory plus the last few turns, then anything stated early in a long session is invisible to recall for the rest of it — the agent contradicts something the user told it twenty minutes ago while that line is still on screen. Short-term has to cover that gap, which means the window is not only a cost constraint, it is the read-your-own-writes layer. The split also draws a line between two blast radii, and that is the half people skip. A window belongs to one conversation and dies with it, so a turn that went badly is escaped by opening a new chat. The store belongs to the user: it is shared by every session they have open — the phone, the laptop, a job running in the background — and it follows them into every conversation they start after this one. Two of those sessions can be writing into it in the same minute, neither aware the other exists, and nobody restarts their way out of whatever lands there.

  • short-termcontext window (transient)
  • long-termpersistent store
  • movewrite ↔ recall

What the new pieces do

Agentgateway
The stateless LLM app. Each call it must be handed the right context, and after each turn decide what is worth remembering.

Step 3 · What to remember

The write path

You can't store every word (that's the failed approach). So after a turn or session, you must decide what's worth keeping. How?

AgentLLM appMemory Writerextract salient
New in this step: Memory Writer.

You can't store everything. What do you write to long-term memory?

  1. A memory-writing step (often an LLM) reads the conversation and extracts the durable, salient information — facts about the user, decisions made, preferences, key events — as concise memories, rather than dumping the raw transcript. This keeps memory small, high-signal, and useful to recall later.

  2. Storing raw transcripts recreates the unbounded-growth problem in the store and makes recall noisy. You distill: keep salient facts/events, not every word.

  3. The last message is arbitrary — the important thing to remember might be earlier. You need a step that identifies what's actually salient across the turn/session.

Run a memory writer that extracts the salient information — durable facts about the user, decisions, preferences, key events — usually with an LLM deciding what matters, and stores concise memories rather than the raw transcript. Decide when to write (after each turn, or a summary at session end) and what type (a fact vs an event). Distilling at write time keeps memory small, high-signal, and easy to recall — the opposite of dumping everything.

Why this piece earns its place

The writer is a non-deterministic component writing into durable state, and that combination is unusual enough to plan for explicitly. Change its prompt and you change what the system remembers from now on, but nothing already stored moves. Six months in, the store has strata: memories written under three generations of extractor, with different granularity, different typing conventions, different ideas of what counts as important — and recall ranks across all of them as peers, so a prompt you fixed in March is still being quoted back at the user in September. Stamp each memory with the extractor version that wrote it. That single field is what turns "we improved the prompt" into a job you can actually run: select everything the previous version produced and re-extract it. Without it there is no cohort to select, only an undifferentiated pile, and every bad write is permanent by default. The second-order effect lands on evaluation. You cannot A/B a new extractor on live traffic the way you would a ranker, because the losing variant leaves residue in real users' stores. Shadow-write it into a separate namespace and compare recall quality there before it touches anything.

  • extractsalient facts/events
  • concisenot the transcript
  • whenper turn / session

What the new pieces do

Memory Writerservice
After a turn/session, extracts the salient facts worth keeping (not the raw transcript) and writes them to memory.

Step 4 · Build the prompt

Assembling working memory

Every model call needs its context assembled from several sources under a hard token budget. What goes in, and how do you fit it?

AgentLLM appContext Builderworking memory
New in this step: Context Builder.

A context builder assembles each prompt from: the system prompt, the recent turns (short-term), and the recalled long-term memories relevant to this turn — all packed within the token budget, prioritizing the most important/relevant and truncating or summarizing the rest. This is the moment memory becomes useful: the right past facts are placed into the window right when they're needed, so the stateless model behaves as if it remembers.

Why this piece earns its place

Two things go wrong here that look like tuning and are really design. First, the budget usually gets written as a remainder — system prompt, then recent turns, then recalled memories in whatever space is left. That makes personalization a function of how verbose the last exchange happened to be: a user who pastes a stack trace silently evicts everything the agent knows about them, at exactly the point the conversation got hard. Give each section a reserve it cannot lose and let the recent turns compete for the remainder, not the other way round — and remember the completion comes out of the same window, so a packer that fills to the model limit leaves nowhere for the answer to go. Second, where you place a memory decides how it is treated. Text in the system prompt reads as instruction and is obeyed; the same sentence dropped into a retrieved-context block reads as reference and is weighed against everything else. Nobody decides this deliberately — it gets decided by whoever wrote the string concatenation — and it is the difference between a preference the agent honors and one it merely knows.

  • system + recentshort-term
  • + recalledlong-term
  • token budgetpack by relevance

What the new pieces do

Context Builderservice
Assembles each prompt: system + recent turns + recalled long-term memories, all under the token budget.

Step 5 · Where memories live

Vector + structured stores

Memories aren't all the same shape. "The user seemed frustrated last week" (fuzzy, recall by similarity) is different from "the user's name is Sam" (a fact you look up exactly). Where do they go?

Memory Writerextract salientContext Builderworking memoryProfile / FactsstructuredMemory Storevectors
New in this step: Profile / Facts, Memory Store.

Use the right store per memory type. A vector store holds embedded memories for semantic recall ("find memories related to what they're asking now") — great for episodic/fuzzy memory. A structured store / profile holds authoritative facts (name, preferences, settings) queried directly, not by similarity, so they're always correct and current. (Some systems add a knowledge graph for relationships.) Match storage to how each memory is written and recalled.

Why this piece earns its place

Two stores means every write is a small distributed transaction that you do not actually have. The extractor decides a turn updates the profile and deserves an episode; the profile write lands, the vector upsert fails or arrives a second later, and the two halves of memory now disagree with no error anywhere. Two-phase commit is the wrong size of hammer here. Decide the order instead, and pick it so a half-finished write fails in the survivable direction: profile row first, episode second, so an interrupted write costs you the story around a fact while the fact itself stays exactly right. The other order keeps the mood and loses the name, which is the version a user notices immediately. Then make the divergence detectable, because nothing on the request path will ever raise it — the consolidator already passes over the store in the background, so give it a second job: check the two sides against each other, episodes asserting a fact the profile never received, profile fields with nothing behind them. The structured side buys you one more thing — a vocabulary. A model left to fill a free-text field will write vegetarian, veggie and no meat as three values for one key; a constrained enum per field turns contradiction detection into an equality check instead of a judgment call.

  • vector storeepisodic / semantic recall
  • profileexact facts
  • graphrelationships (optional)

What the new pieces do

Profile / Factsdata
Structured, authoritative facts about the user (name, preferences, key settings) — queried directly, not by similarity.
Memory Storedata
The long-term memory: embedded memories in a vector store, recalled by semantic similarity to the current turn.

Step 6 · Pull the right memories

The recall path

At each turn, of potentially thousands of stored memories, only a few are relevant. Injecting all of them is impossible (token budget) and injecting the wrong ones misleads the model. How do you recall well?

Context Builder+ recalled memoryRecallretrieve relevantMemory Storevectors
New in this step: Recall.

Thousands of memories exist; only a few are relevant now. How do you recall the right ones?

  1. All memories won't fit the token budget and would bury the relevant ones in noise, degrading answers. You retrieve only the few relevant to the current turn.

  2. Recency alone misses relevant older memories and wastes budget on irrelevant recent ones. You want relevance to the current turn (often combined with recency and importance).

  3. Treat the memory store as a corpus: embed the current query/turn, retrieve the top relevant memories by similarity (often blended with recency and an importance score), pull exact facts from the profile, and inject just those into the context. It's retrieval-augmented generation where the corpus is the user's own history.

Recall is RAG over memory. Embed the current turn, retrieve the most relevant memories from the vector store (often blending similarity with recency and an importance score), fetch exact facts from the profile, and inject just those into the context. Of thousands of memories, only the handful that matter now reach the model — enough for continuity, few enough to fit the budget and avoid misleading noise.

Why this piece earns its place

Recall fails in two directions and only one of them is visible. A memory that should have surfaced and did not costs you a generic answer — the user shrugs, repeats themselves, and nothing in your telemetry records that anything happened. A memory that surfaces when it should not is the one that gets screenshotted: the agent volunteering something irrelevant, dated, or from a context the user did not expect it to reach into. Because the two are not symmetric, the ranker needs a floor, not just an ordering. A top-k query always returns k rows; nearest is not the same as relevant, and in a thin store the nearest memory to anything is simply whatever exists. Injecting nothing has to be a legal outcome. Worth saying too that the three signals are not equally trustworthy: recency is computed exactly, relevance is an approximation, and importance was guessed at write time by a model that had not seen the turn it would eventually be recalled for. Weight it accordingly, keep the weights in config where you can move them without a deploy, and keep how many you inject in the request, where a cheap turn and an expensive one can differ.

  • embed turnquery memory
  • rankrelevance+recency+importance
  • injectthe few that matter

What the new pieces do

Recallservice
Retrieves the memories relevant to the current turn (RAG over memory) to inject into the context.

Step 7 · Keep it coherent

Consolidation & forgetting

Left alone, memory grows forever, accumulates duplicates, and holds contradictions (the user moved cities; both the old and new fact are stored). More memory then makes recall worse. How do you keep it healthy?

AgentConsolidatorMemory WriterContext BuilderProfile / FactsMemory Store
New in this step: Consolidator. · swipe to pan the diagram

Run consolidation (a background process, like sleep for memory): summarize and merge related memories, deduplicate, resolve contradictions (newer/authoritative facts supersede older ones), and decay/forget stale or low-importance memories so the store stays bounded. This keeps recall precise and memory current — critically, an update to a fact (name change, new preference) must overwrite/supersede, not just add another contradictory memory.

Why this piece earns its place

Consolidation is the only process here that destroys information, and it does it unattended, on a model's judgment, with nobody in the loop to notice. The compounding failure is summary-of-summaries: one pass compresses a dozen episodes into a paragraph, a later pass reads that paragraph as its input and compresses again, and by the third generation something specific the user said has become a vague characterization of them that no longer resembles it. Nothing errors, and the original is gone. Carry a generation count on every memory, refuse to compress past a small number of passes, and pin high-importance items so they are never an input to a summary at all. The other thing to decide is when it runs. A nightly sweep over every user reads the whole store per user per night, so the bill scales with how much you have stored rather than how much traffic you served — a dormant account with two years of history costs the same as your heaviest user. Trigger on writes since the last run, and consolidate within a cluster of related memories rather than across the whole store.

  • merge + dedupcompress
  • supersederesolve contradictions
  • decaybound growth

What the new pieces do

Consolidatorservice
Background process that summarizes, merges duplicates, resolves contradictions and decays old memories to keep the store coherent.

Step 8 · The sharp edges

Staleness, privacy & poisoning

Memory has dangerous failure modes: recalling stale/contradictory facts, poor recall precision (right memory not surfaced, or wrong one injected), sensitive PII stored indefinitely, and memory poisoning (a user planting false "facts" the agent later trusts).

UserAgentConsolidatorMemory WriterContext BuilderRecallProfile / FactsMemory Store
The system as it stands at this step. · swipe to pan the diagram

Fight staleness with consolidation and by preferring the authoritative profile for facts (and timestamping memories). Improve recall precision with better ranking, importance scoring, and typed memories. Handle privacy: treat memory as sensitive user data — consent, encryption, retention limits, and a "forget me" that truly deletes. Defend against poisoning by not blindly trusting extracted "facts" (especially security-relevant ones), validating/attributing memories, and scoping memory per user. And watch cost — recall + write + consolidation are extra model/DB calls per interaction.

Why this piece earns its place

One control is missing from that list and it is the one that makes the rest operable: let the user see and edit what the agent remembers. Memory bugs are per-user state, so they do not reproduce. You get a report that the assistant keeps insisting on something wrong, and there is no stack trace, no failing request, nothing in the logs that differs from a healthy turn — the pipeline ran perfectly and returned a line that has been wrong since a bad extraction weeks ago. The only person who can point at which stored sentence is the wrong one is the person it is about, which makes a plain list of memories with a delete button the real support tool, and a faster repair than any of the automated defenses. It carries a second-order effect worth accepting deliberately: once memory is visible, every stored line has to survive being read by the person it describes. That does not rule out the inferred memories — a note that someone seemed frustrated last week is exactly what the vector store is for — but it changes how you write them. Store the observation, not the verdict on the person. "Asked three times for shorter answers" is something a user can read and correct; "is impatient" is something they can only resent. Same observation, two forms, and only one of them survives being shown.

You did it

You just designed an agent memory system.

UserAgentConsolidatorMemory WriterContext BuilderRecallProfile / FactsMemory Store
The finished design, end to end. · swipe to pan the diagram

Everything you assembled, in order

  • LLMs are stateless — without memory they forget across the window and across sessions.
  • Stuffing the whole history fails: it overflows the window, costs more, and never persists across sessions.
  • Split memory: short-term (context window) vs long-term (persistent store), moving salient info between them.
  • Write path: extract and store salient facts/events (an LLM decides), not the raw transcript.
  • Assemble each prompt under a token budget: system + recent turns + recalled memories.
  • Store by type (vector store for episodic/semantic recall, a structured profile for exact facts); recall is RAG over memory.
  • Consolidate and forget — merge, dedup, resolve contradictions, decay — so memory stays coherent, current and bounded.

Deep cut · 22:17

Told it three times. It kept the lie.

The interactive build above lays out an agent memory system: a memory writer that keeps salient facts instead of transcripts, a vector store for episodes and a structured profile for exact facts, a context builder that packs each prompt under a token budget, recall ranked by relevance, recency and importance, and consolidation that merges, supersedes and forgets. The film builds the same memory one fix at a time in a made-up assistant’s treehouse, then replays one birthday from the log to find why an assistant corrected three times still believed a line said once.

  • See the wreck: the profile kept its first answer — a line, once written, stayed — and the first note to fill “diet” was a cousin’s joke in a shared document. The user’s three corrections were hung as episodes instead, merged into one, faded and ranked seventh, while the profile went into every prompt first, trusted. Deleting the chat left the note behind.
  • Take it into the interview: every memory carries its source; facts about the user come only from the user; a newer statement supersedes the old one with its history kept; content the assistant reads never writes the profile; and forget-me follows the sources into every derived copy.

Where an interviewer pokes next

Getting the boxes right is the easy half. These are the questions that separate a candidate who drew the diagram from one who has run the thing. Answer each one out loud before you open it.

  1. Why can't you just give the model a bigger context window instead of a memory system?

    Because a bigger window addresses the wrong problem and hits fundamental limits. First, conversation history grows without bound over weeks of interaction, so any fixed window — however large — eventually overflows; you've only delayed the wall. Second, even before overflow, every token you keep in context is paid for on every single call in both latency and money, so carrying a huge running history makes each turn progressively slower and more expensive, and very long contexts also dilute the model's attention, degrading answer quality. Third and most decisively, a context window is inherently per-session working memory: when the session ends, the window is gone, so a bigger window provides exactly zero persistence across sessions — the agent still can't remember you tomorrow. A memory system solves the actual need by distilling durable information out of the transient window into a persistent store, and then recalling only the relevant pieces back into context when they matter, which gives cross-session continuity at bounded cost. The window is working memory; real memory has to live outside it.

  2. What should the write path store, and why not the raw transcript?

    The write path should store distilled, salient information — durable facts about the user, decisions that were made, stated preferences, and important events — rather than the verbatim conversation, and it typically uses an LLM to decide what actually matters. Storing raw transcripts would recreate the very unbounded-growth problem you're trying to escape, just relocated from the context window into the memory store, and it would make recall noisy because most of the transcript is filler that isn't worth remembering. By extracting concise memories at write time — for example turning fifty messages into "user prefers Python, is building a startup, dislikes verbose answers" — you keep the store small and high-signal, which makes every downstream operation better: recall retrieves cleaner, more relevant memories; the token budget goes further; and consolidation has structured, typed items (facts vs events vs preferences) to merge and reconcile. You also decide when to write (after each turn for immediacy, or a summary at session end for a coherent digest). In short, the quality of the entire memory system is largely set at the write step, because it determines what the agent even has the chance to remember and how cleanly it can recall it.

  3. Why store memories in both a vector store and a structured profile?

    Because memories come in fundamentally different shapes that are recalled in different ways, and using one store for both hurts. Episodic and fuzzy memories — "the user seemed frustrated last week," the gist of a past discussion — are recalled by relevance to the current situation, so they belong in a vector store where you embed them and later retrieve the ones semantically similar to the current turn. Authoritative facts — the user's name, their language preference, a key setting — are different: you need them to be exactly correct and current, and you look them up directly rather than by similarity. If you forced such facts through similarity search, the user's current name could depend on a fuzzy match that returns a stale or wrong value, which is unacceptable for something that should be deterministic. So you keep a structured store or profile for exact, authoritative facts queried directly, and a vector store for episodic/semantic memories recalled by similarity (some systems add a knowledge graph for relationships between entities). Matching each memory's storage to how it's written and how it will be recalled is what keeps facts reliable and fuzzy recall flexible at the same time.

  4. How does recall decide which few memories to inject, and why not inject them all?

    You can't inject them all for two reasons: thousands of memories would blow the token budget, and even if they fit, flooding the context with mostly-irrelevant memories buries the useful ones in noise and actively degrades the model's answers. So recall is treated as retrieval-augmented generation where the corpus is the user's own history: you embed the current turn, retrieve the most relevant memories from the vector store, and inject only those, alongside exact facts pulled directly from the profile. Crucially, pure semantic similarity isn't sufficient on its own — good recall ranks candidates by a blend of relevance to the current turn, recency (a recent memory is often more pertinent than an old one), and an importance score assigned when the memory was written (some facts matter more than others). This weighted ranking surfaces the handful of memories that genuinely help the current turn while leaving out the rest, giving the stateless model enough past context to appear continuous without exceeding the budget or misleading it with stale or off-topic recollections. Recall is the read side of the system, and its ranking quality determines whether memory helps or hurts.

  5. Why is consolidation (and forgetting) essential rather than just accumulating memories?

    Because an append-only memory that never consolidates gets worse as it grows, not better. Three problems compound over time: the store bloats without bound, which slows and dilutes recall; duplicates accumulate as the same fact is extracted repeatedly; and contradictions pile up because when a fact changes — the user moves from New York to Berlin — a naive system simply stores the new fact alongside the old one, and similarity recall will happily return both as equally true, so the agent surfaces stale or conflicting information. Consolidation, which is often likened to what sleep does for human memory, runs in the background to keep the store healthy: it summarizes and merges related memories (compressing many episodes into a coherent summary), deduplicates, resolves contradictions by letting newer or authoritative facts supersede older ones rather than coexist with them, and decays or forgets stale, low-importance memories so the store stays bounded. The contradiction-resolution point is critical: an update to a fact must overwrite or supersede the old value, not just add another entry. Done well, consolidation keeps recall precise and memory current, so more history makes the agent smarter; without it, more memory slowly poisons the agent with noise and contradictions — which is why forgetting is a feature, not a bug.

Check yourself — the answers, and why

Nine steps in, these are the calls you should be able to make cold. Pick one, then read why.

  1. Agents need a memory system because LLMs are…

    Persistent memory outside the window gives continuity the stateless model lacks.

  2. Stuffing the whole history into context fails because…

    The window is transient working memory; real memory must live outside it and be recalled selectively.

  3. The write path should store…

    Distilling keeps memory small and high-signal; dumping transcripts recreates the growth problem.

  4. Recall is essentially…

    Embed the turn, rank memories, inject just the handful that matter; pull exact facts from the profile.

  5. Consolidation/forgetting is needed to…

    Without it, memory bloats and contradicts itself, so more memory makes the agent worse.

How you’d open this design in an interview

Before any boxes: agree what it must do, pin the qualities that shape everything, then build — naming each trade-off as you make it. The walkthrough above is that exact order.

What it must do

Agree on these before drawing a single box.

  • Remember: after each turn/session, decide what’s worth keeping and write it to long-term memory.
  • Assemble context: build each prompt from system + recent turns + recalled memories under a token budget.
  • Recall: retrieve the few memories relevant to the current turn.
  • Store by type: episodic memories in a vector store, exact facts in a structured profile.
  • Stay coherent: consolidate — merge, dedup, resolve contradictions, decay — so memory stays bounded.

The qualities that shape everything

Each one names the mechanism that buys it.

Continuity across sessions, not just one chat
Move the salient parts of the context window into a persistent long-term store outside it — short-term is the window, long-term survives across sessions.
Memory stays small and high-signal
A memory writer extracts durable, salient facts/events (an LLM decides what matters), not the raw transcript.
Every prompt fits the token budget
A context builder packs system + recent turns + recalled memories, ranked by relevance and importance, truncating the rest.
Facts stay exactly correct while fuzzy recall stays flexible
A vector store for episodic/semantic recall plus a structured profile for exact facts queried directly, not by similarity.
Only the relevant few memories reach the model
Recall is RAG over memory: embed the turn, rank by relevance + recency + importance, inject just the handful that matter.
More memory doesn’t make the agent worse
Consolidation merges duplicates, resolves contradictions (newer facts supersede older), and decays stale memories to keep the store bounded.

The trade-offs you say out loud

Senior signal isn’t the boxes — it’s naming what you gave up and why it was the right price.

A memory system over a bigger context window

A bigger window only delays the wall, costs more per call, and dilutes attention — and a window is per-session, so it never persists across sessions at all.

Distill salient memories over storing the raw transcript

Raw transcripts recreate the unbounded-growth problem in the store and make recall noisy. Extracting concise facts/events keeps memory small and high-signal.

Recall the relevant few over injecting all memories every turn

All memories blow the token budget and bury the useful ones in noise. Rank by relevance/recency/importance and inject only the handful that help this turn.

Vector store + structured profile over one store for everything

Forcing exact facts through similarity search makes the user’s current name depend on a fuzzy match. Type each memory and store it by how it’s written and recalled.

Consolidate and forget over appending every fact forever

Append-only memory bloats, duplicates, and holds contradictions similarity recall returns as equally true. Merging, superseding and decaying keep recall precise — forgetting is a feature.

The answer, out loud

What a strong answer to “Design an Agent Memory System” sounds like, first question to last trade-off. It is about 7 minutes of talking; the whiteboard and the interviewer fill the rest of the 45. Read it aloud once, then close the page and give it yourself.

  1. 0-3 min

    Pin down what remembering has to mean

    Before I draw anything I want to be clear about what we are building. The model is stateless — it knows what is in its context window and nothing else — so a chatbot forgets your name once the conversation scrolls past it, and remembers nothing from yesterday. For an assistant meant to know you over weeks, that is fatal. So the requirement is continuity across sessions, personalization that survives, and memory that does not get worse as it grows. That last clause is the one people leave out, and it shapes half the design.

  2. 3-8 min

    Rule out the bigger window first

    The obvious first answer is a bigger context window, and I would rule it out on the record, because size is the wrong axis — the problem is scope. A window belongs to one session. When that session ends, so does everything in it, and tomorrow the agent meets the user as a stranger again. Inside a session it degrades as it fills, too: I re-read and re-pay for every token I carry on every call, and a long context spreads the model's attention thin. I want the opposite curve — an agent that gets better the longer it has known you.

    Built in step 1: Stuffing the history fails
  3. 8-13 min

    Split the window from the store

    So I would split memory in two. Short-term is the context window — the system prompt and the recent turns, small and transient. Long-term is a persistent store outside it that survives turns and sessions. Then the whole system reduces to two moves: write, which pushes the salient parts of working memory into the store, and recall, which pulls the relevant parts back in when they matter. It is the memory hierarchy — a tiny fast working set backed by a large persistent one you page from, and the rest of the design is those two moves done well.

    Built in step 2: Short-term vs long-term
  4. 13-19 min

    Decide what is worth keeping

    The write path first. After a turn or a session something decides what is worth keeping, and it is not the transcript — raw messages just relocate the unbounded-growth problem from the window into the store, and make recall noisy on top of it. So I would run a memory writer, usually an LLM, that extracts the durable material: facts about the user, decisions, stated preferences, key events, as concise typed memories. I would also decide when it runs — after each turn, or a digest at session end — and keep it off the critical path, because nothing needs it until the next session.

    Built in step 3: The write path
  5. 19-25 min

    Two stores, because memories are not one shape

    Memories are not all the same shape, so I would not use one store. The user seemed frustrated last week is fuzzy and recalled by similarity — that belongs in a vector store. The user's name is Sam is a fact I need exactly right, and I look it up directly, so it belongs in a structured profile, queried rather than matched. I insist on the split because I do not want the user's current name to depend on a fuzzy match landing on the right row. A knowledge graph for relationships is an option on top, not a requirement.

    Built in step 5: Vector + structured stores
  6. 25-29 min

    Assemble the prompt under a budget

    Now the read side. On every call a context builder assembles the prompt: the system prompt, the recent turns, and the recalled long-term memories relevant to this turn, packed under the token budget, ranked by relevance and importance, with the rest truncated or summarized. This is the moment memory becomes useful — the right past fact lands in the window exactly when it is needed, so a stateless model behaves as if it remembers. Given the budget, I would rather spend it on two memories that clearly matter than ten that might.

    Built in step 4: Assembling working memory
  7. 29-35 min

    Recall the handful that matter

    Recall itself is RAG where the corpus is the user's own history. I embed the current turn, retrieve the most relevant memories from the vector store, and pull exact facts straight from the profile. I would not rank on similarity alone — I would blend relevance with recency, because a recent memory is usually the more pertinent one, and with an importance score assigned at write time. Injecting everything is not an option: it will not fit, and even if it did, it buries the useful memories in noise. A handful is enough for continuity and few enough not to mislead.

    Built in step 6: The recall path
  8. 35-40 min

    Consolidate, and let it forget

    Then the part that keeps it alive. Left alone, memory accumulates duplicates and contradictions — the user moved cities, both facts are stored, and similarity recall returns them as equally true. So a background consolidator merges related memories, deduplicates, resolves conflicts by letting the newer authoritative fact supersede the older one rather than sit beside it, and decays stale or low-importance memories so the store stays bounded. I would say the superseding part out loud, because an update that merely appends is the bug. That is what makes more memory make the agent worse. Forgetting is a feature here, not a compromise.

    Built in step 7: Consolidation & forgetting
  9. 40-45 min

    The sharp edges and the trade-off

    Then the sharp edges, ranked rather than listed. A mistake here compounds: a bad retrieval is one bad answer, a bad memory is a bad answer every day until somebody removes it. So the defenses I would name first shorten a wrong line's life: a timestamp on every memory, so age is visible at recall time, and an extracted fact held as a claim rather than as truth, because a user can plant one deliberately and the sentence itself will not say so. Privacy falls out of the same property — a durable record of a person. The trade-off underneath it all is remembering more against recalling precisely, and with more time I would let the agent ask instead of assume, so a shaky memory becomes a question.

    Built in step 8: Staleness, privacy & poisoning

What this teaches

Learn AI system design by building a long-term memory system for an LLM agent or chatbot step by step. An interactive guide covering why stuffing the whole history into context fails, short-term vs long-term memory, deciding what to remember, assembling working context under a token budget, storing memories in vector + structured stores, recalling relevant memories, consolidation and forgetting, and the unhappy paths (staleness, contradictions, privacy, poisoning).

Key takeaways

  • LLMs are stateless — without memory they forget across the window and across sessions.
  • Stuffing the whole history fails: it overflows the window, costs more, and never persists across sessions.
  • Split memory: short-term (context window) vs long-term (persistent store), moving salient info between them.
  • Write path: extract and store salient facts/events (an LLM decides), not the raw transcript.
  • Assemble each prompt under a token budget: system + recent turns + recalled memories.
  • Store by type (vector store for episodic/semantic recall, a structured profile for exact facts); recall is RAG over memory.
  • Consolidate and forget — merge, dedup, resolve contradictions, decay — so memory stays coherent, current and bounded.

Concepts covered

  • Why do agents need a memory system?
  • Stuffing the history fails
  • Short-term vs long-term
  • The write path
  • Assembling working memory
  • Vector + structured stores
  • The recall path
  • Consolidation & forgetting
  • Staleness, privacy & poisoning
RUN IT YOURSELF

What pinning throws away to keep one rule

One conversation, one token budget, three compaction policies running for real in your browser: drop the oldest turns, summarize everything into a digest, or pin the durable rules and summarize the rest. It asks one generated question per turn — no hand-picked set to flatter a winner — and prints the uncomfortable result: at a uniform query mix, pinning answers fewer questions than plain summarizing. What it buys is the three standing rules, and the printed break-even is the exchange rate. Change CONTEXT_BUDGET, hit Run, and watch that rate move — and then vanish.

HOW TO READ THE CODE — 5 IDEAS
  1. A rolling window is a correctness decision in a buffer policy's costume: it keeps a contiguous suffix, so a rule set at the start is gone and nothing errors — 9 of 16 turns, 1 of 3 rules (step 1).
  2. The summarizer scores a turn by how often its words recur. The routing rule is the wordiest turn in the conversation and ranks 15th of 16, because its words are used once — which is exactly what "said once" looks like to term frequency.
  3. Gisting is the real win: the same 85 tokens cover 13 turns as gists versus 9 verbatim (step 3, distill don't dump). Pinning then spends 28 of those tokens on 3 verbatim rules and drops back to 12 — it loses on raw coverage, 12 against 13.
  4. So pinning is a trade, not a free win (steps 5 and 7): 3 episodic turns given up to buy 2 rules. It pays only once a standing rule is queried more than 1.5× as often as a detail — a number set by the budget and the gist size, not by the question list.
  5. Above ~110 tokens the break-even reads never: the digest reaches every rule unaided, so pinning is pure cost. That ceiling is the honest boundary of the claim, and it is why this is a small-context mechanism.
CPython · WebAssembly
built to remember you across a thousand chats without drowning in every word — make the calls, wipe the memory, run the gauntlet.
Finished this one? 0 / 65 AI System Designs done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More AI System Designs