A project memory file is good at recording what a team decided. It is much worse at recording what a team stopped believing.

The entry still says "all outbound email goes through the queue", dated, reasoned, confident. Three weeks ago everyone agreed on a call that transactional mail sends directly and the queue only matters for campaigns, and nobody wrote a decision reversal record, because reversals do not feel like decisions. They feel like corrections. Corrections feel like something you do to a file, not something you add to it.

Your assistant cannot tell the difference. It reads the decisions section, finds one clear statement about outbound email, and suggests the queue. You correct it in the chat, it agrees, and the next session suggests the queue again. The notes below come from cleaning up memory files where that had happened more than once.

Decisions age differently than facts

Most stale project memory is factual: a staging URL that moved, a Node version you bumped, a script that got renamed. Those entries are safe to overwrite. Nothing depends on why the old value was the old value, so editing in place loses nothing.

A decision entry is a different shape. It holds a claim ("use the queue"), a justification ("retries and rate limits in one place"), and usually a consequence that shows up in the code ("no direct SMTP calls outside mailer/"). When the claim flips, the justification does not simply become false. It stays a real argument that lost to a newer one, and the newer argument is the part nobody wrote down.

The practical lesson is that a reversed decision left unmarked does more damage than a missing one. A gap makes the assistant ask or guess visibly, and you catch it. A confident, obsolete entry makes it act, and the reasoning it quotes back at you sounds like your own, because it was.

The rewrite that erases the reason you need most

The reflex is to open the file and fix the line.

### 2026-08-14 — Outbound email
Decision: Send transactional mail directly; use the queue for campaigns only.
Why: Retries and rate limits belong in one place.

That now reads as if direct sends were always the plan, with a justification that belongs to the approach you abandoned. The date is wrong too: the entry claims August for a call that happened in October. Two months of code written against the old rule look like deviations from a decision that did not exist yet.

The bigger loss is the rejected approach. Six weeks later someone proposes routing transactional mail through the queue, and there is nothing in memory saying it was chosen once, built, and walked back for a reason. That is the entry that would have saved the conversation. Lightweight decision records earn their keep mostly through this: they record what was ruled out, not just what was picked.

This is also not a pruning problem. Pruning a bloated memory file means deleting entries that no longer change what anyone does. A reversal is the opposite situation. The pair of entries is what carries the information; neither one alone is useful.

Writing the decision reversal record instead of editing the old one

Leave the original body untouched and add one line to it. Then append the new entry.

### 2026-08-14 — All outbound email through the queue
Decision: Every outbound email goes through the job queue.
Why: Retries and rate limits live in one place.
Consequence: No direct SMTP calls outside `mailer/`.
Status: Superseded by 2026-10-02

### 2026-10-02 — Transactional mail sends directly
Decision: Password resets, receipts and verification emails send inline.
Campaigns stay on the queue.
Reverses: 2026-08-14.
What changed: Queue lag of a few minutes is fine for a campaign and
unacceptable for a password reset. We had not separated the two cases.
Still true from the original: rate limiting belongs in one place, so
campaign sends keep going through the queue.
Consequence: `mailer/direct.ts` for inline sends. Keep retry logic in both paths.

Two fields do most of the work. What changed tells the assistant, and the next person, whether the original reasoning was wrong or whether circumstances moved. Those lead to different behaviour: if the reasoning was wrong, be suspicious of anything else built on it; if circumstances moved, the old argument may come back when they move again. Still true from the original stops an over-correction, which is the common failure after a reversal. Without that line, a reasonable assistant concludes the queue was a mistake and starts suggesting direct sends for campaigns too.

Pointing the assistant at the current call, not the history

Appending only works if the reader knows which entry wins. Two habits are enough.

Keep decisions newest first, so the current call is the first thing in the section rather than the twelfth. And pick one word for superseded entries and use it everywhere. Three variants ("old", "no longer applies", "deprecated") read as three different states to anything matching on patterns.

Then say so once in the instruction file. Updating a CLAUDE.md decision pointer is a one-time edit:

## Project memory
Decisions live in `.prjcontext/memory/decisions.md`, newest first.
An entry marked `Status: Superseded by <date>` is history. Follow the
entry that supersedes it, and do not re-propose a superseded approach
without saying why it should come back.

That last clause matters more than it looks. You are not forbidding the old approach, you are asking for an argument. Assistants follow instructions like this unevenly, and when one is ignored the cause is usually the file rather than the loading, which this piece on ignored instructions gets into. Ordering helps where a rule does not: the entry it reads first is the entry it uses.

Once the superseded entries outnumber the live ones, move them to .prjcontext/memory/decisions-archive.md and keep the pointer in the live entry. Reversals stay findable, and the file an assistant reads every session stays short.

When a reversal deserves its own entry, and when a note is enough

Not every change of mind is a reversal. Three questions sort it quickly.

Did anyone build against the original? If code, config or another decision depends on it, write the full record. If the decision existed only in the file and nothing was ever built on it, delete the entry and write the new one.

Did the claim flip, or just narrow? Narrowing is an amendment. Add a dated line under the original and leave the status active:

2026-09-11 — Scope clarified: applies to the API service only,
not the worker.

Was it a decision at all? "Chose SQLite over Postgres, because the app runs without a server" is a decision. "Staging runs on port 4000" is a fact, and facts get overwritten. Mixing the two is what makes files like this hard to maintain in the first place, and splitting rules from facts from decisions is worth doing before you worry about reversals.

One more thing that changes how this feels in practice: when memory is versioned alongside the code, the shape of a reversal is already recorded. We built PrjLab so .prjcontext/memory/ travels with the files, and every push creates an immutable version with a line diff, so you can see when the queue entry was written and when it was marked superseded without keeping a separate change log. That history answers questions for humans. It does not help the assistant, which reads the file as it stands today, so the reversal still has to be written into the entry itself. The getting-started guide covers how memory gets captured and pushed if you want to try it on an existing project.

The habit is small. When a decision gets overturned, the reversal goes in the same place the decision did, in the same format, with a pointer in both directions. It takes a minute longer than editing the line, and it is the difference between a memory file that tells the story and one that quietly argues for something you dropped.