previous
Chat sessions are amnesiac. Every new one starts from nothing, so the user re-explains the same project, the same constraints, the same "no, we don't do it that way" corrections they gave last week. That tax is what this removes.
The shape of it
Three tiers, cheapest first. This split exists because memory is a cost that recurs every session forever, so the expensive step has to be rare:
| Tier | When | Model cost |
|---|---|---|
| Capture | Automatically, at session end (SessionEnd hook) |
Zero — pure Python |
| Hint | Automatically, at session start (SessionStart hook) |
~25 tokens, one line |
| Distil | Next /previous, if captures are waiting |
~300 in / ~100 out per session |
| Consolidate | Once ~5 log entries have built up | One rewrite, amortised |
The hint deliberately stops at a pointer. SessionStart stdout does reach the
model, so it could inject the whole digest — but that would charge every
session for memory, including ones unrelated to the remembered work. Loading
stays an explicit /previous. If a hint appears and the user's request clearly
depends on that history, offer to load rather than loading unasked.
Capture is free because it never involves you: a script reads the raw transcript and keeps only what a human actually typed. In a real session that turned 147k tokens of transcript into 280 tokens of capture. Everything else was tool output — bulk without durable signal.
~/.claude/previous/
├── global/MEMORY.md things true everywhere (preferences, working style)
└── projects/<slug>/ keyed by git remote, so laptop and container share
├── MEMORY.md the digest — rewritten whole when consolidating
├── log.md distilled session entries
├── pending/ raw auto-captures awaiting distillation
├── backups/ archive/
Commands
Run scripts/pmem.py from the project directory — it handles scope resolution,
reading, backups and rotation so you never guess at paths:
python3 <skill>/scripts/pmem.py show # global + project + pending (the restore payload)
python3 <skill>/scripts/pmem.py show --brief # digest only, skips log and captures
python3 <skill>/scripts/pmem.py stats # sizes, restore cost in tokens, what needs doing
python3 <skill>/scripts/pmem.py log --title T # append a distilled entry (body on stdin)
python3 <skill>/scripts/pmem.py clear-pending # drop captures once distilled
python3 <skill>/scripts/pmem.py backup # snapshot MEMORY.md before rewriting
python3 <skill>/scripts/pmem.py archive # rotate log.md
python3 <skill>/scripts/pmem.py list # every project with memory
python3 <skill>/scripts/pmem.py install-hook # turn on both hooks
--global targets the cross-project layer. PREVIOUS_AUTO=0 pauses capture.
Ephemeral hosts
Claude Code on the web clones the repo fresh and reclaims the container
afterwards, so ~/.claude/previous never outlives a session there. If a
project has a .claude/previous/ directory, memory is stored there instead —
in the repo, which is the only thing that survives. pmem.py use-repo switches
a project over and migrates any existing memory.
Repo mode changes one thing about your job: memory only persists if it gets
committed. On an ephemeral host, distil and consolidate during the session
rather than counting on the next one, and include .claude/previous in the
commit you were making anyway. Say so plainly when you do it — the user is
putting memory under version control, and should know it's there.
| What the user says | What to do |
|---|---|
/previous, "catch me up" |
Restore |
/previous save, "checkpoint" |
Save |
| "turn on auto-save" | install-hook, then confirm in one line |
/previous list |
pmem.py list, summarise briefly |
/previous forget <thing> |
Back up, remove it from MEMORY.md, confirm what went |
Restore
Run
pmem.py show. If it reports no memory, say so in one line and get on with the actual request — a fresh start is not a problem to be solved.If it lists pending captures, distil them now (below). This is the only moment that reliably happens, so it's where automatic memory gets folded in.
Give a short orientation — three to six lines, not a recital:
Picked up on auth-service. Last session you moved token refresh onto the worker queue and hit a race in
RefreshLock. Open: the staging cert. You'd already ruled out the middleware approach.Lead with current state, then open threads, then anything they'd want to be reminded they'd ruled out. They wrote this memory to skip the recap; a wall of text is the recap.
If they asked a question alongside
/previous, answer it. The restore is setup, not the deliverable.
Distilling pending captures
A capture is raw: the user's turns verbatim, files touched, notable commands.
Your job is to compress each into 3-6 bullets of what's durable, append it
with pmem.py log --title "<short title>", then run pmem.py clear-pending.
Compress hard. A capture holds everything a session mentioned; a log entry holds only what a future session would need. Most sessions yield two or three bullets, and some yield none — if a captured session produced nothing durable, say so and clear it rather than manufacturing filler.
Then check pmem.py stats: once there are ~5 log entries, consolidate.
Save
Two different jobs share this name; pick by what's actually accumulated.
Light save — append a distilled entry to log.md and stop. This is right
when the session produced a few facts but the digest is still accurate. Cheap,
and most saves should be this.
Consolidation — rewrite MEMORY.md whole. Do this when ~5 entries have
built up, when stats says you're over budget, or when the digest has gone
stale enough to mislead.
To consolidate:
pmem.py backupfirst, always. You're about to replace a file holding weeks of context, and a bad rewrite that quietly drops half of it stays invisible until the moment it matters.- Read the current
MEMORY.mdin full — you can't merge into something you haven't read — plus the log entries since the last consolidation. - Rewrite it as one document: old and new reconciled. Not the old file with a new section stapled on.
pmem.py archiveif the log has grown past budget.- Anything about the user rather than the project — a preference, a standing
instruction — goes to the global layer with
--global. - Report in one line what you saved and the new restore cost from
stats.
What's worth keeping
The test: would a competent stranger picking this up next week need it, and could they not get it from reading the code?
Keep decisions and their reasoning — without the why, the next session re-opens a settled question. Keep corrections the user made ("no, we use X here"); those are the highest-value lines in the file because they cost real effort to transmit. Keep preferences, environment gotchas, what's half-done, and dead ends — what was tried, what broke, why — which are cheap to record and expensive to rediscover.
Drop anything readable from the code in ten seconds, blow-by-blow narration, transient debugging that taught nothing, and your own commentary on how the session went. The memory should point at the codebase, not duplicate it.
How to merge
This is what makes the file compound instead of accrete:
- Supersede, don't accumulate.
Moved to Postgres (was SQLite; hit concurrent-write limits, 2026-03)— one line carrying current state and history beats two lines contradicting each other. - Resolve open threads. Anything settled leaves the open list. A stale open thread is worse than no note; it sends the next session chasing finished work.
- Merge duplicates. Three sessions noting the same quirk collapse to one.
- Promote repeats. Something recurring across sessions has proven it's structural — move it into Stable facts, which gets read first.
- Let things die. A decision about code that no longer exists is noise. Dropping it is part of the job; the backups are right there if you're wrong.
Budgets from stats: MEMORY.md past ~400 lines means merge harder; log.md
past ~40 entries means fold and archive. If a save would blow the budget and
there's genuinely nothing to cut, say so — that usually means the project has
outgrown one file, and the user should get to decide.
references/memory-format.md has the section layout and a worked before/after
merge where the file covers an extra session and comes out shorter. Read it
the first few times you consolidate, or when the existing file is messy.
Cost discipline
The user is paying for this on every session, so treat restore size as a number
you're responsible for. stats reports restore_cost_tokens; a healthy
project sits in the hundreds. If it drifts past ~2k, the digest has stopped
being a digest — consolidate rather than letting it slide.
Two habits that matter: never print restored memory back to the user (it's
already in your context — repeating it doubles the cost for zero gain), and
prefer show --brief when you only need orientation and not the full history.
Offering to save
Capture is automatic once the hook is installed, so the user doesn't need
prompting to preserve a session — it's already preserved. Mention saving only
when consolidation is genuinely due (stats says so), or when they've said
something durable that they'd want in the global layer, which capture won't
classify on its own.
If the hook isn't installed, offer install-hook once — after that, drop it.
Nagging about memory hygiene is its own kind of tax.