Agent memory is the record of what an AI agent learned in past sessions, kept as text it can load again later. Anthropic measured what that record is worth on 6 November 2026: with memory, Claude Opus 5.5 scored 0.838 on the company's Continual Learning Bench, up from 0.655 without it. We build a working version in Python below.

Key takeaways

  • Anthropic splits agent memory into four layers: transcripts, notes, representations such as wikis, and a small block of injected context.

  • Memory lifted every recent Claude model on CL-Bench. Older models gained most: Opus 4.7 rose from 0.383 to 0.700.

  • Offline "dreaming" passes fix wrong notes. In a Pokémon Red run, a corrected note let the agent reach Cerulean City, which the wrong note had blocked.

  • A wiki plus search beat search alone (61% vs 53%) on a 350,000-file corpus, and saved about 20,000 tokens per question.

  • The always-loaded layer must stay small. Claude Code loads only the first 200 lines or 25 KB of its MEMORY.md index, because one wrong note spreads into every session.

  • You can build all four layers in about 100 lines of Python, with a dreaming pass that checks notes against the transcript.

What did Anthropic actually measure?

The post, "The components of agent memory" by Anthropic engineer Lance Martin, is an engineering report, not a product launch. It defines memory narrowly: information learned from interactions or tasks, saved as readable context for later sessions. Model weights, learning inside a single conversation, and compaction summaries are all excluded. What counts is what survives between sessions as text.

The headline test is the Continual Learning Bench (CL-Bench) 1.0, a database task where the agent answers questions and the environment reveals the correct result after each one. Every question starts in a fresh session, so anything the agent carries over must come from its memory files. The results, reported by Anthropic on 6 November 2026:

ModelWithout memoryWith memory

Claude Opus 5.50.6550.838
Claude Sonnet 5.50.6440.833
Claude Opus 4.70.3830.700
Claude Sonnet 50.3060.610

Two details matter. First, older models gained most, which suggests memory compensates for weaker reasoning by supplying facts the model would otherwise have to re-derive. Second, Sonnet 4.6 barely moved until it received refined instructions about how to use memory, which then lifted it to 0.641. Memory is not free performance. Anthropic's rule is to state what memory is for without scripting how to use it.

The most vivid test is not a benchmark at all. Anthropic ran Claude through Pokémon Red, a game that punishes bad navigation over thousands of steps. Sonnet 3.7 wrote 413 memory files across 225 folders, and one note logged the wrong side of the map for a PokéCenter near Mt. Moon. The agent walked off a one-way ledge and into a loop it could not escape. An offline dreaming pass checked the note against the transcript, caught the error, and rewrote it. With the correction, the agent reached Cerulean City at action 1,935. Without it, the run never got past the mountain.

The third test is the wiki. Anthropic built one over roughly 350,000 internal files from Stainless, a company it acquired in May 2026, totalling about 420 million tokens, using 128 sequential Opus 5 sessions and a 25-question evaluation. As dreaming grew the wiki from two sessions to 128, performance rose from 13% to 48% of graded criteria. Search plus the wiki reached 61% against 53% for search alone, and saved about 20,000 tokens per question. On questions needing synthesis, judgement or arithmetic, wiki-based answers scored 87% against 73% with search alone.

How do the four layers fit together?

The design is a hierarchy, with each layer doing one job. At the bottom sit transcripts: the raw record of conversations and activity. They are complete and searchable, but they do not put the right information in front of the model. Searching a transcript finds the first match, not the true one, as the demo below shows.

Above them are memory notes: short lessons, rules and plans the agent writes for itself. Notes are compact, but their quality depends on the agent's judgement about what deserves to survive. A note written from a wrong first impression stays wrong until something checks it.

The third layer holds memory representations: organised structures such as a linked wiki. A representation gives the agent a map for navigating a growing body of knowledge, which is why the wiki helped most on questions spanning several documents and showed no clear gain on single-document tasks.

