Memory Systems
An agent that runs longer than a single context window has to remember what it did and survive losing the machine it did it on. The state it leaves behind has three forms: the durable record of a session, the workspace it mutated, and the memory it carries across sessions. Session logs must record intent separately from result; branching a conversation is cheap while branching a workspace is not; and a shared vector store is a data-breach risk, not merely a relevance bug.
A commit that runs twice
Start with the failure, because it names the whole subject. A runner prompts
the model, the model asks to run git commit, the sandbox runs it and the
commit lands, and then the runner crashes in the window between the effect
firing and its record reaching durable storage. Recovery reads the last
durable state, which is pre-commit, and the agent replays: it issues the same
git commit again.
This is not an exotic edge case. A harness has to survive any of four routine
events: a harness crash, a replica reschedule, a sandbox loss, or a user
resuming a session days later. All of them are the normal operating regime of
a long-running agent, and reported agent retry rates of 15 to 30 percent make
the replay above a regime to design for, not an accident (Fast.io 2025).
The core difficulty it exposes is the gap between a side effect and its commit.
A durable log records intent ("call git commit") and, separately, result
("commit succeeded"). If the harness crashes between those two writes, recovery
re-issues the operation. For an idempotent operation that is harmless. For a
non-idempotent one, a second commit, a duplicate ticket, a repeated payment,
the store and the external world diverge silently, and nothing in the log can
tell you it happened.
Surviving these events means writing state to a durable store, and the hard question is not where to put that state but what to record. The state an agent leaves behind comes in three shapes, and each fails in its own way. The session log is append-only intent, and its characteristic failure is the intent-versus-result gap above. Mutable sandbox state lives in the workspace, where durability is what breaks: a volume that cannot follow a pod across a zone boundary, a snapshot taken too rarely. That leaves long-term memory, the curated and retrievable store that outlives a session, whose failure is tenancy, because a retrieval that crosses a user boundary is not a wrong answer, it is a data breach. One small contract spans all three, append, read, snapshot, fork, applied to state shapes that are anything but uniform. The design discipline is stating plainly what each store does not guarantee. The three stores that follow keep that question in view: what each one buys, and what it refuses.
The session log: three ways to record a step
The session log can be built three ways, and the meaningful difference between them is what they record relative to the effect, which fixes where the intent-versus-result gap lands. Figure 39.3 places all three on that one axis.
The append-only event log writes every user message, model response, tool call, and tool result to a durable store. The log is the sole recovery artifact: any replica can serve any session by replaying it. This buys clean replay, clean audit, and transport-agnostic recovery, and it is the model that exhibits the intent-versus-result gap in its purest form. From the log alone, "requested but never ran" and "ran but never recorded" are identical: a tool call with no result. That is the gap the opening crash walked into.
The durable step log closes the gap by adding a third record between intent and result: a durable step boundary written before the effect executes. Replay skips any step whose outcome is already durable, so "we asked to do X" and "X completed" become two separate, individually-durable facts, and replay can ask "did this step already complete?" instead of assuming it did not. This is the answer durable-execution frameworks settled on, and the evolution is worth tracing. The plain event log gave clean replay but blurred intent and result. Restate (Ewen et al. 2025), Temporal (Davis 2025), and Inngest (Poly 2026) journal each step, run the effect, then record its outcome, formalizing the distinction the event log blurs. Temporal's own framing is that probabilistic LLM behavior makes naive retry logic insufficient, which is exactly the case durable journaling is built for (WorkOS 2025). By 2025 this crossed into the mainstream: AWS Lambda durable functions, Cloudflare Workflows, and Vercel's Workflow DevKit all shipped with AI-agent use cases as primary framing (Poly 2026). The pattern is no longer framework-specific.
The filesystem-as-state model takes the opposite extreme: the working
context is ephemeral and the filesystem is the sole authoritative record of
task state (Zhou and others 2026). The event log demotes to a secondary log
of intent; the filesystem holds the truth, usually through an opinionated
convention (progress.md, plan.md) the agent maintains. This sidesteps the
intent-versus-result gap for anything the filesystem can represent, the file
either has the new content or it does not, but it relocates the gap rather than
removing it: a git push, an external API call, an MCP server-side cursor
advance are still outside the filesystem's reach.
From a list to a tree
The session log records steps, but its shape evolved separately from its
durability. A linear append-only log treats a session as a list, and to back
out you start over. Production harnesses moved past that. Rewind and truncate
jump back to a checkpoint and discard the tail, rolling the workspace back to
match, as in Claude Code's /rewind (Anthropic 2025) and Cursor's
per-edit checkpoints (Cursor 2025). A forkable tree goes further:
multiple live branches from any checkpoint, all preserved. LangGraph's
time-travel model treats checkpoints as a DAG with explicit branch IDs
(LangChain 2025), Claude Code's /fork spawns a child session from a
shared history point (Anthropic 2025), and Replit Agent
(Replit 2025) and OpenAI's Codex CLI (OpenAI 2026) ship their
own variants. The session stopped being a list and became a tree, or at least a
log with a movable head. That tree is cheap to build only because conversation
state is a pointer structure, an assumption the workspace will break in the
next section.
The workspace: partial durability, expensive branching
The workspace is the sandbox's mutable filesystem. It outlives individual tool calls but is not on the durable log, and that distinction is the source of two failures the session log does not have.
Workspace persistence is partial durability, not full recovery.
Persisting the workspace across pod lifecycle events has its own failure modes.
The common Kubernetes pattern mounts a PersistentVolumeClaim at /workspace;
the dominant failure is zone scoping. A ReadWriteOnce PVC is backed by a zonal
block device that is physically attachable only within its own availability
zone, so when a pod reschedules across zones the volume cannot follow and
recovery falls back to a full re-bootstrap
(Rack2Cloud 2026; Kubernetes 2023). When a pod is deliberately
destroyed (teardown, timeout, scale-down) the PVC is often deleted with it, and
uncommitted work is gone: Gitpod issue #9544 is the archetype, a workspace
timeout fired before the final sync completed and a user lost roughly two hours
of work (Gitpod 2022). The alternative, checkpointing the complete state on
a cadence, trades snapshot frequency directly against storage overhead, and the
frequency knob is load-bearing because it sets the maximum work a crash can
erase. Replit's snapshot engine captures filesystem and conversation context
together and reports recovering from hourly OOM crashes without data loss
(Replit 2025; Replit 2025). Any design that claims "sessions
survive indefinitely" owes an explicit account of which failure modes it covers
and which it does not.
Change the snapshot interval below and watch the two curves move in opposite directions: tighter snapshots erase less work on a crash but write more often.
import numpy as np
import matplotlib.pyplot as plt
rng = np.random.default_rng(0)
intervals = np.array([1, 2, 5, 10, 15, 30, 60]) # minutes between snapshots
trials = 20000
lost = []
for iv in intervals:
crash = rng.uniform(0, iv, trials) # crash time within the interval
lost.append(crash.mean()) # work since last snapshot, in minutes
lost = np.array(lost)
writes = 60 / intervals # snapshot writes per hour
for iv, l, w in zip(intervals, lost, writes):
print(f"every {iv:2d} min: ~{l:4.1f} min lost, {w:5.1f} writes/hour")
fig, ax1 = plt.subplots()
ax1.plot(intervals, lost, "o-", color="#c0392b", label="expected work lost (min)")
ax1.set_xlabel("snapshot interval (min)"); ax1.set_ylabel("work lost (min)")
ax2 = ax1.twinx()
ax2.plot(intervals, writes, "s--", color="#2980b9", label="writes/hour")
ax2.set_ylabel("snapshot writes/hour")
fig.legend(loc="upper center"); plt.show()
Branching splits cheaply on conversation and expensively on the workspace. Branching conversation state is a pointer operation: the log is a tree of event IDs and forking a tree is cheap. Branching the workspace is not, because the workspace is a real filesystem and two branches cannot both own it.
As Figure 39.4 shows, the choice collapses to a dilemma: forks that share the workspace race on it, branch A's writes are visible to branch B, and forks that clone it pay the clone cost. Three failure modes follow. A branch cannot fork the external systems it touches, git remotes, issue trackers, MCP cursors, so branchable and unbranchable state drift apart. Merge-back is structurally unsolved: files have a common ancestor at the checkpoint, so a three-way merge applies, but two divergent conversations have no merge base because the content is a reasoning trajectory, not editable lines, which is why the agent, not the harness, has to resolve a file merge. And orphan branches pin workspace and session-log resources until a TTL reclaims them, so without one the leak accumulates silently. Rewind alone suffices for interactive coding; forkable trees pay off when users explore alternatives in parallel or an evaluation pipeline replays a session with a changed prompt.
Whether branching and snapshotting are cheap or ruinous is decided one layer down, by the storage substrate, not by the harness. A PVC has no native cheap-clone primitive, so a fork on PVC must copy the whole workspace. Copy-on-write overlays and content-addressed snapshots duplicate only the divergent blocks, so a fork costs what diverged. Firecracker snapshots serialize an entire microVM and restore in tens to hundreds of milliseconds (Amazon Web Services 2025), which makes per-fork snapshotting affordable that would be ruinous on a PVC. The clone cost is bounded by the substrate, not by the harness, so the session-tree feature in the layer above is enabled or priced out by a storage choice made below it.
Long-term memory: useful because retrievable, dangerous for the same reason
A session is bounded by context compaction (Chapter 35): the context window fills, gets summarized, and the early turns fall out of the model's reach. An agent that is supposed to know a user across weeks therefore cannot rely on the context window alone. It needs a separate store, durable and queryable, that the harness loads into context on demand. That store is memory, and it is a different system from the session log: the log is append-only intent, memory is curated and retrievable.
The shape of memory
Memory has a structure worth naming. Short-term memory is the session context within one conversation. Long-term memory splits into episodic (past interactions), semantic (facts about the user or domain), and procedural (learned workflows). External knowledge, the corpora behind retrieval-augmented generation (Chapter 44) and knowledge graphs, sits alongside but is a different thing: memory is per-user, curated, and writable, where RAG retrieves from a shared corpus the agent does not own.
The base layer is a vector store plus embedding retrieval: each memory is turned into a vector (a list of numbers that captures its meaning), stored, and recalled by finding the nearest vectors to a query. Pinecone (Pinecone 2025), pgvector (pgvector 2025), and Chroma (Chroma 2025) are representative substrates. Above them sit opinionated memory frameworks, Letta (Letta 2025) (the successor to MemGPT (Packer et al. 2023)), Zep (Zep 2025), and Mem0 (Chhikara et al. 2025), that decide what to write, when, and how to compact it. Knowledge graphs model entities and relationships explicitly, which suits compositional queries but is harder to populate automatically, and a temporal knowledge graph adds the time dimension so a fact can be valid for a period and then superseded.
The category arrived the way the other two stores did, by a research artifact hardening into a product. MemGPT (Packer et al. 2023) framed the LLM as an operating system that pages memory in and out of a bounded context, and its successor Letta (Letta 2025) turned that into a hosted primitive. Zep (Zep 2025) built memory on a temporal knowledge graph, and Mem0 (Chhikara et al. 2025) optimized for low latency and token cost. The category is young enough that its benchmarks are still contested.
A now-mainstream alternative skips the index entirely and stores memory as
plain files the agent reads and edits with its ordinary tools. Anthropic's
memory tool gives the model a /memories directory it views, creates, and
edits across sessions, with the application executing each file operation
client-side (Anthropic 2025); the CLAUDE.md convention in coding
agents is the same idea maintained by hand. Recall becomes an agentic read or
grep instead of a similarity query, and curation becomes the model's own
editing rather than a framework's write policy. The trades this chapter closes
on shift with it: file memory needs no embedding-model commitment, so there is
no reindex cliff, and a per-tenant directory is directly inspectable and
erasable, at the cost of recall that reaches only what the agent thought to
write down and chose to read back.
Cross-vendor memory benchmarks are not yet trustworthy. Mem0 reports a 26 percent LLM-as-Judge gain over OpenAI memory on LOCOMO (Snap Research 2024) with large latency and token-cost reductions (Chhikara et al. 2025). Letta's counter-benchmark reports a higher LoCoMo score with disputed methodology (Letta 2025). Zep reports its temporal knowledge graph leading on a different benchmark (Zep 2025). The vendors agree on neither the metric, the dataset split, nor the baseline, so treat any cross-vendor memory benchmark as contested until it is independently reproduced. The useful signal is the shape of the trade, recall quality against latency and token cost, not the headline number.
What memory refuses to guarantee
Memory trades cost against simplicity, and write-time safety against read-time convenience. Memory must be either loaded ambiently into every prompt (simple, expensive) or exposed as a tool the agent queries on demand (cheaper, scales better, but the agent has to know when to ask). Changing the embedding model invalidates the existing index and forces an expensive reindex, so the embedding choice is a long-term commitment.
The sharpest trade is tenancy, and it is what makes a shared vector store a data-breach risk rather than a relevance bug. The same property that makes memory useful, that retrieved content lands directly in the model's context, makes it dangerous, because that content is indistinguishable from instructions the user or harness put there. A top-k similarity query (fetch the k closest stored vectors) against a shared vector store without an enforced tenant filter can return another tenant's data, and the defense has to live in the index, not in application code. Partition at write time, per-tenant namespaces, pgvector row-level security or separate schemas, so a query physically cannot reach another tenant's vectors. Attributing every chunk and filtering after the query is weaker, because filter-at-read-time is one bug away from a leak, whereas partition-at-write-time fails closed. This is where memory consumes the identity model directly (Chapter 56): tenancy is sourced from identity, and partitioning enforces it.
A second tenancy risk is specific to memory and has no analogue in the session log: because the agent writes memory based on user messages, a malicious user can craft messages that plant persistent misinformation. This is the prompt-injection problem moved into the memory store, where it survives across sessions. A misretrieved or poisoned record lands in the prompt and shifts the agent's behavior in ways that are hard to debug, because the trace shows retrieval succeeding: the injected text is not an error, it is a correct retrieval of wrong content.
Two properties to nail down before the first crash
Both of the following are invisible until exactly the wrong moment, which is why they belong in the design and not in the postmortem.
The first is the reconciliation gap, the operational form of the opening crash. If the sandbox holds mutable state outside the durable log, the harness must reconcile the two on recovery. For idempotent operations it does not matter; the result is the same whether the operation ran once or twice. For non-idempotent ones, append to a file, increment a counter, create a commit, call a side-effectful API, the harness needs either a step-log boundary or an idempotency key threaded through the tool. The journal records the step's intent and its key before the effect fires; on replay the harness performs check-then-act against that key, or against the external system's own dedup, git's ref state, a payment processor's idempotency header, rather than blindly re-issuing. The step log does not make external effects idempotent on its own. It gives you the hook to make them so.
# Idempotency-keyed step, conceptual sketch.
def run_step(journal, key, effect):
if journal.has_outcome(key): # replay: skip a completed step
return journal.outcome(key)
journal.record_intent(key) # durable boundary BEFORE the effect
result = effect() # the non-idempotent side effect
journal.record_outcome(key, result)
return result
The second is erasure reach. A right-to-erasure request has to cascade across every store that holds a user's data, and memory is the painful case: framework-managed memory may not expose a delete API at all, so a store chosen for recall quality can quietly become a store you cannot erase. Partition-at- write-time helps here too, because a per-tenant namespace is a unit you can drop wholesale. The broader audit and compliance machinery, tamper-evident logs, data residency, the regulatory regimes, belongs to Chapter 56; the memory-specific obligation is to keep every record attributable to a tenant and erasable by tenant.
For the memory store itself, the base implementation is a vector index (pgvector (pgvector 2025) inside an existing Postgres is the low-operational-overhead default; Pinecone (Pinecone 2025) or Chroma (Chroma 2025) when a dedicated store is warranted), wrapped either by a framework (Letta (Letta 2025), Zep (Zep 2025), Mem0 (Chhikara et al. 2025)) that manages write and compaction policy, or by a thin custom layer when the policy is simple enough to own. The harness (Chapter 41) is what loads the result into context, ambiently or through a retrieval tool, closing the loop back to the session that the memory outlived.
Further reading
- Fast.io, “AI Agent Reliability Report 2025,” 2025. fast.ioA developer guide explaining why AI agents must use idempotent operations (atomic writes, idempotency keys, file locking) to prevent data corruption and duplicate side effects on retries.
- Ewen et al., “Durable AI Loops: Fault Tolerance across Frameworks,” 2025. restate.devRestate's durable execution wraps agent loops to provide fault tolerance, resumability, observability, and human-in-the-loop support across any agent SDK without framework lock-in.
- Davis, “Durable Execution meets AI,” 2025. temporal.ioTemporal's Durable Execution model satisfies every core reliability requirement of LLM-powered AI agent applications because agents are distributed systems that need resilience against transient failures.
- Poly, “Durable Execution: The Key to Harnessing AI Agents in Production,” 2026. inngest.comInngest blog post arguing that durable execution, a programming model guaranteeing code completion despite failures, is the key infrastructure primitive for running AI agents reliably in production.
- WorkOS, “Maxim Fateev on Durable Execution for AI Agents,” 2025. workos.comA WorkOS interview with Temporal co-founder Maxim Fateev on why durable execution is necessary for reliable AI agent workflows that survive failures.
- Zhou & others, “Externalization in LLM Agents: A Unified Review of Memory, Skills, Protocols and Harness Engineering,” 2026. arXiv:2604.08224This survey proposes externalization as the unifying principle of LLM agent design, framing memory, skills, and protocols as cognitive artifacts that offload model burdens into persistent external infrastructure coordinated by a harness layer.
- Rack2Cloud, “Kubernetes Day 2 Failures: 5 Incidents and the Metrics That Predict Them,” 2026. rack2cloud.comA practitioner guide cataloguing five common Kubernetes Day 2 failure modes and identifying one predictive metric for each to enable early detection.
- Kubernetes, “PVC Binding Prevents Pod Rescheduling,” 2023. github.comA Kubernetes bug report where a pod that fails at the bind stage leaves its PVC and PV permanently bound to a node, causing the pod to remain stuck in Pending forever.
- Gitpod, “Data loss when workspace timeout fires before sync completes,” 2022. github.comA GitHub issue reporting that Gitpod workspace data is lost when a workspace times out and its pod enters a terminating state, because data is not synced to storage during the active session.
- Replit, “Inside Replit's Snapshot Engine,” 2025. blog.replit.com
- Replit, “Finding and Solving Memory Leaks,” 2025. blog.replit.com
- Anthropic, “Claude Code: Checkpointing and Session Forks,” 2025. code.claude.comClaude Code's checkpointing reference page documents how to track, rewind, and summarize Claude's file edits and conversation to manage session state.
- Cursor, “Checkpoints in the Agent Workflow,” 2025. stevekinney.comCursor automatically snapshots codebase state before every AI Agent edit, storing ephemeral local checkpoints outside Git so users can roll back unwanted changes.
- Replit, “Checkpoints and Rollbacks,” 2025. docs.replit.comReplit's Checkpoints and Rollbacks docs page describes how the Replit Agent automatically saves project snapshots at development milestones, enabling one-click restore of full project state and AI conversation context.
- LangChain, “LangGraph Time Travel and Branching,” 2025. docs.langchain.comLangGraph's time-travel feature lets users replay past graph executions and fork from any prior checkpoint to explore alternative execution paths.
- OpenAI, “Codex CLI: Conversation and Code-Reverting Rewind,” 2026. github.comA GitHub issue on openai/codex CLI requesting a /rewind command that atomically restores both conversation history and Codex-applied file edits to a selected checkpoint.
- Amazon Web Services, “Firecracker Snapshotting,” 2025. github.comFirecracker's snapshot-support docs describe how to serialize a running microVM to disk and restore it with on-demand memory loading via MAP_PRIVATE, enabling fast workload cloning and resume.
- Ustiugov et al., “Benchmarking, Analysis, and Optimization of Serverless Function Snapshots,” 2021. arXiv:2101.09355This paper introduces vHive, an open-source serverless experimentation framework, and REAP, a record-and-prefetch mechanism that reduces snapshot-based cold-start latency by 3.7x via working-set prefetching.
- Pinecone, “Pinecone: Managed Vector Database for AI,” 2025. pinecone.ioPinecone is a managed vector database service providing low-latency similarity search and metadata filtering for building RAG pipelines and AI applications at scale.
- pgvector, “pgvector: Open-source Vector Similarity Search for Postgres,” 2025. github.compgvector is an open-source PostgreSQL extension that adds vector similarity search, enabling nearest-neighbor retrieval directly in Postgres.
- Chroma, “Chroma: Open-source Embedding Database,” 2025. trychroma.comChroma is an open-source AI search infrastructure providing vector, full-text, regex, and metadata search built on object storage, available under Apache 2.0.
- Letta, “Letta: Stateful Agents with Memory as a First-Class Primitive,” 2025. letta.comLetta is an AI research lab and product platform for building stateful agents that maintain persistent memory, learn continuously, and improve over time, originating from the MemGPT project.
- Packer et al., “MemGPT: Towards LLMs as Operating Systems,” 2023. arXiv:2310.08560MemGPT introduces virtual context management for LLMs, using an OS-inspired hierarchical memory system to page data between a fixed context window and external storage, enabling unbounded context for document analysis and multi-session chat.
- Zep, “Zep: Temporal Knowledge Graph for Agent Memory,” 2025. getzep.comZep is an enterprise agent memory platform that stores facts in a temporal context graph (Graphiti), tracks how facts change over time, and serves retrieved context to agents in under 200 ms.
- Chhikara et al., “Mem0: Building Production-Ready AI Agents with Scalable Long-Term Memory,” 2025. arXiv:2504.19413Mem0 is a scalable memory architecture for AI agents that dynamically extracts, consolidates, and retrieves salient facts across sessions, achieving 26
- Snap Research, “LOCOMO: Long-Term Conversational Memory Benchmark,” 2024. snap-research.github.ioLoCoMo is a benchmark for evaluating very long-term conversational memory of LLM agents via question answering, event summarization, and multimodal dialog generation tasks.
- Letta, “Benchmarking AI Agent Memory,” 2025. letta.comA Letta agent using only filesystem tools scores 74.0
Comments
Log in to comment