Two plain markdown files. No API keys, no external services.
Claude forgets everything when a session ends. Open a new one and you are back to explaining your project from scratch โ what it does, how you work, what you decided last week. I got tired of that and fixed it with two plain markdown files.
No API keys. No external services. No login. Claude Code already reads a file called CLAUDE.md from your project root at the start of every session โ that hook is all you need.
How it works
The system splits one job into two files, and the split is the whole idea:
| File | What it holds |
|---|---|
CLAUDE.md | The rules โ how Claude should behave in this project, and when to write to memory |
memory-log.md | The record โ what actually happened, newest entry on top |
Claude reads CLAUDE.md automatically at session start. That file tells it to append a summary to memory-log.md when the session wraps up, and to look things up there when it needs past context. That is the entire loop.
Why not just a longer prompt? A prompt dies with the session. A file does not. And because it is a plain file, you can read it, edit it, diff it, and commit it โ you are never guessing what the model "remembers".
Step 1 โ Drop this into CLAUDE.md
Put CLAUDE.md in your project root and paste this in. Adjust the wording freely; the structure is what matters.
## Memory routing (local files)
- **Auto-summarize**: When a session involved real work (files created or
changed, decisions made, something deployed) and is wrapping up, add a new
entry to the TOP of memory-log.md. Cover what was done, what was decided,
and what is left. Entry heading format: "## YYYY-MM-DD - Title".
If I say "summarize", do it immediately.
- **Starting a new session**: If you need past context for the task at hand,
find and read only the relevant entries in memory-log.md.
Do not read the whole file.
- **Compaction**: When memory-log.md has more than 10 "## " entries, move the
oldest ones to memory-log-archive.md with compress-memory-log.mjs, leaving
the latest 10 plus a one-line index. Nothing is deleted, only moved.
Three rules: when to write, how to read, and what to do when it gets long. Skip the third one and the file grows until it eats your context window.
Step 2 โ Create memory-log.md
Same folder. Start it with a header so Claude knows what the file is for, then let it fill in.
# Memory log
Session summaries, written by the rule in CLAUDE.md. Newest entry on top.
When a new session needs past context, read only the relevant entries here.
---
Entries end up looking like this. Claude writes them; you mostly just read them.
## 2026-08-25 - Fixed the reel that shipped in the wrong language
**What happened.** The publisher sent a Korean video to the English account.
ensureVideo() already took an `account` argument - publishReel just never
passed it, so it fell back to the default account's folder.
**Decision.** Split the temp-file path per account too. The old code relied
on the two files happening to differ in size, which is not a guarantee.
**Left to do.** Check the other publish paths for the same missing argument.
Notice what makes this useful: it records why, not just what. Six weeks later "we split the temp path per account" means nothing on its own. "Because size comparison is not a guarantee" is the part that stops you from undoing it.
Step 3 โ Keep it from growing forever
This is the step people skip, and it is the one that decides whether the system survives past a month. Every entry you keep is context Claude re-reads. Past a certain size the log stops helping and starts crowding out the actual work.
So the log gets compacted: entries past the newest 10 move to an archive file. Nothing is deleted โ only moved. The archive stays in the repo and stays searchable.
node compress-memory-log.mjs memory-log.md 10
The script reads the log, keeps the newest N entries, appends the rest to memory-log-archive.md, and leaves a one-line index behind so you can still see what moved. Roughly 40 lines of Node with no dependencies.
One thing worth getting right: compaction must move entries, never summarize them away. The moment it starts rewriting old entries, you lose the exact detail that made the log worth keeping โ and you will not notice until you go looking for something that is gone.
Where to go from here
Two files will carry you a long way. When you outgrow them, the next steps are additive โ you never throw the first one out:
| Step | What you add | What it buys you |
|---|---|---|
| 1 | memory-log.md | Context survives the session. Zero setup. |
| 2 | memory/*.md by topic | Permanent knowledge stops scrolling away with time. |
| 3 | NotebookLM MCP | Ask past sessions questions in plain language. |
| 4 | Local vector store | Context gets injected automatically, not looked up. |
Step 2 is the one worth doing early. A time-ordered log is the wrong shape for facts that do not change โ "the publish server runs on port 3791", "this API rate-limits per minute, not per day". Those belong in a topic file that stays current, not in an entry that scrolls into the archive. Keep memory-log.md for what happened, and memory/<topic>.md for what is true.
Steps 3 and 4 are worth it once you have enough history that reading files by hand is the slow part. Below that, they are setup cost with nothing to search.
Start today
You do not need any of the later steps to get the benefit. Two files, one rule block, ten minutes.
- Put
CLAUDE.mdin your project root with the rules above. - Create
memory-log.mdnext to it with the header template. - At the end of your next session, say "summarize" once. After that Claude does it on its own.
The short version
AI coding workflows, tested on real work โ not demos. New posts weekly.