The memory systems tour: from MemGPT to your window

TL;DR. Every memory system in the ecosystem answers three questions: what gets extracted from a conversation, where it lives, and what lands in your window per turn, and only the third question costs money. This chapter opens up the well-known open-source systems along that axis: Letta (the MemGPT lineage: self-editing core blocks, archival search, memory pressure), mem0 (the extract-and-update pipeline, its famous paper, and its 2026 pivot to add-only), the knowledge-graph stores (Graphiti, cognee, and the tiny MCP reference server), LangMem's memory taxonomy, and Anthropic's own pair (the client-side memory tool and context editing). A from-scratch lab reproduces the MemGPT loop in 150 lines: append, replace on contradiction, evict under pressure, retrieve on demand, quiz 4/4 from a fresh window at 78 tokens versus 292 for transcript replay. Each system ends with the practical wiring for Claude Code.

Contents

Chapter 9 built the four operations (extract, store, retrieve, invalidate) and named the big three systems. Chapter 40 priced the loading rules. This chapter goes inside the real projects: how each one actually works, what each one puts in your window, and how each one bolts onto Claude Code. Facts below were checked against the projects' repositories, docs, and papers in July 2026; version numbers and star counts are that snapshot.

The three questions, and the two camps

Strip the marketing from any memory system and three design questions remain:

  1. Extraction. What becomes a memory: raw turns, LLM-extracted atomic facts, graph entities and relations, or files the agent writes deliberately?
  2. Storage. Where it lives: a vector store, a relational table, a knowledge graph with timestamps, or markdown on disk?
  3. Window contract. What lands in the context per turn, and who decides? This is the only question with a per-turn bill attached, and the one this book cares about most.

The third question splits the field into two camps:

  • Pipeline memory. Your code (or the vendor's) runs extraction and retrieval around the model. The model never knows memory exists; it just receives a few injected facts. mem0, Graphiti, and LangMem's background mode work this way. Predictable cost, no agent effort, but the retrieval rule is fixed by the developer.
  • Agentic memory. The model gets tools: read this, write that, search the store. The agent decides what to remember and what to recall, turn by turn. Letta, Anthropic's memory tool, the MCP memory servers, and Serena's memories work this way. Adaptive, and the agent can manage its own working set, but you are trusting the model to page well, and every memory op costs a tool round-trip.

Don't be confused. Around Claude specifically, three different things are all called "memory," and they are different products at different layers. Auto memory is a Claude Code feature: Claude writes markdown under ~/.claude/projects/<project>/memory/ and loads the index each session (Chapter 18). The memory tool is an Anthropic API feature: a tool type (memory_20250818) your own application handles, with the files stored on your side. Memory MCP servers (OpenMemory, the reference server, Graphiti's) are third-party stores any MCP client can mount, Claude Code included. Same word, three mechanisms, three owners.

The lab: the MemGPT loop from scratch

The MemGPT paper (Packer et al., 2023, "MemGPT: Towards LLMs as Operating Systems") supplied the field's central metaphor: the window is RAM, external storage is disk, and the agent pages data between them itself, prompted by "memory pressure" interrupts as the window fills. The lab reduces that to its mechanics: a small always-in-window core the agent edits in place, an unbounded archival store reached by search, a recall log of past turns, and eviction when the core budget overflows.

"""A MemGPT-style memory hierarchy from scratch: core, archival, recall.

The idea from the MemGPT paper (Packer et al., 2023), reduced to its mechanics:
treat the context window like RAM and the store like disk, and give the agent
TOOLS to move facts between them. Three tiers:

  core memory     small, ALWAYS in the window, edited in place by the agent
                  (core_append / core_replace), evicted to archival on overflow
  archival memory unbounded store outside the window, searched on demand
  recall memory   the raw transcript of past turns, searched on demand

This lab simulates three work sessions. Facts arrive, a contradiction forces an
in-place edit, the core budget forces evictions, and a final quiz has to be
answered from a FRESH window: core is simply there, everything else must be
retrieved. A transcript-replay baseline runs alongside for the token
comparison. Deterministic, standard library only.

Run:  python3 memgpt_core.py
"""

def toks(s):
    return len(s) // 4

def words(s):
    return set("".join(c if c.isalnum() else " " for c in s.lower()).split())

