Context anchoring, and what happens after

Sep 03, 2026 · 10 min read

If you work with a coding agent for more than a week, you end up keeping notes for it somewhere. A CLAUDE.md, a decisions folder, a handful of markdown files that say what this project is and why it is built the way it is.

Rahul Garg calls this context anchoring, and his test for whether you have got it right is the one worth stealing: if you can close your chat session and start a new one without anxiety, your context is anchored properly.

I have been doing this for months. It works. The part nobody covers is what happens after.

The failure mode is not what you expect

Here is a note I wrote about my own project:

An automated check for this does not exist yet and is worth building.

The next day I built the check. The note stayed exactly as it was, now confidently instructing every future session to build something that already existed. Nothing flagged it. No test failed, because nothing tests a markdown file.

That is ordinary documentation rot, and you already know documentation rots. The reason it matters more here is the reader. A human skims a stale doc and thinks "this looks old". An agent reads it and acts on it. A decision record naming a function you deleted last month gets asserted, not questioned. You have moved your context out of the chat and into a file, which was the right move, and in doing so you created something that can be wrong at you in a new way.

So the interesting question is not how to write these files. It is how to keep them true, and how to keep them true when more than one person is writing them.

Split what decays from what does not

The most useful thing I changed was sorting every note into one of two kinds.

An invariant says why a constraint exists.

The crawler keys on a URL, so a domain that changed hands returns 200 with a clean, parseable page belonging to a different company. Parse success is not identity success.

That is still true months later, because it describes a mechanism rather than a state. Nothing anyone commits can falsify it.

A status says what is true today.

The automated check does not exist yet. The API key has no credit. That scraper gets a 403.

Every one of those has an expiry date nobody wrote down.

The practical test I ended up with: if you cannot state the mechanism, you have written a status. "Product Hunt returns 403" is really "this scraper is blocked, so that field enriches nothing", which names one. "The API key has no credit" names nothing at all. It is a fact about Tuesday.

Most status has a better home already. Whether a feature exists is answered by your tests and your git log, and those update themselves. If a note is only telling you what the code currently looks like, delete it and let the code say so.

But some status you genuinely need. "This API key has no credit" is not a design constraint and it has no invariant form, and the agent still has to know it or it will burn twenty minutes writing a script that cannot run.

Keep the command that produced the claim

For the status you keep, write down how to check it.

Not "verified on 21 August". The actual command, and what its output means:

curl -sL -o /dev/null -w '%{http_code} %{url_effective}\n' \
  -A 'Mozilla/5.0' https://www.producthunt.com/posts/notion
# 403 means still blocked, this note holds.
# 200 means they stopped blocking and this note is dead.

Re-running beats re-reading. A note you can verify in four seconds is a different object from a note you have to trust.

I did this to my own files last week, then ran two of the probes. Both claims were already wrong, within two weeks of being written, and both were about systems I do not own, which is the category nothing else catches. No commit of mine could contradict them, so git held no evidence either.

The first is the one worth dwelling on. The note said a third-party site returns 403 to server requests. It now returns 308, redirecting to a renamed path that serves 200 perfectly well. They stopped blocking. The URL moved.

And the conclusion in my note was still correct, because my scraper does not follow redirects. But it was correct for a completely different reason than the one written down. The fix had quietly changed from "this is impossible" to "add one flag", and the note still read as true.

That is worse than being out of date. A stale note looks stale. An inverted one passes review.

Date it from the last check, not the writing

An anchor has two dates and most formats only record one.

There is when you wrote it, and there is when its claim was last verified. The staleness discount belongs on the second. A year-old note re-checked this morning is fresher than yesterday's note that nothing has looked at since, and a file carrying only its write date cannot tell you which one you are holding.

This matters specifically because of who is reading. An agent discounts by whatever date you show it. Show it only the write date and you teach it the wrong confidence in both directions at once: too much in the old note nobody checked, too little in the checked one that happens to be old.

One more thing I got wrong here. I had a file carrying a single date over two claims: a convention that is still true months on, and a coverage count that was false within four days. Same file, same date, completely different decay. The date belongs next to the claim, not on the file.

Correct by superseding, never by editing

When a note dies, the instinct is to open it and fix the line. Do not.

Write a new record that names the old one and says why it died, and give the old one an end date. The relationship between them is then data, not something the next reader has to reconstruct from two files that happen to disagree.

