Jul 11, 2026 · 6 min · Engineering

Persistent Memory for Claude Code: How Cross-Session Context Actually Works

Persistent Memory for Claude Code: How Cross-Session Context Actually Works

You close your laptop on Friday after a three-hour session where Claude Code refactored your auth module, learned that your team uses a custom Result<T> wrapper instead of throwing exceptions, and finally understood why UserService can’t import from billing/. On Monday you open a fresh session and Claude confidently wraps a new function in a try/catch and imports the forbidden module. Everything it learned is gone.

This is the defining ergonomic problem of agentic coding tools: the model has no episodic memory. Every session starts from zero plus whatever you’ve manually written down. Let’s engineer our way out of it — honestly, including the parts that don’t work.

The core problem: sessions are stateless by design

A Claude Code session is a conversation. When it ends, the transcript is archived and the working context — the accumulated understanding built up over dozens of tool calls — evaporates. The next session reconstructs context from three sources:

None of these capture derived knowledge: the dead ends you already explored, the reason a weird workaround exists, the fact that you tried approach A and it broke CI. Re-deriving that knowledge costs tokens and wall-clock time on every single session.

The instinct is to build a memory layer — capture what happened, compress it, and inject the relevant slice back at startup. The open-source claude-mem project is a clean reference implementation of exactly this pattern, and it’s worth studying whether or not you adopt it.

The architecture pattern behind claude code persistent memory

Every serious approach to claude code persistent memory follows the same three-stage loop. Understanding the stages independently lets you build, buy, or skip each one.

Stage 1: Capture

You need to observe what happened in a session. Claude Code exposes lifecycle hooks — shell commands that fire on events. The relevant ones:

HookFires whenUse for
SessionStartA session beginsInject memory
SessionEndA session terminatesTrigger capture
PostToolUseAfter any tool callStream granular activity
StopThe agent finishes respondingCheckpoint

A minimal capture hook in .claude/settings.json:

{
  "hooks": {
    "SessionEnd": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/mem/capture.sh"
          }
        ]
      }
    ]
  }
}

The hook receives JSON on stdin describing the event. For SessionEnd you get the session ID and transcript path, so capture.sh can read the full conversation log:

#!/usr/bin/env bash
INPUT=$(cat)
TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcript_path')
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id')

# Hand the raw transcript to the compression stage
python3 "$CLAUDE_PROJECT_DIR/.claude/mem/compress.py" \
  --transcript "$TRANSCRIPT" \
  --session "$SESSION_ID"

A common gotcha: hooks run with a timeout, and a large transcript passed synchronously to an LLM will blow past it. In practice you want capture to be fire-and-forget — write the transcript path to a queue and process it in a background worker, not inline in the hook.

Stage 2: Compress

Raw transcripts are useless as memory — they’re enormous and full of noise (file dumps, tool spam, your typos). The compression stage uses an LLM to distill the session into durable facts. This is where you spend a Haiku 4.5 call, not an Opus one — summarization is exactly the kind of high-volume, low-stakes task Haiku is priced for.

import anthropic

client = anthropic.Anthropic()

PROMPT = """You are a memory compressor. From this coding session
transcript, extract ONLY durable facts worth remembering next session:
- Architectural decisions and their rationale
- Conventions discovered (naming, error handling, imports)
- Dead ends already explored (so we don't repeat them)
- Unresolved TODOs

Output JSON: a list of {fact, category, files[], confidence}.
Omit anything transient. If nothing durable happened, output []."""

resp = client.messages.create(
    model="claude-haiku-4-5",
    max_tokens=1500,
    system=PROMPT,
    messages=[{"role": "user", "content": transcript_text}],
)
memories = resp.content[0].text

Note the confidence field and the explicit instruction to output []. Both are damage control for the failure mode we’ll get to. Store the output somewhere queryable — SQLite with an embedding column, or a vector store keyed by project and file paths.

Stage 3: Inject

