Documentation menu
explanation

What the repo
remembers about itself.

Every session starts from nothing unless something carries facts forward. Project memory is that carrier. It holds eight files of durable knowledge and keeps your exact words where your words matter. It re-checks a fact before anyone leans on it, and it can hold a commit until the record is straight.

canonical files
7
inbox
_pending.md
curated by
/memory-sync

The eight files

Memory lives at .claude/memory/ and travels with the repo. Each file holds one kind of knowledge, and every entry has a stable key. A second entry under the same key replaces the first, so nothing piles up beside it.

landmarks

Where things live in this repo.

written by scout · key: path:line

libraries

Third-party APIs that were checked against current docs, pinned to the version checked.

written by research · key: <lib>@<version>

decisions

Choices made and why, plus the options that were turned down.

written by spec · rca · key: short slug

landmines

Traps. Edit this and you have to edit that too.

written by security · integrate · scout · key: path:line or slug

conventions

How this repo writes its tests and lays out its code.

written by scenario · implement · key: short slug

pending-questions

Questions a session could not settle, and what each one blocks.

written by any phase · key: Q-NNN

backlog

Future work, picked up from what you and Claude said you would come back to.

written by /memory-sync · key: slug plus a 4-character hash

constraints

Facts about the world this project has to build around, and whether each still holds. When one stops holding, every decision resting on it is suspect.

written by spec · scout · key: short slug

Each file is owned by the phase which learns its kind of fact. Scout finds where things live, so scout writes landmarks; research checks a library API, so research writes libraries. The knowledge lands next to the work that produced it, while the reason for it is still fresh in the session.

Two shapes are supported. A small project keeps one flat file per category, capped at 500 entries, and the oldest unverified entries fall off the end. A larger one shards each category into a directory of single-fact files, where the cap goes away. Every reader handles both, because the baseline ships you the flat shape and a shard-only reader would go quiet on a fresh install without saying so.

From inbox to canonical

Nothing writes straight into a canonical file from a conversation. Candidates land in an inbox first, and a person decides what survives.

At the end of every turn, the memory_stop hook reads what just happened and pulls out anything which looks like a durable fact. Those candidates go to _pending.md, which is an inbox and holds no authority of its own.

At flush time a router reads each candidate and proposes where it belongs, matching purely on shape. A file path reads as a landmark, a line ending in a question mark reads as an open question, words like follow-up or defer read as backlog, and a phrasing such as "decided to" reads as a decision. Each suggestion carries the cue it fired on and a weight between 0.1 and 0.7, so obvious chatter sorts to the bottom.

The router is deliberately dull: no filesystem, no network, no model call, and it writes nothing at all. It hands you a default to accept or override, and promotion to a canonical file stays a human act.

the path a fact takes
turn ends      memory_stop   → _pending.md
flush          route         → suggested bucket + weight
               you           → accept, move, or discard
               write         → the canonical file, with provenance

The split matters more than it looks. Automatic capture is generous by design, because a fact nobody noticed is simply gone, while curation is strict, because a memory file full of noise is one nobody reads. Separating the two lets each side do its job honestly.

An empty inbox costs almost nothing. The flush short-circuits, runs its housekeeping sweeps, and reports that there was nothing to curate, so a workflow which learned nothing new pays only a bounded price for saying so.

Your words and Claude's reading of them

Every entry declares where the rule came from, so you can trace any remembered fact back to its source. An entry learned by reading the codebase says so. One learned from a documentation lookup says so too, and pins the version it read, which is how you tell that it has gone stale.

Two sources carry a stricter rule. If you state a directive, or correct Claude's behaviour, the resulting entry must quote you.

an entry with a verbatim
## dont-mock-the-broker-in-channel-tests

> verbatim (user, 2026-06-14):
> run it against a real socket, a mocked broker
> has never once caught the bug I care about

- source: user-feedback
- verified-at: 8f2c1ab
- last-touched: 2026-06-14

The quote is the canonical part. The body underneath is Claude's reading of it, and readings drift, so when the two disagree your words win and Claude must raise the conflict with you before acting on its own paraphrase.

/memory-sync enforces this at the boundary. If a candidate claims to come from you and carries no quote, promotion is refused and it stays a candidate.