# ---------------------------------------------------------------------------
# The three tiers.
# ---------------------------------------------------------------------------
class Memory:
    CORE_BUDGET = 62    # tokens the core block may occupy, total

    def __init__(self):
        self.core = {"user": [], "project": []}
        self.archival = []          # facts evicted from core or filed directly
        self.recall = []            # every raw turn ever seen
        self.trace = []

    # -- the self-editing tools the agent calls ----------------------------
    def core_append(self, section, fact, priority=1):
        self.core[section].append((fact, priority))
        self.trace.append(f"[core_append    ] {section}: {fact}")
        self._evict_if_over()

    def core_replace(self, section, old_key, fact):
        kept, replaced = [], None
        for f, pr in self.core[section]:
            if old_key in f and replaced is None:
                replaced = f
                kept.append((fact, pr))
            else:
                kept.append((f, pr))
        self.core[section] = kept
        self.trace.append(f"[core_replace   ] {section}: '{replaced}' -> '{fact}'")

    def archival_search(self, query, k=1):
        q = words(query)
        scored = sorted(self.archival, key=lambda f: -len(words(f) & q))
        return [f for f in scored[:k] if words(f) & q]

    def conversation_search(self, query, k=1):
        q = words(query)
        scored = sorted(self.recall, key=lambda t: -len(words(t) & q))
        return [t for t in scored[:k] if words(t) & q]

    # -- memory pressure: overflow spills the lowest-priority fact ---------
    def _evict_if_over(self):
        while toks(self.render_core()) > self.CORE_BUDGET:
            section, idx = min(
                ((s, i) for s in self.core for i in range(len(self.core[s]))),
                key=lambda si: self.core[si[0]][si[1]][1])
            fact, _ = self.core[section].pop(idx)
            self.archival.append(fact)
            self.trace.append(f"[evict->archival] {fact}  (core over budget)")

    def render_core(self):
        out = []
        for section, facts in self.core.items():
            out.append(f"<{section}>")
            out += [f"- {f}" for f, _ in facts]
        return "\n".join(out)

# ---------------------------------------------------------------------------
# Three sessions of facts arriving. Priority 2 = belongs in core long-term.
# ---------------------------------------------------------------------------
SESSIONS = [
    [  # session 1: onboarding
        ("user",    "name is Sol, timezone America/Toronto", 2),
        ("user",    "prefers pip for Python installs", 2),
        ("project", "site deploys to Cloudflare Pages on push to main", 2),
        ("project", "books build with mdBook, navy theme", 2),
    ],
    [  # session 2: work continues, one fact changes, minor facts arrive
        ("project", "CI runs on GitHub Actions with a 15 min budget", 1),
        ("user",    "SWITCH: now prefers uv over pip", 2),
        ("project", "the 2026-03 incident was a bad symlink in build.sh", 1),
        ("project", "mathjax.js must be copied verbatim between books", 1),
    ],
    [  # session 3: fresh window, the quiz
    ],
]

mem = Memory()
baseline_transcript = []

for n, session in enumerate(SESSIONS, 1):
    for section, fact, pr in session:
        # A real transcript carries both sides of the exchange, not bare facts.
        baseline_transcript.append(f"user: by the way, {fact}")
        baseline_transcript.append(f"assistant: got it, I will keep in mind that {fact}.")
        mem.recall.append(f"session {n}: {fact}")
        if fact.startswith("SWITCH:"):
            mem.core_replace(section, "pip", fact.removeprefix("SWITCH: now "))
        else:
            mem.core_append(section, fact, pr)

print("=== The agent's memory ops, in order ===")
for line in mem.trace:
    print(line)

print("\n=== Core memory block as session 3 opens (always in the window) ===")
core = mem.render_core()
print(core)
print(f"[{toks(core)} tokens, budget {Memory.CORE_BUDGET}]")

# ---------------------------------------------------------------------------
# The quiz, answered from a FRESH session-3 window.
# ---------------------------------------------------------------------------
print("\n=== Session 3 quiz: fresh window, core + retrieval only ===")
QUIZ = [
    ("Which installer does the user prefer, pip or uv?",  "uv"),
    ("Where does the site deploy?",                       "cloudflare"),
    ("What caused the 2026-03 incident in build.sh?",     "symlink"),
    ("What runs the CI, and what is its budget?",         "github"),
]
core_facts = [f for sec in mem.core.values() for f, _ in sec]
retrieved, score = [], 0
for q, needle in QUIZ:
    src, pool = "core    ", [f for f in core_facts if words(f) & words(q)]
    if not any(needle in f.lower() for f in pool):
        pool = mem.archival_search(q)
        retrieved += pool
        src = "archival"
    hit = next((f for f in pool if needle in f.lower()), None)
    score += hit is not None
    print(f"  Q: {q}")
    print(f"     -> {src} {'HIT ' if hit else 'MISS'} {hit or '(nothing relevant)'}")
