Skip to content
SJ
All writing
10 min read

Two Files That Give an AI Agent a Memory

Coding agents start every session cold, and code records what it does but never why. Two files in the repo — one append-only, one living — fix more than a better prompt ever will.

AI AgentsClaude CodeCursorDocumentationWorkflow

Working with a coding agent for more than a week surfaces a specific, repeating annoyance. You spend an hour with Claude or Cursor reasoning through a design — you consider three approaches, reject two for concrete reasons, and ship the third. A week later, a fresh session opens the same file, sees the third approach, and cheerfully offers to refactor it into the first one you rejected.

It is not being stupid. It is being new. The reasoning existed in a chat window that no longer exists, and the repository it can actually read never recorded any of it.

The cold start

Two facts about agentic tools, and everything else follows from them.

Every session starts from zero. Chat history is not project state. Whatever context you built up yesterday — the constraint you explained, the approach you vetoed, the edge case that made you pick the uglier implementation — is gone. The agent re-derives what it can from the code, and re-derivation is inference, which means it is sometimes wrong and always confident.

Code records what, never why. This is not new and not specific to AI. A function that stores money as an integer count of minor units tells you it stores integers. It cannot tell you that floats were tried, that the parts stopped summing to the whole, and that the rounding drift is exactly why the column is a bigint. The reasoning was never in the file, so nothing can read it back.

Humans work around this with tribal memory, and that has always been a weak fix — it fails the moment someone leaves, and it fails for you-in-six-months. Agents have no tribe at all. They have the repository, and only the repository.

So the fix is not a longer prompt or a bigger context window. It is making the reasoning an artifact of the work: a file, in the repo, written as the work happens and read on the way in.

Why exactly two files

The obvious move is one NOTES.md. It does not survive contact with a real project, because two different questions get asked and they have opposite requirements.

  • Why is it like this? — answered by a log. Entries are statements about a moment in time. They are true when written and stay true; a decision you later reverse does not become false, it becomes superseded. This file is append-only.
  • What happens when this runs? — answered by a map. It describes the code as it is right now. When the code changes, the old text is not history, it is a lie. This file is overwritten in place.

That difference in mutability is the entire reason for two files. Put both jobs in one document and you get something that is simultaneously enormous and stale, where nobody can tell which paragraphs are historical record and which are supposed to describe today. Split them and each file has one rule an agent can follow without judgement: never rewrite the log, always rewrite the map.

DECISIONS.md

Every meaningful decision, newest first, logged as it is taken rather than reconstructed at the end of the session. The entry shape matters more than it looks:

### 2026-08-11 — Money is an integer count of minor units

**Why:** Splitting ₹100 three ways has to round somewhere. With floats the
parts stop summing to the whole and balances drift by fractions of a paisa
across a trip. With integers the rounding is explicit and testable.

**Alternative rejected:** `numeric` — correct, but every read becomes a
string parse, and it does not stop the parts-sum-to-whole bug on its own.

**Affects:** schema, split calculation, every amount formatter.

The line that does the work is “alternative rejected.” Without it, an entry is a description of the code, and the code already described itself. With it, the entry answers the question a future session is actually about to ask — should this be numeric? — before it wastes an hour arriving there independently. It is the difference between documentation and a guardrail.

The other half is knowing what does not go in. A decision is meaningful when a competent person could reasonably have chosen otherwise. Renaming a variable is not a decision. Picking a library, changing a data shape, deviating from the design system, deliberately deferring work, reversing an earlier entry — those are. Without that filter the log fills with mechanics, and a log nobody reads is worse than no log, because it still costs review time.

One rule to state explicitly, because it runs against an agent's instincts: never rewrite past entries — supersede them. Models are relentlessly tidy. Left alone they will “update” a stale entry, which quietly destroys the only record that the decision ever changed. A reversal is a new entry at the top that says what it replaces.

FLOW.md

One section per execution path, covering four things: the entry point, the execution order, the call graph with real file.ts:function references, and what changed in this pass. Revisiting a flow updates its section — it never appends a second one.

## 7. Add an expense

/trips/:id/expenses/new ──▶ createExpense ──▶ INSERT expenses
                                   │
                                   ▼ same transaction
                              expense_splits (parts sum to total)
                                   │
                                   ▼
                              revalidatePath(/trips/:id)

expense-form.tsx → actions/expenses.ts:createExpense → lib/split.ts:splitEvenly

**Changed this pass:** the remainder used to land on the payer; it now
distributes one minor unit at a time down a stable member ordering.

Two details earn their keep. The file references make drift visible — a path that no longer exists is a broken link a human or an agent can notice, where prose like “the expense service handles this” rots silently. And “changed this pass” gives the next session a diff of intent rather than a diff of lines, which is the difference between reading a summary and reading a patch.