You can add quotes over time. Clarify an instruction across three turns and the entry keeps all three blocks, oldest first, because how a rule got sharpened is often the thing that explains it.

Every deferral names what stopped it

Where the rule above governs what you said, this one governs what Claude said about its own unfinished work. An entry carrying source: assistant-deferral must also carry deferred:, and the value must be one of four: dependency, risk, cost, or human-directed. Anything else will fail the review, as will an entry that omits the field entirely.

Because the checker reads only the files a change actually touches, the requirement applies on touch rather than retroactively. An entry that Claude writes or reopens must carry its reason before the change can land; the entries already sitting in your backlog from before the rule existed stay exactly as they are until somebody picks one up.

Claude can still defer work, and for a genuine dependency or a cost you have not agreed to spend, it should. What it can no longer do is defer silently. Read the backlog six weeks later and each open item tells you which of the four reasons applies, which is the difference between a list of work nobody did and a record of decisions somebody made.

Verification, closure and decay

An entry leaves the store three ways. Only one of them is time passing, and it no longer touches the reasoning you most want to keep.

It fails a check. Citing an entry means re-checking it first. Does the file still exist? Is the symbol still at the line the entry names? Is that library still pinned to the version that was validated? An entry that fails gets corrected or deleted on the spot, in the same run, before the work carries on.

It closes. Entries carry closure stamps. An answered question gets resolved-at; anything superseded on the other seven files gets superseded-at. Every flush sweeps for those stamps and deletes what they mark. A stamp on the wrong file, or a malformed date, is reported and the entry is kept, because guessing is how a real fact gets swept away.

People usually write closure in prose rather than stamping it, so a second sweep scans entry bodies for the phrasings they actually use, along the lines of "superseded by", "resolved on" and "resolution path taken". Every match is surfaced for you to answer: close it, keep it, or leave it for next time.

It ages out when its subject moves. An entry names the paths it governs, and it goes stale when one of those paths changes after the entry was last verified. An entry that governs nothing falls back to 30 days. The next phase that touches a stale entry either re-checks it or removes it. What you get is a queue of entries whose subject actually moved, instead of everything written in the last few days. Two categories sit outside that rule. A decision expires when something supersedes it, so one you made a year ago still holds today. Backlog holds intent, and code can confirm a fact but never an intention. A constraint ages like everything else, and deliberately so: it records whether something about your environment is still true, and that is exactly what changes while you are looking elsewhere.

When a constraint stops holding, flipping it does more than close one entry. Every decision that named it in rests_on is listed at your next session start, grouped under the constraint that changed. A superseded constraint does not supersede the reasoning built on top of it, so those decisions stay exactly as they were until someone looks at them again. That edge is the whole reason a constraint is a category of its own rather than a field on a decision.

One precondition guards all of this. Before any sweep runs, the store is checked for stamps still sitting in entry bodies instead of the frontmatter, and while any remain the session-start reader and the sweep will count different entries as stale. Curating against the wrong count churns entries and leaves the real problem in place, so the sweep stops and says so.

The result is a store which heals itself while being used. Nobody schedules a memory cleanup, because wrong entries are caught by the next task that depends on them, which is the one moment somebody actually cares.

Memory at the commit gate

Here is what makes memory more than a notebook. It can stop a commit.

When /triage starts work from a backlog entry, it records that entry's key in the workflow state. The work now carries a debt: the backlog said this was coming, and landing it ought to close the record.

At commit time, /commit stamps each of those backlog entries picked-up with today's date and stages the change. If the staged backlog.md is then missing a stamp for any key the workflow claimed, git_commit_guard blocks the commit and names the keys in the message.

The stamp has to land in the same commit as the work, and splitting the two is refused. A backlog entry which closes one commit later will still be open when the branch is cut, and the next person reads it as work nobody started.

There is a second lock. Unless memory-sync sits in the workflow's completed list or has been explicitly excepted, /commit refuses to run at all, so whatever the session learned is curated before anything lands.

Wiring memory into the commit path is a deliberate choice, and it has a cost: you cannot land work while the record is behind. That is the point. Memory that nothing depends on rots quietly, and the rot only shows up months later when someone trusts it.

Facts that fire at a decision point