print(f"Score: {score}/{len(QUIZ)}")

# ---------------------------------------------------------------------------
# What did session 3's window carry, versus replaying the transcript?
# ---------------------------------------------------------------------------
replay = toks("\n".join(baseline_transcript))
carried = toks(core) + toks("\n".join(retrieved))
print(f"\n=== Session 3 window cost, facts only ===")
print(f"transcript replay : {replay:>4} tokens after 8 exchanges, grows every session forever")
print(f"core + retrieved  : {carried:>4} tokens, bounded by budget + the slices this quiz used")
print(f"""
Lesson: the core block buys 'always known' for a fixed rent ({Memory.CORE_BUDGET} tokens),
eviction keeps that rent capped as facts accumulate, and everything evicted
stays reachable through search. The agent, not the developer, runs the moves:
append, replace-in-place on contradiction, evict under pressure, retrieve on
demand. That loop IS MemGPT, and every system in this chapter is a variation
on which tier holds what and who decides.""")

The verified output:

=== The agent's memory ops, in order ===
[core_append    ] user: name is Sol, timezone America/Toronto
[core_append    ] user: prefers pip for Python installs
[core_append    ] project: site deploys to Cloudflare Pages on push to main
[core_append    ] project: books build with mdBook, navy theme
[core_append    ] project: CI runs on GitHub Actions with a 15 min budget
[core_replace   ] user: 'prefers pip for Python installs' -> 'prefers uv over pip'
[core_append    ] project: the 2026-03 incident was a bad symlink in build.sh
[evict->archival] CI runs on GitHub Actions with a 15 min budget  (core over budget)
[core_append    ] project: mathjax.js must be copied verbatim between books
[evict->archival] the 2026-03 incident was a bad symlink in build.sh  (core over budget)

=== Core memory block as session 3 opens (always in the window) ===
<user>
- name is Sol, timezone America/Toronto
- prefers uv over pip
<project>
- site deploys to Cloudflare Pages on push to main
- books build with mdBook, navy theme
- mathjax.js must be copied verbatim between books
[54 tokens, budget 62]

=== Session 3 quiz: fresh window, core + retrieval only ===
  Q: Which installer does the user prefer, pip or uv?
     -> core     HIT  prefers uv over pip
  Q: Where does the site deploy?
     -> core     HIT  site deploys to Cloudflare Pages on push to main
  Q: What caused the 2026-03 incident in build.sh?
     -> archival HIT  the 2026-03 incident was a bad symlink in build.sh
  Q: What runs the CI, and what is its budget?
     -> archival HIT  CI runs on GitHub Actions with a 15 min budget
Score: 4/4

=== Session 3 window cost, facts only ===
transcript replay :  292 tokens after 8 exchanges, grows every session forever
core + retrieved  :   78 tokens, bounded by budget + the slices this quiz used

Lesson: the core block buys 'always known' for a fixed rent (62 tokens),
eviction keeps that rent capped as facts accumulate, and everything evicted
stays reachable through search. The agent, not the developer, runs the moves:
append, replace-in-place on contradiction, evict under pressure, retrieve on
demand. That loop IS MemGPT, and every system in this chapter is a variation
on which tier holds what and who decides.

Every move in that trace has a production counterpart in the systems below. Watch for them.

Letta: the operating-system view, productized

What it is. Letta (github.com/letta-ai/letta, Apache-2.0, roughly 24k stars, actively released through 2026) is the MemGPT paper turned into an agent platform: agents run as services with their memory managed server-side, persisting across conversations by construction.

How it works inside. The in-window unit is the memory block: a labeled string with a description, a value, and a limit in characters, rendered into the prompt in an XML-like format on every turn. Default agents ship two blocks, persona and human (20,000 characters each by default; general blocks allow 100,000), and blocks can be attached to several agents at once, which gives you shared memory between agents for free. The agent edits its own blocks with tools whose names you will recognize from the lab: core_memory_append and core_memory_replace, alongside archival_memory_insert, archival_memory_search (a vector store of passages, unlimited, retrieved on demand), and conversation_search over past messages. The paper's memory-pressure design survives in production as a summarization pass: when a step's usage passes 90% of the window, older messages are trimmed into a recursive summary, after a warning message urges the agent to save what matters to memory first, exactly the eviction the lab forced with a 62-token budget.