At session start, you retrieve the relevant memories and inject them. The SessionStart hook can print to stdout, and that output is added to Claude’s context:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "*",
        "hooks": [
          { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/mem/inject.sh" }
        ]
      }
    ]
  }
}

The critical engineering decision is relevance filtering. Injecting all memory is the naive move and it’s wrong — it balloons your context and drowns the useful facts. Better: retrieve by what the session is likely about. If you can’t tell yet, inject only high-confidence, project-wide facts and expose the rest through an MCP server so Claude can query memory on demand:

# A tiny MCP tool the agent calls when it wants history
@mcp.tool()
def recall(query: str, files: list[str] = []) -> list[dict]:
    """Retrieve relevant memories about this codebase."""
    return memory_store.search(query, files=files, min_confidence=0.6)

This pull model beats the push model in practice. Instead of guessing what’s relevant at startup, you let the agent ask “have we touched this billing code before?” precisely when it matters. The hook seeds a small always-on summary; the MCP server handles targeted recall.

Where this breaks — and it does break

I’ve run memory layers long enough to be skeptical of them. The failure modes are real and they compound.

AI-compressed memory hallucinates. The compressor is itself a language model. It will occasionally record a “decision” that was actually a discarded idea, or invert the rationale for a choice. Now you have authoritative-sounding false memory injected at startup, and the agent trusts it more than it should. This is worse than no memory, because it’s confidently wrong. The confidence field and human review of the memory store are the only real mitigations.

Stale context is a silent killer. You refactor UserService on Tuesday. Your Monday memory still says “auth lives in UserService.” Nothing invalidates it. Memories decay in accuracy the moment the code changes, and there’s no clean signal to expire them. Tie memories to file hashes or commit SHAs and treat them as suspect when the underlying file moves.

Token costs rise on every session. Injected memory is context you pay for on every turn, whether or not it gets used. A memory layer that injects 4K tokens at startup adds that to your input cost for the entire session. The pull-based MCP model helps, but capture and compression are their own recurring spend.

Here’s the honest comparison:

ApproachSetup costRecurring tokensStaleness riskBest for
CLAUDE.md onlyMinutesLow, deterministicYou control itMost projects
Scoped MCP toolsHoursOn-demand onlyLowLive system state
Full memory layerDaysHigher, per-sessionHighLong, exploratory work

The uncomfortable truth: you probably don’t need it

Before you build any of this, write a good CLAUDE.md. A hand-curated file that says “we use Result<T>, never throw; UserService must not import billing/; auth was moved to auth/ in Q2” solves 80% of the memory problem with zero hallucination risk and deterministic cost. It’s human-authored, so it’s trustworthy by construction.

Memory layers earn their keep in a narrower band than the hype suggests: long-running, exploratory work where the derived knowledge (dead ends, half-finished investigations) genuinely can’t be pre-written because you don’t know it yet. For steady-state feature work, CLAUDE.md plus a couple of scoped MCP servers covers more ground than an automated memory pipeline, and it never lies to you.

If you do build the pipeline, the compression stage is where cost sensitivity matters most — running frequent Haiku summarization is cheap, but only if your per-token pricing is reasonable to begin with, which is one reason teams route this kind of high-volume background work through providers like AI Prime Tech.

Practical takeaways

SA
Sofia Almeida · AI Infrastructure Architect

Sofia designs agent and retrieval pipelines around Claude and the Model Context Protocol. She focuses on agentic reliability, evaluation, observability, and turning Claude Code from a demo into a dependable engineering tool.

Get cheaper Claude API access

One API key for Claude Opus 4.8, Sonnet 4.6, Haiku 4.5, Fable 5, plus GPT & Gemini — up to 80% off official pricing, pay-as-you-go.

Get Your API Key →
AI Prime Tech is an independent third-party API gateway. Claude™ and Anthropic® are trademarks of Anthropic, PBC. No affiliation or endorsement is implied.