At the top is injected context: a small amount of material loaded into every session, so the agent does not need to search for it. It must stay small and reliable, because it consumes tokens in every session. Claude Code loads only the first 200 lines or 25 KB of its MEMORY.md index when a session starts.

Dreaming runs between the layers. When the agent is idle, an offline pass reads past transcripts and current memory, then rewrites wrong notes, merges duplicates, adds links and trims what gets loaded into every session. Anthropic's Dreams API runs this as an asynchronous job over one to 100 session transcripts. The idea borrows from sleep-time compute, proposed by researchers at Letta and UC Berkeley in April 2025: models use quiet periods between interactions to reorganise what they already know.

Can you build this yourself in an afternoon?

Yes, and the failure modes show up even in a toy. I built a minimal four-layer memory in Python, using only the standard library, and ran it on a scripted three-session scenario: a coding agent connects a billing service to Postgres, gets the port wrong on its first attempt, then writes notes. One note records the wrong port, and another is an exact duplicate. Here is the complete program:

import json import re TRANSCRIPT = [ {"id": "e1", "session": 1, "text": "User asks: connect the billing service to Postgres and add a retry."}, {"id": "e2", "session": 1, "text": "Tried port 5432 first; connection refused."}, {"id": "e3", "session": 1, "text": "Checked docker-compose.yml: Postgres actually listens on 5433."}, {"id": "e4", "session": 2, "text": "Connected on port 5433. Retry loop added with backoff 2s, 5 attempts."}, {"id": "e5", "session": 2, "text": "DECISION: retries use exponential backoff, max 5 attempts."}, {"id": "e6", "session": 2, "text": "Test run: 3 retries then success on the 4th attempt."}, {"id": "e7", "session": 3, "text": "FACT: the billing API key lives in BILLING_API_KEY, not in .env.local."}, {"id": "e8", "session": 3, "text": "Shipped. Noted for next session: verify the key name before rotating."}, ] NOTES = [ {"id": "n1", "text": "Postgres runs on port 5432.", "source": "e2"}, {"id": "n2", "text": "Retries use exponential backoff, max 5 attempts.", "source": "e5"}, {"id": "n3", "text": "Billing API key is BILLING_API_KEY.", "source": "e7"}, {"id": "n4", "text": "Postgres runs on port 5432.", "source": "e2"}, {"id": "n5", "text": "Verify the key name before rotating credentials.", "source": "e8"}, ] def dream(transcript, notes): report = {"duplicates_removed": [], "corrected": []} seen, unique = {}, [] for note in notes: key = re.sub(r"\W+", " ", note["text"].lower()).strip() if key in seen: report["duplicates_removed"].append((note["id"], seen[key])) else: seen[key] = note["id"] unique.append(dict(note)) # For a claim about a port, the LATEST transcript mention wins.  mentions = [(ev["id"], m.group(1)) for ev in transcript for m in [re.search(r"port (\d+)", ev["text"])] if m] latest = mentions[-1][1] for note in unique: m = re.search(r"port (\d+)", note["text"]) if m and m.group(1) != latest: old = note["text"] note["text"] = note["text"].replace(m.group(1), latest) report["corrected"].append((note["id"], old, note["text"])) index = {} for note in unique: for word in re.findall(r"[a-z]{4,}", note["text"].lower()): index.setdefault(word, []).append(note["id"]) return unique, index, report notes, index, report = dream(TRANSCRIPT, NOTES) t_words = sum(len(e["text"].split()) for e in TRANSCRIPT) injected = "\n".join(f"- {n['text']}" for n in notes) i_words = len(injected.split()) print(f"Transcript: {len(TRANSCRIPT)} events, {t_words} words") print(f"Notes before dreaming: {len(NOTES)}") print(f"Duplicates removed: {len(report['duplicates_removed'])} {report['duplicates_removed']}") for nid, old, new in report["corrected"]: print(f"Corrected {nid}: {old!r} -> {new!r}") print(f"Notes after dreaming: {len(notes)}") print(f"Index topics: {len(index)} keywords ({', '.join(sorted(index)[:8])}...)") print(f"Injected context: {i_words} words vs full transcript {t_words} words ({t_words / i_words:.1f}x smaller)") print("\n--- Injected context for the next session ---") print(injected) 

