The common fix for a bloated instruction file is to edit it down: delete the stale lines, tighten the wording, add a few headings. That treats the symptom. The reason the file keeps growing is that two different jobs ended up in one place, telling an assistant how to behave and telling it what has already happened. Sorting out CLAUDE.md project memory is a filing question before it is a writing question.
The moment it happens is small. You spend an hour finding out that the migration script has to run before the seed script or the foreign keys break, you want that written down before you forget, and CLAUDE.md is the file already open. In it goes. Repeat that for a few months and you have 400 lines where roughly half are rules and the rest are facts about a Tuesday in March.
The fact deserved to be written down. It just was not an instruction.
Why everything lands in the instruction file
There is a practical reason, not a lazy one. CLAUDE.md is loaded by Claude Code without you asking, and AGENTS.md does the same for Codex and several other agents. Anything else you write is a file the assistant will read only if it happens to open it or you tell it to. So when you have a fact you really need the next session to know, the instruction file looks like the only reliable delivery mechanism.
The cost shows up later, in two ways. The first is dilution: a file full of one-off notes makes it harder for the model to pick out the lines that are actually binding, and you notice it ignoring rules it used to follow. The second is that undated facts quietly turn into permanent rules. "Use the local Redis container for the queue" was true while the staging environment was down. Nobody marked it as temporary, so it now reads like policy, and the assistant keeps steering back to it six months after staging came back.
What instruction files are actually for
Instructions describe how work is done in this repository, at any moment, regardless of what you happen to be building this week. Commands, conventions, boundaries, the things you want done without being asked.
## Commands
- Install: `pnpm install`
- Test: `pnpm test` (run before proposing a commit)
- Typecheck: `pnpm typecheck`
## Conventions
- Keep SQL portable; no database-specific column types.
- New API routes get an integration test in the same PR.
## Boundaries
- Never edit files under `generated/`.
- Ask before adding a dependency.
Every line there is written in the imperative and would still be true if you deleted the entire feature branch. That is the shape to aim for. AGENTS.md vs CLAUDE.md is a naming question, not a content question: same kind of content, different filename depending on which assistant reads it, and writing project instructions your assistant follows covers how to keep both in sync without maintaining two copies.
A quick signal when deciding what to put in CLAUDE.md: if the line needs a date to make sense, it is probably not an instruction.
Project memory answers a different question
Memory is the record of what happened and why. It is what a colleague would tell you over coffee in week one: the choice between two libraries and the reason one lost, the approach you tried for a day and abandoned, the part of the codebase that looks wrong and is not, where the current refactor stopped.
### 2026-09-14 — Migration order
The seed script assumes tables exist with constraints applied.
Running seeds first fails on foreign keys with a misleading error.
Order: `migrate` then `seed`. Documented in the Makefile target.
That entry has a date, a cause and a consequence. It accumulates rather than being maintained, and old entries stay useful as history even when they stop being current. Project memory for AI coding goes into the entry types in depth, and lightweight decision records covers the format when a decision is big enough to deserve its own file.
The rule of thumb: standing instructions vs. situational facts
Two questions settle most cases.
Does the line tell the assistant what to do, or what is true? "Run the typecheck before proposing a commit" is a thing to do. "We moved off the ORM in August because the query builder could not express the reporting joins" is a thing that is true. The first is an instruction, the second is memory.
Would the line survive if you rewrote this feature from scratch tomorrow? Standing instructions survive rewrites. Situational facts belong to a specific moment and die with it.
| The fact you are about to write | Goes in | Why |
|---|---|---|
"Run pnpm test before committing" | Instructions | Always true, always actionable |
| "We picked Vitest over Jest for ESM support, 2026-07-02" | Memory | A decision with a reason and a date |
| "Don't suggest server-sent events, we tried it and dropped it" | Memory | History that prevents a repeat suggestion |
"Never edit generated/" | Instructions | A permanent boundary |
| "Auth refactor is half done; login works, password reset does not" | Memory | Current state, changes weekly |
| "Ask before adding a dependency" | Instructions | Behaviour you want every session |
Some facts produce both, and that is where people get stuck. A decision often creates a rule. When you choose SQLite for the desktop build, the reasoning is memory and the resulting constraint ("keep queries portable, no JSON columns") is an instruction. Write both, and do not copy the reasoning into the instruction file. One line of rule, one entry of why, linked by nothing more than being in the same repository.
The CLAUDE.md project memory split in practice
Plain files are enough. A layout that holds up:
CLAUDE.md
AGENTS.md
.prjcontext/
memory/
decisions.md
rejected.md
state.md
sessions/
The piece that makes it work is a short pointer in the instruction file, because memory only helps if the assistant knows to open it:
## Project memory
Before proposing an architectural change, read `.prjcontext/memory/decisions.md`
and `.prjcontext/memory/rejected.md`. Do not re-propose anything listed there.
That is an instruction about memory, which is exactly right: it tells the assistant how to behave. After that, your instruction file stops growing. Memory grows constantly, and that is fine, because nobody expects a log to stay short. What belongs beside your code covers the wider question of which of these files are worth carrying between machines at all.
Untangling a file where both jobs are merged
Set aside half an hour and read your instruction file top to bottom with one mark per line: R for rule, F for fact. Most people find the split is somewhere around half and half, and that a surprising number of lines are neither, just notes to self that were never true instructions in the first place.
Move the F lines into memory, grouped by decisions, rejected approaches and current state. Do not preserve the original wording. A line that read "we're on Node 22 now (upgraded in July, node-gyp broke on 20)" becomes a proper entry with the reason intact. Delete the dead ones outright; anything referring to a service you no longer run is not history worth keeping.
Then reread what is left. A file of forty tight rules gets followed in a way a file of four hundred mixed lines does not, and you will notice the difference in the first session after the cleanup.
Keeping the split is mostly a habit at write time. Before adding a line, ask whether you are describing behaviour you want repeated or an event you want remembered. We built PrjLab so that distinction lives in the repository rather than in someone's head: instructions, memory and the sessions you keep are separate versioned pieces of the same project, pushed with prj push and pulled onto another machine with everything still in its own place. The getting-started guide walks through the first push if you want to see how the layout travels.