Every coding session with an AI agent starts from zero. The agent does not remember yesterday. It does not know which approach you already rejected, or which bug you already fixed, or why the database column is named the way it is.
There are two obvious ways to fix that, and both of them fail.
Read everything. Feed the agent the full history of the project at the start of each session. This works for a week. Then the history is longer than the context window, and every session costs more than the one before it.
Read the newest few. Feed the agent the last three notes. The cost stays flat, but the memory does not. A decision made in June falls off the back of the list in July and is gone. The system remembers the most recent work, which is rarely the most important work.
claude-vault is my answer to that trade-off. It is the memory layer my Claude Code sessions run on. This note describes what it is, not how I got there: the four stages of the loop, the rules each stage follows, and the two properties the whole design exists to guarantee.
The loop, end to end
Four stages run in a circle. A session resolves which memory belongs to it, reads that memory back, does the work, then folds what it learned into the memory it read from. The output of stage four is the input of stage one, next time.
_master.md — rewritten in stage 4 and read
back in stage 2. The rail down the left margin, interrupted by its own name plate, is that
return trip. It is the only long connector in the figure because it is the only one that
matters: that rail is the loop.
Stage 1 — Resolve: which memory belongs to this directory?
Before anything can be read, the system has to answer one question: which project is this? That sounds trivial. It is not, and getting it wrong is silent. The session still runs. It just runs with the wrong memory, or with none.
The resolver is a fallback chain. Three steps, in order, first match wins.
Step 2 is the load-bearing one, and the exact git command matters. The obvious choice,
--show-toplevel, returns the directory you are standing in. In a worktree —
a second checkout of the same repository, used to run parallel branches — that is the
worktree, not the project. Five worktrees of one repository produce five separate memories,
each holding a fifth of the history, and none of them announcing that anything is missing.
--git-common-dir returns the primary repository instead, so every worktree
resolves to one project.
One rule holds this together: the resolver lives in exactly one file, and everything else calls it. Not because duplicated logic is untidy, but because duplicated logic drifts. Three copies of "which project is this" cannot be kept identical by discipline, and when they disagree the symptom is a memory that is quietly incomplete.
Stage 2 — Recall: what gets read back, and what it costs
A hook runs when a session starts. It reads three things and hands them to the agent as context. Each has a hard limit, set in code.
| Source | Limit | Why it is in the budget |
|---|---|---|
| _master.md | 8,000 chars | The rolling digest. Everything the system understands about this project, compressed. This is the layer that compounds. |
| 2 newest notes | 3 sections 600 chars each |
Recent detail the digest has not absorbed yet. Only Summary, Key Decisions, and Next Steps are extracted. |
| patterns/*.md | 25 lines | Standing rules that apply to every project, one line each. Cheap enough to carry unconditionally. |
The whole block is wrapped in a marker: === VAULT RECALL ===. That fence is not
decoration. It tells the write path, later in the same session, that this text is old memory
rather than new work.
Without it the system develops a specific and nasty failure. The agent reads the digest, then at save time summarises the session — including the digest it just read. The summary of the summary goes back into the digest. Each cycle moves the memory one step further from what the notes actually said, and each cycle sounds more confident than the last. A memory that folds its own output back in without knowing it will drift, and drift quietly.
Two more guarantees the read path holds. It never blocks a session: if the resolver is missing, if a file is unreadable, if the vault directory does not exist, the hook degrades and exits cleanly. And it writes nothing, anywhere. Reading memory cannot corrupt memory.
The hook loads the resolver by dot-sourcing it, which in PowerShell executes the other file inside the caller's own variable scope. Both files had a variable named for the vault root. The resolver's value silently overwrote the hook's, every lookup pointed one directory too high, and every project reported zero memory.
Both files were correct on their own. The defect existed only in the composition. The fix was mechanical — prefix every shared-scope variable — but the rule it produced is the useful part: test the composed path, never only the unit.
Stage 3 — The session, and the notes nobody reads
While work happens, a separate hook writes one small note per git commit. There are 843 of these files. They are the densest record in the store, and they are never injected directly — one note per commit would fill the context window on its own.
So they take a different route. They are input to the fold, not input to the session. The distinction is worth stating plainly, because it is the piece that most append-only memory systems are missing: a file being valuable is not a reason to read it at session start. It is a reason to make sure something eventually compresses it.
Stage 4 — The fold: three targets, three write modes
Saving a session writes three files in one pass. Each one is written in a different mode, and the mode is the design.
| Target | Mode | Rule |
|---|---|---|
| sessions/<project>/ <date>-<slug>.md |
APPEND | Never edited, never deleted. This is the audit trail, and the only thing a full rebuild is allowed to read from. |
| sessions/<project>/ _master.md |
REWRITE | Read the current digest, merge this session into it, write the whole file back. The watermark advances so the next fold reads only what is new. |
| <repo>/CLAUDE.md | PATCH | Only the text between two sentinel comments is replaced. Everything outside is preserved byte for byte. If only one sentinel is found, the script refuses to write. |
The middle row is the mechanism the whole system is named for:
The digest is not a list of sessions. It is six fixed categories, and every fold rewrites all six.
That last sentence is the bound, and it is worth being precise about why it holds. The digest is keyed by category, not by recency. Its size does not follow how much was written down. It follows how many kinds of things are known. Write four hundred more notes about the same six categories and the digest gets more accurate, not longer.
The fold rules
| Rule | Reason |
|---|---|
| Merge, never append | One line per fact. Appending is what produces a log, and a log is the thing being replaced. |
| Newer wins | On a conflict, the later session is correct. Say so out loud when the change is material. |
| Drop superseded | A reversed decision leaves the digest completely. It stays in the append log, where history belongs. |
| Cap every cell | At the limit, compress. Never spill into a seventh cell. |
| Never invent | Every line traces to a note. Counts are re-checked when used, never carried forward from memory. |
| State, not narrative | "Parked on branch X awaiting merge" beats "we discussed whether to park it." |
| Advance the watermark | So the next fold reads only what is new, and re-running with nothing new does nothing. |
What the loop actually buys
The heaviest project in my vault has 665 note files, holding about 688,000 characters of raw history. Its digest is 9,074 characters. That is roughly 76 times smaller, and it is the version the next session actually reads.
The ratio is not the interesting number, though. The interesting number is the one that does not move. Across projects holding anywhere from a single note to six hundred and sixty-five, every digest lands between about 2,200 and 9,100 characters. Priming a session costs about the same on a project I started yesterday as on one I have worked in for eight months — and on the old one, the agent gets all of the history instead of the most recent slice of it.
The technique is not new to this system. I first built it as a standalone experiment against a different memory store, where the two load-bearing ideas showed up: key the summary by category rather than by recency, and keep a watermark so each run only reads what arrived since the last one. This is that idea wired into the loop I use every day, rather than run as a batch job beside it.
The line between the script and the model
One boundary runs through the whole design, and it is the same boundary I use in every agent system I build.
The scripts do the mechanical work. Resolve the project. Read files and strip the byte-order mark. Find the sentinels. Replace the block between them, or refuse. Return an exit code that says which happened. All of it deterministic, all of it testable, none of it requiring a model.
The model does the merge. Deciding that this session's "we moved the launch date" supersedes a line written three weeks ago, that two differently-worded gotchas are the same gotcha, and that a paragraph can become a clause — that is a judgement about meaning. A deterministic merger cannot consolidate prose. It can only concatenate it, which is the problem being solved.
Where it is honest to say this is incomplete
Three limits, stated plainly.
The size cap is requested, not enforced. The hook will inject at most 8,000 characters of digest. The largest digest in the vault is 9,074, so it is currently truncated at read time, with a marker telling the agent to re-compress it. A limit written in a prompt is a strong suggestion to a model. It does not become a guarantee until code truncates or rejects, and mine does not yet.
Coverage is partial. 20 of 31 project folders have a digest. The rest report that fact honestly at session start rather than pretending to be empty, which is the right behaviour, but it is still eleven projects running on the old shallow recall. And on the smallest projects the digest is not a saving at all: six cells have a floor cost of roughly 2,200 characters whether the project has one note or fifty. This technique earns its place on deep history, and nowhere else.
One user, one machine. This is a local git repository with no remote. It works for a single operator across many projects. Nothing here has been tested with two people folding the same digest at once, and the merge step — a model rewriting a whole file — is exactly the kind of operation that would need real conflict handling first.
What holds regardless of scale is the shape. An agent's memory should be two layers, not one. The log is append-only and grows forever, because history has to stay checkable. The understanding is rewritten in place and stays flat, because that is what you actually read back.
Do not keep every note you ever took. Keep a good summary, and revise it every time you learn something.