A useful test for whether a FLOW section is pulling its weight: it should answer “where do I start reading?” for someone who has never opened the repo. If it only makes sense to someone who already knows the codebase, it is decoration.

Making the agent actually do it

Neither file appears by asking politely once. The instruction has to live where the tool reads it at the start of every session — that is CLAUDE.md for Claude Code, .cursorrules or a project rule for Cursor, AGENTS.md for the tools that adopted that convention. Same content, different filename, and several tools now read more than one of them.

## Working agreement (MANDATORY)

### 1. Log decisions — DECISIONS.md
Append an entry for every meaningful decision, with the reasoning, **as you
take it** — not at the end of the session. Meaningful = anything a future
reader would otherwise have to reverse-engineer from the diff. Never rewrite
past entries; supersede them.

### 2. Log execution flows — FLOW.md
For every flow you touch: entry point, execution order, what calls what, and
what changed in this pass. Update the existing section; do not duplicate it.

### 3. Quiz before big changes
Before a large or structural change, ask me questions about the codebase and
the intent until you can restate the change back to me. Then propose it.

Three properties of that text are deliberate. It says as you take it, because a log written at the end of a session is a reconstruction, and reconstructions launder the messy real reason into a tidy fake one. It defines meaningful instead of leaving it to taste. And it states the anti-instinct rules — supersede, do not duplicate — as rules, because that is the only form a model reliably respects.

The third item is the one people skip and then miss. Making the agent quiz you before a structural change surfaces the mismatch between your mental model and its plan while it is still a conversation, rather than after it has confidently rewritten nine files against the wrong assumption.

What actually changes

Having run this across several projects, the effects are more specific than “better documentation.”

  • Cold start gets cheap. A new session reads two files instead of inferring architecture from forty. Fewer tokens, less wandering, and — more importantly — it stops guessing at intent, which is the thing it is worst at.
  • Settled arguments stay settled. The rejected alternative is written down, so the agent stops re-proposing it and you stop re-explaining it. This alone repays the convention.
  • Review gets an order of magnitude cheaper. An agent can produce six hundred lines in a minute. Reading the four-line decision entry first tells you whether it solved the right problem; the code review only has to check whether it solved it correctly. Wrong-problem is the failure mode that actually costs you days.
  • Writing it improves the work before any code exists. Requiring a rejected alternative forces the choice to be made explicitly. Half the value lands at that moment, and it is the same reason design docs work on humans.
  • Handover stops being a meeting. The files are as useful to a person joining the project as to a model. That is a strong signal the convention is sound rather than AI-specific ceremony.

Where it breaks

Three failure modes, all of which I have walked into.

  • The log degrades into a changelog. The moment typo fixes get entries, the signal-to-noise collapses and people stop reading — which is fatal, because the whole mechanism depends on being read. The “could reasonably have chosen otherwise” test is not decoration; it is the thing keeping the file alive.
  • The map drifts, and a stale map is worse than none. A FLOW section describing last month's code is actively dangerous, because the next agent will trust it over the source and produce something confidently wrong. Update it in the same pass as the code, never “later,” and lean on file references so drift shows up as a broken path.
  • Agents write plausible fiction. A model will happily generate an entry that reads beautifully and describes reasoning it did not have. These files are worth exactly what your review of them is worth — read the entry, and if it does not match why you actually made the call, fix it then.

There is also an honest ongoing cost: two more files in every pull request, and a log that grows. Section the decisions file by area once it is long, and supersede rather than accumulate near-duplicates.

Why it outlives the tool

The part worth internalising is that none of this is Claude-specific or Cursor-specific. The rules file is tool-specific and disposable — rename it, rewrite it, keep three of them. What accumulates is two plain Markdown files in your repository, readable by the next model, the next editor, and the next person.

Switching agents costs you a config file, not your project's memory. That asymmetry is the actual argument: the leverage is in the artifacts the tool leaves behind, not the tool.

And none of it is new. A decision log is an architecture decision record with a lighter format; a flow map is a runbook. Both have been good practice for decades and both were skipped constantly, because writing them competed with shipping. What changed is that the agent already holds the full reasoning at the moment the work happens, and writing it down costs it nothing. The discipline finally became cheaper than skipping it.

The short version

Agents forget everything between sessions and code has never recorded intent. Keep an append-only DECISIONS.md — with the rejected alternative, which is the load-bearing part — and a rewritten-in-place FLOW.md with real file references. Mandate both in whichever rules file your tool reads, require them to be written as the work happens, and read what gets written. The tool will change within the year. The two files will still be there.

Written by Saumya Jain

Full Stack Engineer working on headless commerce, NestJS microservices, and real-time systems. Currently open to remote work.