The dreaming pass merges exact duplicates, checks each note's port claim against the transcript and rewrites it when a later event contradicts it, then rebuilds the keyword index from the surviving notes. The checker is deterministic pattern matching, not a model call, so be clear about what this proves: the architecture works, not the judgement. A production dreamer swaps the regex for an LLM judge and keeps the same shape. Here is the actual output of running it:

Transcript: 8 events, 77 words Notes before dreaming: 5 Duplicates removed: 1 [('n4', 'n1')] Corrected n1: 'Postgres runs on port 5432.' -> 'Postgres runs on port 5433.' Notes after dreaming: 4 Index topics: 13 keywords (attempts, backoff, before, billing, credentials, exponential, name, port...) Injected context: 28 words vs full transcript 77 words (2.8x smaller) --- Injected context for the next session --- - Postgres runs on port 5433. - Retries use exponential backoff, max 5 attempts. - Billing API key is BILLING_API_KEY. - Verify the key name before rotating credentials. 

The retrieval check is the part worth pausing on. Ask "which port does Postgres use?" and a keyword search over the raw transcript returns the first hit, event e2: "Tried port 5432 first; connection refused." That is the stale first attempt, and it is the wrong answer. Search the dreamed notes instead and you get the corrected note: "Postgres runs on port 5433." The transcript layer preserves everything, including the mistakes. The notes layer is where truth is supposed to live, and dreaming is what keeps it honest. That is the same shape as Anthropic's Pokémon result, at a much smaller scale. Note the compression too: the injected context is 28 words against a 77-word transcript, so the next session starts with four verified facts instead of eight raw events.

What goes wrong when memory is left unchecked?

Anthropic lists the failure modes plainly. Stale notes are the first: a wrong note misleads every session that loads it, which is why the injected layer must stay small and verified. Wikis can bloat: one wiki in the tests grew to 1.77 times the size of its source documents, turning the map into a second haystack. Concurrent writers are another hazard: when several agents write memory at once, contract facts were dropped. And a wiki is not always the answer: on single-document questions it showed no clear gain over plain search, so the extra machinery only pays off when questions span many documents.

There is also a cost question Anthropic leaves open. Dreaming consumes compute, and the post names the open problems directly: how often to dream, how to handle notes that go stale, and what the compute bill looks like at scale. No pricing for the Dreams API is named. Treat dreaming as a scheduled job, like a nightly backup, not as something that runs after every session.

What is still unknown?

Keep vendor claims separate from independent verification. CL-Bench is Anthropic's own benchmark, and several results in the post are single runs, which the company says openly. The 25-question wiki test is small and private, so the 61% versus 53% gap is a signal, not a settled number. Dreaming is not a general product feature: the post describes research setups on Managed Agents and Claude Code's auto memory, but it announces no general release. And the models tested are Claude models, so whether the same four layers give the same gains elsewhere is untested here.

If you are building agents that work across sessions, follow the order of the layers. Start with notes and a small injected file in the style of Claude Code's MEMORY.md, kept under 200 lines or 25 KB so a wrong note cannot hide in it. Add a dreaming pass as a nightly script that checks notes against transcripts and merges duplicates. Add a wiki only when your corpus is large and your questions genuinely span documents. That is the shape Anthropic measured, and the demo above shows it running, with real output, in about 100 lines. For the agent that uses this memory, see our guide to building your first AI agent with LangGraph.

Originally published on LogicStack. Run the code examples right in your browser with our free interactive Python and SQL playgrounds — no setup, no sign-up.