There are a hundred "give Claude Code a memory" posts and almost all of them say the same thing: put your context in CLAUDE.md and it loads every session. True, and also the start of a problem.
Once that file grows past a screen or two, you're not giving the model memory — you're charging yourself a context tax on every single conversation. Every token in CLAUDE.md is re-read on every turn whether it's relevant or not. A 4,000-token "memory" file means the model wades through your entire life story to answer "rename this variable."
Real memory isn't storing everything. It's recalling the right thing at the right moment. Here's the format I landed on after watching a monolithic memory file slowly poison every session with irrelevant context.
One fact, one file
The unit of memory is a single file holding a single fact. Not a section. Not a bullet under a heading. One file.
memory/
├── MEMORY.md # the index (always loaded)
├── user_identity.md # who I am, how I work
├── feedback_no_guessing_numbers.md # a correction I gave once
├── project_q3_migration.md # an ongoing constraint
└── reference_staging_dashboard.md # a pointer to an external thing
Why one-fact-per-file instead of headings in a big doc? Three reasons that only show up after a few weeks of use:
-
Files can be deleted cleanly. When a fact goes stale, you
rmit. Deleting a section out of a 600-line file is the kind of chore nobody does, so stale context accumulates and quietly degrades every answer. - Files can be recalled selectively. The index loads every session; the bodies load only when relevant. A monolith has no granularity — it's all-or-nothing.
- Files have provenance. Each one carries when and why it was written, so a fact that was true in March doesn't masquerade as true today.
The frontmatter is the part everyone skips
Every memory file gets a small YAML header. This is the difference between "a folder of notes" and "a system that recalls."
---
name: feedback-no-guessing-numbers
description: "Never state a dollar/metric figure without verifying it from a source first."
metadata:
type: feedback
---
When I ask for a number, pull it from the API/DB/explorer — don't estimate.
**Why:** an estimated revenue figure once drove a wrong decision.
**How to apply:** if you can't verify it, say "unverified" rather than guess.
Related: [[project-q3-migration]]
Two fields do the heavy lifting:
-
descriptionis the recall key. When deciding whether a memory is relevant to the current task, that one line is what gets scanned — not the whole body. Write it as the thing this memory would help with, not a title."Never state a figure without verifying"beats"numbers rule". -
typesorts the fact into a category —user,feedback,project,reference. This matters because the four types have different lifespans.userfacts are durable.projectfacts expire. Knowing the type tells you (and the model) how much to trust an old one.
The index is loaded; the bodies are not
MEMORY.md is the only file guaranteed in context every session. So it holds one line per memory and nothing else:
# Memory Index
## How I work
- [No guessing numbers](feedback_no_guessing_numbers.md) — verify before stating any figure
- [User identity](user_identity.md) — role, stack, comm style
## Active projects
- [Q3 migration](project_q3_migration.md) — Postgres cutover, hard deadline Aug 1
That's it. The index is a table of contents, not a container. The instant you start pasting fact content into the index, you've reinvented the monolith and the context tax comes right back.
The mechanic: the model reads the index, sees a one-line hook that matches what it's about to do, and then opens that specific file. You pay for the body only when it's relevant. A 40-memory system can have a 40-line index and still load almost none of the bodies on a given turn.
Linking, so recall finds the neighbors
Inside a body, link related facts with [[name]] (the other file's name: slug). When the model pulls one memory, the links tell it which neighbors might also matter — the migration constraint links to the deadline, which links to the on-call rule. Link liberally; a [[name]] that doesn't exist yet is fine — it's a marker for a memory worth writing later, not an error.
The failure mode nobody warns you about: stale memory is worse than no memory
This is the one that bit me. A memory that says "the deploy script lives at scripts/deploy.sh" is actively harmful the day someone moves it. The model will confidently recommend a path that no longer exists, and it'll sound exactly as sure as a correct answer.
Two rules keep this from rotting:
- Convert relative dates to absolute when writing. "We're migrating next month" becomes "migrating by 2026-08-01." A memory read in October shouldn't think "next month" means November.
- Verify before recommending. If a recalled memory names a file, function, or flag, the model should confirm it still exists before acting on it. Treat memory as what was true when written, not what is true now.
Memories aren't append-only truth. They're timestamped claims. The system is only as good as your willingness to delete the wrong ones.
Why not a vector DB / RAG setup?
Because for this job it's overkill that buys you nothing. The whole corpus is a few dozen short markdown files — it fits in a table of contents. There's no embedding server, no similarity threshold to tune, no retrieval black box to debug. Plain files mean you can read your AI's entire memory in your editor, git diff what changed, and delete a bad fact with rm. The simplest thing that recalls the right context wins, and at this scale a folder of markdown is the simplest thing.
Try it
I keep the templates — the frontmatter schema, the index format, the four types, and the maintenance rules — in a free, zero-dependency repo. Copy the structure, point your agent at the memory/ folder, and let it write its own facts as you correct it.
→ github.com/LuciferForge/claude-code-memory
If you've built your own version, I'd genuinely like to see the format you settled on — the Discussions board is open. The most interesting question to me right now is when to delete — nobody has a clean rule for memory expiry yet.
Top comments (0)