A coding agent starts every session knowing nothing. Three hours into a job it knows where the bodies are buried in your repository: which function must never buffer, which default an unattended night job depends on. Then the session ends, and the next run opens with the enthusiasm of a newcomer who has read nothing.

Two habits get most of that across the gap, and neither needs a framework or a vector store. One is about where background information lives; the other about what finished work turns into. Both belong to the family of approaches published as Interpretable Context Methodology (ICM), and this post is about the two parts that decide what an agent must read before it can be useful.

For concreteness I invented rowcleaner, a tool that strips empty rows out of CSV files. It is fictional, boring on purpose, small enough to hold in your head, and every number below was measured on it. The tree is a zip if you want to check my arithmetic.

Every session starts with amnesia

The naive fix is to hand the agent everything: concatenate the repository, prepend it to the prompt, done. It fails on cost, because prefill is not free and you pay it again next session. It fails on window size, because a real repository does not fit. And it fails on usefulness, because Liu et al. show accuracy sags when relevant material sits in the middle of a long context instead of at the ends.

So orientation cost scales with the size of a project while orientation value does not. A 40-line fix in one module needs two facts from a codebase of 200 files, and the naive approach charges you for the other 199.

Most of what an experienced agent knows is not in the code either. Two categories keep disappearing:

  • Constraints with no local evidence. “Never load a whole file into memory” is invisible in a function that runs perfectly on a 40 kB fixture and dies on a 600 MB export.
  • Decisions plus what they replaced. You made CRLF handling strip line endings only for the blank-row test, because one importer is picky about carriage returns inside quoted fields. The diff shows what you did; nothing says what you considered, or why it lost.

A new session redoes that reasoning, and a decision redone without its history lands on the rejected alternative with confidence. Tests pass, the pull request merges, and six weeks later the thing you avoided is back.

How the method works

Context linking puts background information next to the code it explains, in a short note per folder that ships with the source but not with the product. Every folder may hold a context.md, the code names that note in its header, and one file at the root says where background lives, so nothing depends on guessed filenames.

History compaction turns finished work into a dated entry: what was decided, what was rejected and why, the constraint that now binds, and the command that proves the code still holds it. Pending work is a file in one folder, finished work a file in another: the filesystem carries the state, with no status field to fall out of sync.

rowcleaner/
├── AGENTS.md              handbook: purpose, rules, index of the notes
├── README.md              for humans
├── src/
│   ├── context.md         why reading is streaming; what was rejected
│   └── cleaner.py         header names context.md
├── tests/
│   ├── context.md         why the suite never opens a socket
│   └── test_cleaner.py
├── queue/                 pending work, one file per item
│   └── 07_auto-detect-delimiter.md
└── journal/               finished work, dated entries
    ├── 2026-10-19_crlf-blank-rows.md
    └── 2026-10-26_read-input-as-a-stream.md

An agent on a cold evening reads three files and is oriented: the handbook, the queued item it was pointed at, and the note for the folder in front of it. That set is chosen by location, not by search: no embedding index, no repo-wide grep, no “let me explore the codebase first” phase eating a third of the window.

The handbook stays small because it refuses anything specific: purpose, three rules, one line saying finished work ends as a journal entry, an index of the notes. 455 words. Rules live there, reasons live in the folder they apply to, and that is what keeps the boot path flat as a project grows.

A journal entry is where the compression happens, and its shape is load-bearing. Four sections: Decision, Rejected alternative, Invariant, Verify command. The first two are what any engineer writes down; the second two save the next session. The invariant states the constraint in present tense (“no function in src/ returns a list of rows”) and the verify command is a one-liner that fails when the constraint breaks. Reasoning you cannot re-test quietly becomes folklore.

Does it work

Yes, with limits. The boot path on the example tree, from a bootmap target I will come back to:

background notes, by size:
    247 words  src/context.md
    117 words  tests/context.md
cold-boot set: 5122 bytes, about 1280 tokens (budget 1600)

Token figures throughout are bytes / 4 estimates, good to about ±20 % and dependent on your tokenizer; bytes are exact.

Loaded whole, the same tree comes to 17,530 bytes: about 4,360 estimated tokens against the 1,280 needed to start. On a toy this size that is a factor of three, nothing to write home about. The shape of the curve is the point: the whole-tree figure grows with every file anyone adds, while the boot set grows only when you add a folder and work in it, or let the handbook fatten up.

On two projects I maintain this way, handbooks come to 590 and 670 words, folder notes run between 360 and just over 1,000 words each, and 16 compacted entries average 943 words. Ceilings set by convention, not by codebase: one of the two has 82 kB of Rust source and its boot path did not notice. Measure your own repository in two commands, from a clone (untracked files are invisible to git ls-files, which is the one trap):

# what orientation costs when you know where background lives
git ls-files 'AGENTS.md' 'src/context.md' | xargs cat | wc -c
# what it costs when you do not, and read everything instead
git ls-files | xargs cat | wc -c

Swap in your own filenames. If you have neither, the second number is your orientation cost today, and that is the finding.

Compaction is harder to measure honestly, because nobody keeps the raw session, and I have never stored a transcript of my own work: any before-and-after ratio would be invented. What can be said: a session doing one unit of work occupies tens of thousands of tokens while it runs and leaves roughly 940 words behind (about 3,800 bytes, call it 950 estimated tokens) if you compact well. That is an inference from what a session costs while running, not a logged measurement.