Two 2025-2026 additions are worth knowing. Sleep-time agents (from the "Sleep-time Compute" paper, Lin et al., 2025) attach a second agent that shares the primary's memory blocks and reorganizes them between conversations with dedicated tools (memory_replace, memory_insert, memory_rethink, memory_finish_edits): memory maintenance moved off the hot path, the paper reporting about 5x less test-time compute for comparable accuracy on its benchmarks. And Agent File (.af) is an open format that serializes an entire stateful agent (blocks, message history, tool code, config) into one importable file, which is what checkpointing an agent's memory looks like when memory is server-side state.

The window contract. Blocks are always resident (bounded by their limits); archival and conversation search land as tool results only when called; the recursive summary replaces evicted history. Rent is fixed and known in advance, which is the whole point.

Applied to Claude Code. Letta is a platform you run, not a plugin, so the practical integrations are: point Letta at Anthropic models for its agents (it is model-agnostic), or mount a Letta agent's capabilities into Claude Code via MCP (Letta speaks MCP as a host; the community also ships Letta MCP servers). The more direct lesson for a Claude Code user is architectural: Claude Code's own auto memory (MEMORY.md index always loaded, topic files on demand) is the same core-plus-archival split with files standing in for blocks, and its sibling letta-code CLI is that comparison made explicit by the Letta team themselves.

mem0: the pipeline, the paper, and the pivot

What it is. mem0 (github.com/mem0ai/mem0, Apache-2.0, roughly 61k stars, the most-starred project in this space) is pipeline memory in its purest form: a layer that watches conversations, extracts facts, and serves back a relevant slice per query.

How the paper's design works. The mem0 paper (Chhikara et al., 2025) describes the two-phase pipeline Chapter 9 taught. Extraction: an LLM reads the new exchange plus a running conversation summary and recent messages, and proposes candidate facts. Update: for each candidate, the top-10 semantically similar stored memories are fetched and a second LLM call picks one of four operations, verbatim from the paper: ADD (no equivalent exists), UPDATE (augment an existing memory), DELETE (the new fact contradicts a stored one), NOOP. That update phase is the invalidation step every append-only store lacks, and it is what made the design the reference pipeline. The paper's headline numbers on the LoCoMo benchmark: 26% relative improvement over OpenAI's built-in memory (LLM-as-judge score), 91% lower p95 latency and over 90% token savings versus stuffing the full conversation into context, with a graph variant (Mem0-g) about 2% better again.

