Collapse per-project, per-machine agent memory into one baseline you can actually read.
Claude Code stores memory per project, keyed by absolute path
(~/.claude/projects/-home-user-Projects-foo/memory/). Nothing is shared between
projects, and nothing is shared between machines. Give the same instruction in five
repos and you get five files that drift, duplicate, and eventually contradict, and a
new project starts with none of them.
The cost is not storage. It is that a rule you wrote down months ago does not apply where you need it, so the same correction gets made again, and the same mistake gets repeated in the project where the rule happens not to live.
This kit harvests all of it, distills the cross-cutting rules into one user-level
CLAUDE.md, and rolls that out everywhere.
Bash, git, and Claude Code. Tested on macOS and Linux, across four machines.
- Harvest.
harvest.shcopies this machine's memory into a timestamped directory. Read-only against~/.claude. Run it on every machine. - Consolidate.
prompts/01-consolidate.mdinventories every rule, groups them by what they mean rather than what they are called, and classifies each group as promote, tier-2, keep-local, split or drop. Contradictions are listed as questions for you rather than resolved. It works one machine at a time, appending to the audit as it goes, so an interrupted run resumes instead of restarting. - Draft.
prompts/02-draft-baseline.mdturns that audit, plus your answers to its questions, intotemplates/CLAUDE.md,templates/rules/*.md, a tier-3 skeleton attemplates/CLAUDE.project.md, andPRUNE.md, the delete list. Every row in the delete list cites a backup path, and the path is checked to exist before the row is written. - Roll out.
prompts/03-rollout.mdcopies the baseline into your dotfiles and symlinks it into~/.claude.prune-move.shthen moves the superseded per-project files out, and the per-project indexes get reconciled to what the script actually did, taking the filesystem as the source of truth rather than the script's output.
Nothing in steps 2 to 4 deletes anything on its own. The prompts write files and stop. You review and execute.
| tier | file | scope | tracked in |
|---|---|---|---|
| 1 | ~/.claude/CLAUDE.md |
universal, every project every machine | dotfiles |
| 2 | ~/.claude/rules/<stack>.md |
per-stack, imported by a project that needs it | dotfiles |
| 3 | <project>/CLAUDE.md |
one repo's build/architecture/conventions | that project |
| 4 | ~/.claude/projects/*/memory/ |
auto-memory; project facts only, never rules | nowhere |
Tier 1 loads in full in every session, so it is budgeted by attention rather than by
bytes. A dozen rules all marked "always" means none of them is rule 1. The cap and the
tier-1/tier-2 split are specified in prompts/02-draft-baseline.md.
@import inlines at session start with no lazy loading, so stack-specific files must be
imported from a project's own CLAUDE.md, never from tier 1, or every project pays for
every stack.
Tier 4 is where everything ends up by default, which is the problem. The point of the kit is to keep tier 4 holding only project facts, this codebase's quirks, and to move every behavioural rule up to tier 1 or 2 where it applies everywhere.
1. Harvest, on every machine
git clone <this-repo> && cd agent-memory-kit
./harvest.shEach run writes a new harvest/<hostname>_<YYYY-MM-DD>_<HHMMSS>/, so merge conflicts
are impossible by construction. No branch per machine, no manual merge. The script
creates and never deletes: re-running adds a harvest rather than replacing one, so
earlier dumps and any notes filed alongside them survive. It refuses to run if the
target directory already exists. Consolidation uses the newest harvest per hostname. If
two machines answer the same hostname -s, set HOST_LABEL on one of them.
To move harvests between machines, keep them in a private repo. They quote your work
verbatim. The .gitignore here excludes harvest/, pruned/, audits/ and
PRUNE*.md for that reason, and those rules are in the first commit so a stray
harvest.sh run cannot put your memory into a public one.
2. Consolidate, once, on one machine
Feed prompts/01-consolidate.md to Claude with the repo as cwd. Answer its questions,
then run prompts/02-draft-baseline.md.
3. Roll out, on every machine
Follow prompts/03-rollout.md. Install first:
mkdir -p ~/.dotfiles/claude
cp templates/CLAUDE.md ~/.dotfiles/claude/CLAUDE.md
cp -r templates/rules ~/.dotfiles/claude/rulesthen link it into place on each machine:
ln -sfn ~/.dotfiles/claude/CLAUDE.md ~/.claude/CLAUDE.md
ln -sfn ~/.dotfiles/claude/rules ~/.claude/ruleswhich leaves this layout:
~/.dotfiles/claude/ tracked, same on every machine
├── CLAUDE.md tier 1
└── rules/ tier 2
├── python-uv.md
└── research-outputs.md
~/.claude/
├── CLAUDE.md -> ~/.dotfiles/claude/CLAUDE.md
├── rules -> ~/.dotfiles/claude/rules
├── settings.json a tracked copy, never a symlink
└── projects/*/memory/ tier 4, untracked
CLAUDE.md and rules/ are read-only to Claude Code, so symlinks are safe and a
git pull updates every project on that machine at once.
Then prune:
./prune-move.sh # dry run, shows every move
./prune-move.sh --apply # performs themprune-move.sh reads PRUNE.md, takes only the section matching this machine, and for
each entry copies the file into pruned/, verifies the copy is byte-identical, and only
then removes the original. It refuses to overwrite anything already in pruned/, skips
SPLIT entries entirely, and is a dry run unless you pass --apply.
The kit is re-runnable. Add a machine, or come back in six months after tier 4 has
refilled: ./harvest.sh on each machine, then 01-consolidate.md again. It audits
against the baseline you already have and reports only what is new or diverged.
A redacted example of real output, from running this across four machines, with names removed. It is there so you can see what the method produces before you run it: nine rules, each stating what to do, the evidence it came from, and how to apply it. Running the kit overwrites these with your own.
harvest.shis read-only against~/.claude. The one thing it touches outside it is listing$HOME/Projectsand$HOME/Projects/*-workspaceto map slugs back to project paths. Two listings, no recursion, no searching.- A slug is the absolute path with
/,.and_flattened to-, which cannot be decoded back. It never needs to be: the script lists the project directories, computes each one's slug forwards, and looks the answer up. So dots and underscores need no special cases, and a slug with no match means that directory is genuinely gone rather than unrecognised. - A project checked out at different paths on different machines produces different
slugs and therefore separate, independently drifting memory. The audit catches this;
MANIFEST.mdrecords the real path behind each slug. - Never symlink
settings.json. Claude Code writes it, and write-by-atomic-rename replaces a symlink with a real file, silently detaching it from the repo. Keep it a tracked copy and diff it by hand.
MIT.