This is the part that matters most on a team, and it took me a while to see why. If you edit in place, the only person who knows the old claim died is you, in the moment you edited. Anyone who read it last week is still carrying it in their head. Anyone whose agent cached it is still acting on it. A superseding record leaves a trail that points forward, so the next person to land on the dead note finds the live one instead of quietly believing the wrong thing.

What this gives a team

The reason I keep this in files in the repo rather than a hosted memory service comes down to three things, and only the first is about cost.

A teammate can argue with it. A decision record in a pull request gets reviewed like code. Someone can comment on the line, disagree, ask why. You cannot review a vector store. You cannot leave a comment on someone's embedding.

The split tells you what is shared and what is not. This one surprised me. Invariants are team knowledge: why the crawler is unreliable, why submitted pricing cannot be trusted, why one deploy path is faster than another. Status is very often one person's environment: which CLI is on whose PATH, whose API key is empty. That second kind does not belong in a shared repo file at all, because it will be false for everyone else the moment it is written. Sorting notes by kind sorts them by audience almost for free.

Anyone can check anything without asking you. "I do not trust your note from August" is a conversation. With a probe attached it is a command someone runs in four seconds and either the note holds or it does not. That removes you as the bottleneck on your own documentation, which is the thing that actually kills shared context in a team of more than two.

The honest limit: none of this is enforced. It is frontmatter and a convention, and a convention holds only while the probes stay cheap. I have one note whose check is "ask Bojan whether he tried the product yet". There is no command for that. It is also the oldest one still open, which I do not think is a coincidence. The notes you cannot cheaply re-run are exactly the ones that quietly outlive their truth.

What it looks like

The dead record does not get edited. It gets an end date and a pointer.

---
name: producthunt-scraper-blocked
kind: status
written: 2026-08-21
last_verified: 2026-08-21
ended: 2026-09-03
superseded_by: producthunt-path-moved
---

Product Hunt returns HTTP 403 to server requests, so `producthunt_slug`
enriches nothing.

The correction is a new record that names its predecessor and says why it died.

---
name: producthunt-path-moved
kind: status
written: 2026-09-03
last_verified: 2026-09-03
supersedes: producthunt-scraper-blocked
supersedes_reason: >
  The 403 is gone. /posts/ now redirects to /products/ and serves 200.
  The conclusion survived but the mechanism inverted, so the old record
  gave the right answer for the wrong reason and implied the wrong fix.
probe: |
  curl -sL -o /dev/null -w '%{http_code} %{url_effective}\n' \
    -A 'Mozilla/5.0' https://www.producthunt.com/posts/notion
expect: >
  200 with a /products/ URL means this record still holds. A 403 means
  Product Hunt started blocking again and this one is dead too.
---

`producthunt_slug` still enriches nothing, but because
`lib/scrapers/producthunt.js` requests `/posts/` and does not follow
redirects. That is a one-line fix, not an impossibility.

An invariant needs less. No probe, because there is nothing to re-run, and no verification date, because it does not decay on a clock:

---
name: pricing-crawler-domain-identity
kind: invariant
written: 2026-09-01
---

The crawler keys on a URL, so a domain that changed hands returns 200 with a
clean, parseable pricing page belonging to a different company. Parse success
is not identity success.

Five fields carry it. kind splits what decays from what does not. probe and expect keep the producing command next to the claim. The gap between written and last_verified is what your trust should actually decay from. supersedes makes a correction legible to someone who was not there.

Start here

If you already have context files and no idea which of them are lying to you, the cheapest useful hour is this:

  1. Read each one and mark it invariant or status.
  2. Delete any status your tests or git log already answer.
  3. For the status you keep, write the command that would check it.
  4. Run those commands.

Step four is where you find out. Two of my five were wrong, and one of them had already caused me to give someone a confidently incorrect answer. Neither was catchable by reading the file. Both read as specific and sure of themselves.

The rot does not go away. It just stops being invisible.


This came out of a comment thread on a post I wrote about the token cost of file-based context. Three people there sharpened the ideas above considerably, and the author of the memory service I had measured showed up to correct my framing and ship a fix, which is not the response I expected. The invariant-versus-status split, the producing-command probe, the two-dates argument and the supersedes record are all theirs. I mostly just applied them and reported what broke.

All writing