The pivot. In April 2026 mem0 changed the default algorithm to single-pass, add-only extraction: one LLM call, no UPDATE or DELETE; "memories accumulate; nothing is overwritten," with retrieval and reranking doing the work of surfacing the current fact. The old two-phase behavior is gone from current versions (the version switch is ignored), and mem0 self-reports large benchmark gains from the change (LoCoMo 92.5 versus the old algorithm's 71.4). Two readings coexist: reranking may genuinely beat eager reconciliation, and add-only is certainly cheaper and faster to write; but the burden of resolving contradictions has moved from write time to read time, and Chapter 42 shows exactly the staleness probe that tells you whether read-time resolution is working on your data.

The benchmark fight, and the lesson. Zep published a detailed critique alleging the mem0 paper misconfigured Zep in its comparisons (wrong message roles, timestamps embedded in text instead of the dedicated field, sequential searches inflating latency), reporting a corrected Zep score about 10% above mem0's best, and noting that in mem0's own paper a plain full-context baseline outscored mem0's pipeline. mem0 published counter-corrections of Zep's numbers in turn. The dispute is unresolved and probably unresolvable from the outside, which is the durable lesson: vendor benchmark tables are adversarial documents. The response is not to pick a side; it is Chapter 42's harness, run on your own sessions.

The window contract. Nothing resident. Per turn, search() returns the top-k facts and your code injects them: a few hundred tokens, flat, regardless of how much is stored, which is rung 3 of Chapter 40's ladder implemented as a service.

Applied to Claude Code. The route is OpenMemory, mem0's local-first MCP server: a Docker Compose stack that runs on your machine (server on localhost:8765, dashboard on :3000) and exposes exactly five tools to any MCP client: add_memories, search_memory, list_memories, delete_memories, delete_all_memories. Registered with claude mcp add, those tools appear in every session, and one CLAUDE.md line ("check search_memory before asking me to restate preferences") turns it into cross-session, cross-client memory: the same store serves Claude Code, Claude Desktop, and Cursor at once, which per-project CLAUDE.md files cannot do. Note how much steering lives in the tool descriptions themselves: OpenMemory's search_memory description says to call it for every user question, an instruction the model reads every session; Chapter 28 explained why that channel works.

The graph stores: Graphiti, cognee, and the MCP reference server

Graphiti (github.com/getzep/graphiti, Apache-2.0, roughly 29k stars) is the engine Chapter 10 covered in depth: a bi-temporal knowledge graph where facts are edges with validity intervals, superseded facts are closed rather than deleted, raw inputs are preserved as episodes, and retrieval is a hybrid of embeddings, BM25, and graph traversal. Its repo ships an MCP server, so the practical Claude Code wiring is the same shape as OpenMemory's: mount it, and the agent gets "what was true in March" as a tool. Choose it when the history of facts is itself the question.

cognee (github.com/topoteretes/cognee, Apache-2.0, roughly 28k stars) targets documents more than conversations: ECL pipelines (Extract, Cognify, Load) that turn ingested files into a knowledge graph plus embeddings, queried through both relationship and similarity lenses, also mountable into Claude Code over MCP. Think of it as Chapter 31's retrieval pipeline with a graph bolted on, sold as memory.

The MCP reference memory server (@modelcontextprotocol/server-memory, from the official modelcontextprotocol/servers repo) is the smallest real system in the chapter and the best one to study. It is a knowledge graph in a single JSONL file (memory.jsonl, path set by MEMORY_FILE_PATH): entities (name, type, a list of atomic observation strings), relations (directed, active voice), and nine tools that are the whole API: create_entities, create_relations, add_observations, delete_entities, delete_observations, delete_relations, read_graph, search_nodes, open_nodes. No embeddings, no LLM in the loop: extraction is whatever the agent chooses to write, retrieval is string search over names and observations. One claude mcp add memory -- npx -y @modelcontextprotocol/server-memory and Claude Code has durable, greppable, agent-curated memory whose entire mechanism you can read in one file. For a personal repos ledger like Chapter 40's, this is honestly hard to beat.

LangMem: the taxonomy

LangMem (github.com/langchain-ai/langmem, MIT, roughly 1.6k stars, a much smaller project than the others here) matters less for its code than for its vocabulary, which the field has broadly adopted. It classifies agent memory as semantic (facts and knowledge: preferences, triplets; stored as a strict-schema profile or a searchable collection), episodic (past experiences kept as learning examples: the situation, the reasoning, why it worked), and procedural (how to behave: the system prompt and rules, evolved through feedback). It also names the two formation modes: hot path (update memory during the conversation, paying latency now) versus background (reflect after the conversation goes quiet), the same split Letta's sleep-time agents implement.

Mechanically it is a thin, honest layer: create_manage_memory_tool() and create_search_memory_tool() give any agent write and search tools over a LangGraph store (JSON documents in namespace tuples like (user_id, "preferences"), with put, get, and semantic search), plus a background manager that extracts and consolidates on a schedule.

The taxonomy maps straight onto this book: semantic memory is Chapter 9, episodic is the transcript and Chapter 12's failure examples, procedural is CLAUDE.md and Chapter 12's learned rules. When a vendor pitches "memory," asking which of the three it stores, and which formation mode it uses, is the fastest way to place it.

Anthropic's native pair: the memory tool and context editing

Anthropic ships the two halves of MemGPT's loop as separate API features, and the split is instructive: one feature is the durable store, the other is the eviction.

The memory tool (now generally available on the Messages API, no beta header) is declared as {"type": "memory_20250818", "name": "memory"}. It is entirely client-side: Claude issues file commands and your code executes them against storage you control, under a /memories path prefix. The commands are view, create, str_replace, insert, delete, and rename, and if the shape sounds familiar, it should: it is a text editor pointed at a memory directory, the same design as Claude Code's auto memory and Serena's .serena/memories/, generalized to any application. Declaring the tool auto-injects a system instruction that tells Claude to check its memory directory before doing anything else and to "assume interruption" (the context window might reset at any moment), which is the memory-pressure discipline from the lab, imposed by prompt. Follow-along shape, output illustrative since the SDK is not installed on this box:

# Follow-along: requires the anthropic SDK and an API key.
import anthropic

client = anthropic.Anthropic()
response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    tools=[{"type": "memory_20250818", "name": "memory"}],
    messages=[{"role": "user", "content": "Pick up the migration where we left off."}],
)
# Claude's first move, before touching the task:
#   tool_use: {"command": "view", "path": "/memories"}
# Your handler executes it against ./memory_store/ (never trust the path
# blindly: reject anything that escapes /memories) and returns the listing.

