Agent Memory Systems That Persist State
Model calls forget everything between sessions. Agent memory systems file distilled facts outside the window and pull back only the top match for the task at hand.
Why Does This Exist?
A model call is stateless. It reads exactly the tokens in its window and keeps none afterward. For one question that hardly matters. For an agent serving the same user for weeks, it breaks everything: preferences restated every Monday, fixes relearned every sprint, addresses re-asked after a move.
The naive fix pastes old chats into every prompt until the window bursts. Six past sessions at 500 tokens each plus 400 of system and 700 of task want tokens against a 4000 window, over by 100. Memory systems replace that paste with a file and a pull: write distilled facts to durable storage once, retrieve the top match per task, and leave the rest filed.
This page covers the stores, the write decision, and the retrieve step. What each type is for lives in agent memory types. The loop that spends the assembled prompt lives in agent architectures, and the plans that survive across sessions live in long-horizon planning. The general short versus long-term split lives in memory systems, while the episode-to-fact distillation lives in episodic and semantic memory.
Basics first: the context window is the maximum tokens one call can read, prompt plus reply together. Retrieval usually ranks by similarity over embeddings. If windows are new, the worked example shows the token arithmetic before any jargon arrives.
Think of It Like This
A contractor with a tool belt, a site diary, and a materials catalog
A contractor arrives each morning with three holders. The tool belt carries today's tools: hammer, tape, pencil. Small, fast, emptied nightly. That is short-term memory. A site diary on the truck holds dated notes: the north wall ran damp last March, the fix that held in June. That is episodic recall. A materials catalog in the office holds undated facts: load ratings, drying times, part numbers. That is semantic storage.
The day works because the contractor consults all three without confusing them. The belt answers what is in hand now. The diary answers what happened on this site before. The catalog answers what is true everywhere. Filing every coffee chat into the diary would bury the damp-wall note, and carrying the whole catalog on the belt would stop all work.
Where it stops: paper never contradicts itself, but stored agent facts do. The client moves, tastes change, and two filed notes disagree with equal confidence. A catalog never charges rent per page, while every retrieved line costs window space, so pulling the whole drawer each morning buries the task it meant to help.
How It Actually Works
Call the window: the most tokens one request may hold. Call the live contents: system instructions, recent turns, and whatever retrieval just added. Call the durable pile: one row per saved fact with its source. A write moves a fact from into . A retrieve moves a few rows from back into for one request. Saved and used are two steps, and skipping the second builds a store nobody reads.
The write is a durability judgment
A one-off detail, like renaming one variable today, belongs in and dies there. A repeated preference, like pytest over unittest stated twice and confirmed by edits, earns a row in with its source episode. When in doubt, do not write. Every extra row taxes every future search. The step loop that produces these facts lives in ReAct, and the milestone state it must survive lives in long-horizon planning.
Retrieval ranks facts against today
Call the current task : the query for what you need right now. Call each stored fact : fact number as a vector. Call its score against . Call how many you pull back: usually one or two. You rank by and copy only the top rows into .
Worked example: three facts ranked, one budget checked
Take three stored facts as two-dimensional vectors so you can check the math. Real stores use hundreds of dimensions. The arithmetic is the same. Here stands in for writing a pytest fixture, is prefers pytest, is deploys on Fridays, and is likes demo GIFs.
Score with cosine similarity, which is alignment divided by lengths. The length of is . Lengths: , same for and . . Dots: . . . Divide: . . . Ranked, beats beats . With you pull back the first two. The diagram shows these three scores and the two returning arrows.
Now the token reason this matters. Name . System needs 400 tokens and today's task needs 700. Pasting six old sessions at 500 tokens each costs , so the request wants . Overflow is . The store instead holds 20 facts at 12 tokens each: tokens total. Pulling the top 2 costs 24. The request becomes , leaving spare. Same history, two shapes: one never fits, one barely registers.
That trade assumes past choices predict future ones. When the user switches runners or moves cities, old rows lie until a new episode overwrites them.
Code
The snippet reruns the ranking and the token fit, so you can check both numbers yourself:
import math
def cosine(u: list[float], v: list[float]) -> float: dot = sum(x * y for x, y in zip(u, v)) nu = math.sqrt(sum(x * x for x in u)) nv = math.sqrt(sum(x * x for x in v)) return dot / (nu * nv)
query = [2.0, 1.0]mems = [[2.0, 1.0], [1.0, 2.0], [0.0, 2.0]]print([round(cosine(query, m), 4) for m in mems]) # -> [1.0, 0.8, 0.4472]print(400 + 700 + 6 * 500 - 4000) # -> 100print(20 * 12) # -> 240print(400 + 700 + 24) # -> 1124Four outputs match the worked example: the three scores, the 100-token overflow, the 240-token store, and the 1124-token request.
Watch Out For
Filing every turn as a permanent fact
The symptom is a store that grows by hundreds of rows a week while retrieval starts returning one-off details with the same confidence as real preferences. A query about tests pulls back a renamed variable from March alongside the pytest preference, and the model treats both as instructions.
Fix it by writing distilled facts, not transcript lines. Require a second confirmation before a write, keep the source episode on every row, and overwrite rivals instead of appending beside them.
Pulling the whole store into every prompt
The symptom is session twelve opening with all forty stored facts pasted in just to be safe. The prompt costs more, answers slower, and the one fact today needed now competes with thirty-nine it did not.
Fix it by retrieving top only, usually one or two, ranked against today's task. Leave the rest filed until some query actually calls for them.
The Quick Version
- Short-term memory is the live window. It costs nothing to read and it vanishes when the session ends.
- Long-term memory is facts filed on purpose outside the window. Filed facts do nothing until retrieval copies them back.
- Three facts scoring 1.0, 0.8, and 0.4472 retrieve the top two, turning a 3000-token paste into a 24-token pull.
- A 4100-token paste overflows a 4000 window by 100, while an 1124-token memory request fits with 2876 spare.
- Twenty 12-token facts cost 240 tokens total, a fraction of one pasted session.
- Writes should record repeated preferences, not one-off details, and old rows must be overwritten when the world changes.