A fact can name the workflow phases it applies to. When one of those phases begins the fact is surfaced inline, before any of the work starts.

What gets surfaced is the quote, first and whole. A paraphrase is Claude's reading, and the reading is the part that drifts, so the words you actually said are what reaches the decision.

Scoping a fact to a phase is coarse on its own, because a busy phase can carry more entries than anyone reads. When a workflow declares which paths it expects to touch, phase surfacing narrows to the facts governing those paths, so a scout write shows the landmarks for the code this work actually reaches.

A workflow that declares no surface loses nothing. Every fact scoped to the phase surfaces exactly as it did before, and a fact carrying no path of its own is never dropped for lack of one. Narrowing removes an entry only when the declared surface proves the work will not reach it.

The same mechanism runs against shell commands. An advisory guard watches for the command shapes a landmine entry warns about and prints that warning before the command runs, at which point the entry stops being a record and becomes a constraint on the operation in front of you.

This is the whole argument for keeping memory at all. A lesson filed away and read by nobody is archaeology, and a habit of filing them is worse. A lesson that appears at the point where it would have been ignored is a guardrail.

Where the record surfaces

Two of the eight files exist to be read back to you, and one command does the reading.

/standup reports the store. It buckets backlog entries into open, picked-up and dropped, nesting epic children under their parent, and condenses open questions down to an id, the question, and what each one blocks. Alongside those it reads your roadmap tracker, so a single recap covers what you meant to do and what is left of it.

That closes the loop the commit gate opens. A backlog entry gets picked up by triage, stamped closed by the commit that lands it, deleted by the next flush, and gone from the next recap. Work you finished stops asking for your attention, and nobody had to tidy up.

The roadmap tracker sits beside memory instead of inside it, and two skills keep it honest. Your plan stops drifting from your work, because every track that commits now reports into it. /roadmap-sync runs at phase 10.6, and what it does there depends on what the track landed. A track that lands code flips the tasks your workflow named from planned to done and promotes their epic heading. A heading you set by hand no longer strands the roadmap in a state nobody chose. After the named rows flip, the phase recomputes every remaining epic heading against its own task rows and reports what it repaired under healed. It does that even when your workflow named no task at all, which is the case where a stale heading would otherwise sit forever. An epic lands discovery, so it has no row to flip yet. That run appends the epic's own heading and one row per slice, tagged with the epic slug, and stamps the number it assigned into the epic state as roadmap_epic. Each child reads that number to find its own row.

It is built to stay out of the way: it never throws, it does nothing at all where no roadmap is configured or no task matches, and it never blocks a commit. A roadmap is a plan, and a plan which can veto shipped work is a gate wearing a plan's clothes.

roadmap-planner is the other half, and you run it on purpose. It re-derives the roadmap from your vision and spec documents by first principles, then diffs that fresh derivation against the plan you already have. What it is really checking is order: whether a task sits ahead of something it depends on. Reach for it when the plan feels wrong and you cannot say why.

Session memory

Two more files sit in the same directory and do a different job. Both are gitignored, because they describe one session instead of the project.

_resume.md

A continuity snapshot: the last phase completed, what is due next, the files in flight, your recent prompts. Rewritten at every turn-end and again before the context is compacted, then re-injected the moment a session starts.

_thread.md

A durable trail of shelved threads. One rolling section per shelve, holding your exact words, the open questions and the next step. Claude Code shelves and resumes it on its own; there is no command for you to run.

The difference between them is lifespan. _resume.md is overwritten constantly and describes only the present moment. _thread.md is append-only and sits outside the flush. A thread you shelved two weeks ago survives a /clear, a compaction and every flush in between.

At session start you get an index of the store, not the store itself: one row per category with its entry count and how many have gone stale. It is capped in size, so a large store still leaves room for your code.

What memory cannot do

Memory speeds up triage. A landmark can save a scout pass, a pinned library version can save a research pass, and a landmine stops the same trap catching a second person.

What it can never do is authorise a skip. Remembering that a phase went fine last time is evidence about the past, whereas skipping one takes a named exception recorded in the workflow state where the audit can see it. The two are kept apart deliberately, because a store which could licence its own shortcuts would soon start finding reasons to.

last updated 2026-08-25 edit on GitHub →