The security note is not optional: the docs warn explicitly about path traversal (/memories/../../secrets.env), and Chapter 32 explains why a memory an attacker can write into is an injection channel with persistence.

Context editing (beta, header context-management-2025-06-27) is the eviction half: server-side strategies that prune the conversation as it grows. clear_tool_uses_20250919 clears old tool results past a trigger (default 100,000 input tokens), keeping the newest few (default 3, tunable, with exclude_tools to protect specific tools); clear_thinking_20251015 prunes old thinking blocks. Newer still is server-side compaction (beta compact-2026-01-12), which summarizes instead of clearing; Chapter 42 covers it where it belongs, next to the eval that tells you what a summary dropped. Pair the memory tool with context editing and you have the lab's exact architecture at API scale: durable facts written out before old turns are cleared away.

Choosing for Claude Code

The decision, compressed to the axes that matter:

You wantReach forWindow contractWiring
Zero setup, per-project factsAuto memory (Ch 18)index resident, topics on demandon by default
Rules and conventionsCLAUDE.md hierarchyresident, in fullyou write it
Code knowledgeSerena / LSP (Ch 38)retrieved slicesMCP
Cross-client personal memoryOpenMemory (mem0)top-k slice per queryMCP, Docker
Inspectable, tiny, greppable@modelcontextprotocol/server-memoryretrieved nodesone claude mcp add
Fact history over timeGraphiti (Ch 10)retrieved subgraphMCP, graph DB
Memory inside your own appAnthropic memory toolfiles the model viewsSDK, your handler
Agents as a managed serviceLettablocks resident + searchseparate platform

Remember. Judge every system by its window contract, because that is the column you pay for on every turn. Resident memory (blocks, CLAUDE.md, an index) buys "always known" at a fixed rent; retrieved memory buys "findable" at per-query cost; and the systems that win in practice keep the resident part small and push everything else behind search, which is exactly what the 78-versus-292-token quiz in the lab measured.

Further reading

  • Packer et al., "MemGPT: Towards LLMs as Operating Systems" (2023, arXiv 2310.08560): the paper behind the lab; the memory-pressure and recursive-summary designs are sections worth reading in full.
  • Chhikara et al., "Mem0: Building Production-Ready AI Agents with Scalable Long-Term Memory" (2025, arXiv 2504.19413): the two-phase pipeline, and the numbers the benchmark fight is about; read alongside Zep's critique ("Is Mem0 Really SOTA in Agent Memory?", blog.getzep.com) as a case study in adversarial benchmarking.
  • Lin et al., "Sleep-time Compute: Beyond Inference Scaling at Test-time" (2025, arXiv 2504.13171): memory maintenance off the hot path, measured.
  • Anthropic docs: the memory tool (platform.claude.com/docs/en/agents-and-tools/tool-use/memory-tool) and context editing (.../build-with-claude/context-editing); the memory tool page includes the full command reference and the path-traversal warning.
  • Chapter 9, Chapter 10, Chapter 18, Chapter 38: the foundations this tour builds on.

Takeaways

  • Three questions sort every memory system: what is extracted, where it lives, what lands in the window per turn. Only the third has a per-turn bill, and it splits the field into pipeline memory (code decides) and agentic memory (the model decides, via tools).
  • The MemGPT loop (append, replace on contradiction, evict under pressure, retrieve on demand) reproduces in 150 lines and answers a fresh-window quiz 4/4 at 78 tokens versus 292 for replay; Letta is that loop productized, with blocks, sleep-time maintenance, and .af checkpoints.
  • mem0's paper design added the update-or-delete reconciliation step; its April 2026 pivot to add-only moved contradiction handling from write time to read time, and the mem0-versus-Zep benchmark dispute is the standing argument for running your own eval.
  • The MCP reference memory server (nine tools, one JSONL file) is the most inspectable real memory system available to Claude Code, and often enough; OpenMemory adds cross-client reach; Graphiti adds time.
  • Anthropic's memory tool is a client-side text editor over a /memories directory (the same file-based design as auto memory and Serena), and context editing is the matching eviction; together they are the lab's architecture at API scale.

👉 Every system in this tour ships a benchmark table, and two of them are publicly at war over one. The next chapter builds the thing that settles it for you: an eval harness for memory and context, before and after a change, across sessions, across compactions, and across model swaps, with a from-scratch lab and the exact claude -p recipes. Continue to The memory eval harness.