The method does not make the working session cheaper: once you are three hours in, you are three hours in. Nor is it free at boot: 1,280 estimated tokens for a one-line change. And compaction costs a full read of the session by whoever writes the entry, at the end of a run when the window is fullest, which is why entries get written badly.

Where it breaks

Eight ways this rots, cheapest to discover first. Six are demonstrated in the example tree; the last two I argue from experience.

Problem What breaks How you would notice Status
A rename orphans a link The handbook points at a note that no longer exists, and the next agent skips the constraint Nothing notices, until someone asks why nothing says why demonstrated
Retrieval depends on filenames A note in a new folder stays invisible unless the index names it “I could not find guidance on X”, about a file that exists demonstrated
One fact gets two homes A rule is copied into a second note and the copies diverge at the next change Two notes disagree; nobody knows which one the code follows demonstrated
Code moves, the note does not The rationale describes behaviour that no longer exists, and reads as an authoritative lie A confident answer built on a note that was true in March demonstrated
The handbook fattens up Every “quick” rule lands in the file everyone reads first Cold starts get slower; the boot set passes its budget demonstrated
Compaction drops a constraint The summary keeps the outcome, loses the reason, and the invariant survives only by luck Your fix gets reverted weeks later, citing an entry that no longer mentions it demonstrated
Wrong reasoning fossilises An entry records a decision made on bad information, and later sessions inherit it as fact A constraint nobody can justify when asked, which turns out false argued
Two writers, one tree Nothing arbitrates queue numbering or item ownership for a second agent Two items numbered 07, or the same work done twice argued

Every failure here is silent. There is no runtime to complain while a note drifts from the code it describes. Writing stays optional, reading stays invisible: an agent working around a stale note produces no error, just a slightly worse change that looks fine in review.

Four fixes that fit in a Makefile

Any fix here has to be CLI-only and independent of your agent software. Four cover most of that table, all of them grep, git and make.

1. Mandatory compaction slots. Against the entry that loses a constraint: make the sections part of the interface, so a gate refuses entries that skip one.

SLOTS := Decision Rejected_alternative Invariant Verify_command
JOURNAL_EXEMPT := journal/2026-10-19_crlf-blank-rows.md
check-journal:
	@fail=0; \
	for f in $(filter-out $(JOURNAL_EXEMPT),$(JOURNAL)); do \
		for s in $(SLOTS); do \
			n=$$(echo "$$s" | tr '_' ' '); \
			grep -qE "^## $$n$$" "$$f" || { echo "$$f: missing slot '$$n'" >&2; fail=1; }; \
		done; \
	done; \
	[ $$fail -eq 0 ] && echo "journal ok"

Write an entry as cheerful prose and this happens:

$ make check-journal
journal/2026-10-29_summary-line-to-stderr.md: missing slot 'Invariant'
journal/2026-10-29_summary-line-to-stderr.md: missing slot 'Verify command'
make: *** [Makefile:42: check-journal] Error 1

A summariser drops the constraint first because it is the least narrative part of the story; a gate gives it a heading with a name. Old entries sit on the exempt list once, and that list only shrinks. What the gate cannot see is content: an Invariant section reading “looks good” passes.

2. Link and structure lint. Against orphaned links and invisible notes: extract every relative markdown link, resolve it against the linking file’s own directory, fail on missing targets, and on any context.md the root index does not name.

$ make check-links
broken link: AGENTS.md -> src/context.md
broken link: README.md -> src/context.md
make: *** [Makefile:23: check-links] Error 1

$ make check-links        # after a docs/ folder grew a note nobody indexed
note missing from handbook index: docs/context.md
make: *** [Makefile:23: check-links] Error 1

If your notes do not ship, add what your packaging needs: no published document may link to an unpublished note. Lint cannot tell you whether a link goes somewhere true, only that it resolves.

3. A staleness stamp from git history. Against notes that describe old code: compare each note’s last commit against the newest commit of any tracked non-markdown file in the same folder; report stale, ok or unknown.

$ make check-stale
src/context.md: STALE - code in src moved 8 days after the note
tests/context.md: ok
make: *** [Makefile:53: check-stale] Error 1

Two details cost me debugging. The comparison must be strictly greater-than: a commit touching code and note together gives them identical timestamps, and >= flags every healthy change as stale. A never-committed note returns an empty date from git log, which you report as unknown rather than as fresh. And it is a proxy. “The code moved after the note was written” correlates with staleness without proving it, and plenty of notes survive their folder changing. Asking is the feature.

4. A generated boot map with a budget. Against handbook creep and filename archaeology: print every note with its size, sorted, then sum the cold-boot files against a token budget.

$ make bootmap
background notes, by size:
    247 words  src/context.md
    117 words  tests/context.md
cold-boot set: 5122 bytes, about 1280 tokens (budget 1600)

Add two sections to the handbook and the same check turns red at 6,431 bytes, about 1,607 estimated tokens against a budget of 1,600. Nobody decides to bloat a handbook; it gains a checklist here and a style note there, and eighteen months later every session pays maybe 4,000 estimated tokens to reach paragraph three. A budget turns “this file feels big” into a number in a build log. What it cannot see is the note you did not open.

All four run as make check in about a second, on any agent.

One thing stays unsolved underneath all of it. Compaction decides what the next session knows, and whoever finished the work writes it at the moment they least wanted to, about reasoning they no longer hold precisely. Every gate above checks whether an entry is complete; none can check whether it is true. A confident wrong entry outlives a correct comment because it arrives with the authority of history. I keep waiting for that to bite me in something bigger than a CSV tool.

(◣_◢)