Persistent Memory: How Claude Code Remembers
How Claude Code's file-based persistent memory works — one fact per file, a MEMORY.md index, four memory types, and hygiene rules that keep recall useful.
What You’ll Learn
For most of Claude Code’s history, every session started from zero. You could encode standing rules in CLAUDE.md, but everything learned during a session — the correction you made on Tuesday, the deploy gotcha you discovered last month — evaporated when the session ended. The Fable 5 era changes that with a persistent, file-based memory directory per project.
By the end, you’ll understand:
- Where memories live and the one-fact-one-file format
- The four memory types:
user,feedback,project,reference - How
MEMORY.mdacts as an always-loaded index - Wiki-style
[[links]]between memories - The hygiene rules that keep memory useful instead of noisy
- How recall works (and why recalled memories must be re-verified)
- How context summarization replaced hard compaction
The Problem
An agent without memory forces you to be its memory. Every session, you re-explain that the staging database is the one with the weird name, that you prefer squash merges, that the flaky test was already investigated and it’s the CI runner’s clock. That’s real cost, paid on every session start.
The naive fixes both fail:
- Reload the chat log. Transcripts are enormous and mostly noise — tool outputs, dead ends, abandoned approaches. Loading them wholesale burns the context window on garbage.
- Stuff everything into CLAUDE.md. CLAUDE.md is for standing rules — how to build, what conventions to follow. Mixing in ephemeral discoveries (“the auth refactor is half-done”) turns your rules file into a junk drawer that goes stale.
What’s needed is a third thing: curated, durable knowledge that accumulates across sessions, loads cheaply, and can be corrected when it’s wrong.
How It Works
One Memory, One File, One Fact
Each project gets a memory directory:
~/.claude/projects/<project-slug>/memory/
├── MEMORY.md ← the index (always loaded)
├── user_prefers-squash-merges.md
├── feedback_use-pnpm-not-npm.md
├── project_auth-refactor-status.md
└── reference_staging-dashboard.md
The rule is strict: one memory = one file = one fact. Each file carries YAML frontmatter:
name— a short kebab-case slugdescription— a one-line summary, used for relevance during recallmetadata.type— one ofuser,feedback,project,reference
Why one fact per file? Because facts change independently. When the auth refactor finishes, you delete that file — not hunt through a monolithic notes document for every stale sentence it contaminated.
The Four Memory Types
| Type | What it captures | Example |
|---|---|---|
user | Who the user is — role, expertise, preferences | ”Prefers squash merges; senior backend dev” |
feedback | Corrections and confirmed approaches, with Why and How to apply | ”Use pnpm, not npm — npm corrupts the lockfile” |
project | Ongoing work, goals, constraints not derivable from code or git history | ”Auth refactor paused pending security review” |
reference | Pointers to external resources — URLs, dashboards, tickets | ”Staging metrics live at the Grafana dashboard” |
The feedback type is the workhorse. Its body should answer two questions: Why is this the right approach, and How to apply it next time. A correction without those is just a rule the agent will misapply in a slightly different situation.
Note what’s not a type: “code structure.” That’s deliberate — see the hygiene rules below.
MEMORY.md: The Always-Loaded Index
Loading every memory file each session would recreate the chat-log problem. So the system uses the same deferred-loading philosophy you saw with skills in Session 5: names upfront, content on demand.
MEMORY.md is the index — one line per memory:
- [Use pnpm, not npm](feedback_use-pnpm-not-npm.md) — npm installs corrupt the lockfile
- [Auth refactor status](project_auth-refactor-status.md) — paused pending security review
The index is loaded into context each session. Memory content stays in the individual files; only pointers live in the index. The agent scans one line per fact and pulls full files only when relevant.
Wiki-Style Links
Memories link to each other with [[wiki-style]] links by slug:
The lockfile issue first surfaced during the [[project_auth-refactor-status]] work.
Dangling links are fine — a link to a memory that doesn’t exist yet simply marks something worth writing later. It’s a cheap way to leave a breadcrumb without stopping to write the full fact.
Recall Is Background Context, Not Gospel
When memories are recalled mid-session, they appear inside <system-reminder> blocks — harness-injected notes, the same mechanism used for tool-availability changes and mode changes. Two properties matter:
- They’re context, not user instructions. A recalled memory doesn’t command; it informs.
- They reflect what was true when written. The codebase has moved since. Recalled memories must be re-verified against the current code before acting on them.
That second point is the difference between memory helping and memory hurting. A six-week-old note saying “the config loader lives in src/utils/” is a lead to check, not a fact to build on.
The Three Knowledge Layers
Memory doesn’t replace CLAUDE.md or skills — it completes a stack:
CLAUDE.md → standing rules ("always use pnpm; run tests before commit")
Memory → accumulated experience ("we tried X on 2026-06-12; it broke Y")
Skills → procedures ("how to cut a release, step by step")
Rules, experience, procedures. If you find yourself writing a memory that reads like a rule, move it to CLAUDE.md. If it reads like a step-by-step process, it wants to be a skill.
Summarization Replaced Hard Compaction
Persistent memory pairs with a change in how long conversations are handled. In the old era (Session 6), hitting the context limit triggered “auto-compact.” In the Fable 5 era, when the conversation grows long, some or all of the context is summarized; the summary plus any remaining unsummarized context carries into the next context window, and work continues seamlessly. The agent is explicitly told it does not need to wrap up early or hand off mid-task.
Two supporting details round this out:
- Scratchpad directory: each session gets a session-specific scratchpad directory for all temporary files (instead of
/tmp) — isolated from the project and generally usable without permission prompts. Ephemeral working files go there, not into memory. - The division of labor is clean: summarization preserves this session’s thread; memory preserves what should outlive the session; the scratchpad holds what shouldn’t outlive it at all.
Hands-On Example
Here’s a realistic feedback memory, written the way the format intends — one fact, a recall-friendly description, and a body with Why and How to apply:
# File: ~/.claude/projects/my-shop-app/memory/feedback_use-pnpm-not-npm.md
---
name: feedback_use-pnpm-not-npm
description: Always use pnpm in this repo — npm installs corrupt pnpm-lock.yaml
metadata:
type: feedback
---
# Use pnpm, not npm
Confirmed on 2026-07-08 after a broken deploy.
## Why
The repo is a pnpm workspace. Running `npm install` writes a
package-lock.json and desyncs pnpm-lock.yaml, which broke the
CI install step (see [[project_ci-pipeline]]).
## How to apply
- All installs: `pnpm install`, never `npm install`
- Adding deps: `pnpm add <pkg>` in the relevant workspace package
- If package-lock.json ever appears, delete it and re-run `pnpm install`
And its index line in MEMORY.md:
- [Use pnpm, not npm](feedback_use-pnpm-not-npm.md) — npm installs corrupt pnpm-lock.yaml
Notice the details:
- Absolute date. “Confirmed on 2026-07-08,” not “confirmed yesterday.” Relative dates rot — “yesterday” is meaningless when the file is read three months later.
- Dangling link.
[[project_ci-pipeline]]may not exist yet. That’s fine — it marks a memory worth writing. - The description earns its keep. It’s the line used for relevance during recall, so it states the fact, not just the topic.
The Hygiene Rules
Memory quality is maintenance, not accumulation. Four rules:
- Update, don’t duplicate. If a fact changes, edit the existing file. Two files disagreeing about the same fact is worse than no file.
- Delete wrong memories. A memory proven false is negative-value. Remove it.
- Don’t save what the repo already records. Code structure, git history, anything in CLAUDE.md — the repo is the source of truth for those, and it’s always current. Memory that shadows it goes stale by definition.
- Convert relative dates to absolute. “Last week” becomes “2026-07-01.”
What Changed
| Before Persistent Memory | With Persistent Memory |
|---|---|
| Every session starts from zero | Curated facts persist per project |
| You re-explain preferences and gotchas | user and feedback memories carry them |
| Learned context dies with the session | One-fact-one-file survives it |
| Chat log is the only record (noisy, huge) | MEMORY.md index: one line per fact |
| CLAUDE.md becomes a junk drawer | Rules, experience, procedures — separate layers |
| Hard compaction at the context limit | Summarization carries work across windows |
Key Insight
Memory is curated knowledge, not a chat log.
The design keeps making the same choice at every level: distill, don’t accumulate. One fact per file forces each memory to be a deliberate, editable unit. The index-plus-files split means the always-loaded cost is one line per fact — the same names-upfront, content-on-demand philosophy that makes skills scale. The hygiene rules exist because memory’s failure mode isn’t forgetting — it’s confidently remembering things that are no longer true.
That’s also why recall arrives as background context to re-verify rather than instructions to obey. A good memory system doesn’t make the agent trust its past; it makes the past cheap to check.
Next Session
Session 30 covers Deferred Tools & ToolSearch — how the names-upfront, definitions-on-demand philosophy you’ve now seen twice (skills, memory) got generalized to the entire tool surface, and why that matters most for MCP servers with large tool catalogs.