DEV Community

Context anchoring works. Files, not a service.

Bojan Tomic on September 02, 2026

I used to keep AI chat sessions open far longer than made sense, because closing one meant re-explaining my project from scratch. Rahul Garg has a ...
Collapse
 
reidmarlow profile image
Reid Marlow

The 8k token tax from MCP tool schemas alone explains why so many agent setups feel sluggish before doing any actual work. Shipping dozens of JSON schemas on every round trip eats context window fast.For context rot, the failure mode usually comes from letting anchor files track current feature status rather than invariants. Feature status belongs in tests and git history. When an anchor file documents what exists today instead of why a constraint exists, it starts drifting the moment someone pushes a commit.

Collapse
 
bojantomic profile image
Bojan Tomic

This is the best answer I have had, and it explains my own example better than I did. "Does not exist yet" is a status. It was never going to survive me building the thing.

After reading this, I went back through my anchor files, and the split held. The ones that rotted were all status: one said a price column was mostly unverified, true when written and false four days later once a script had worked through it. I even found the stale number still sitting in that file's summary line while the body had been corrected.

The ones that held up read like your invariants. "This scraper returns 403, so that field enriches nothing." Still true months on, because it says why a constraint exists rather than what the code looks like today.
Where I am still stuck: some things I need to record are status with no invariant form. "This API key has no credit" is a fact about today, not a design constraint, and the agent still needs it. For those, I have only got a date on every line and a rule to verify anything naming a file or flag before acting on it.

On the token tax: 8,866 was 27 schemas with nothing stored, so that is the floor, not the ceiling.

Collapse
 
heinrichneb profile image
Heinrich Neb

The 8,866 came from my server, so I can confirm it: that is the tool-schema catalog, and it is a real architectural tax - you pay it before a single memory is stored. One correction to the framing, not the number: 579 vs 8,866 is index-versus-catalog, not file-versus-service. The catalog scales with tool surface, not with stored memories, and clients have started lazy-loading schemas on demand, which moves that cost from "every request" to "first use". A lean profile with three read tools is the configuration that should exist, and your post is a good argument for shipping it.

On staleness - that open problem at the end is the one I ended up building the most machinery for, and the core of it works in plain markdown too: a correction never edits the old anchor in place. It writes a NEW record that names the old one ("supersedes: X - reason: the check exists since today"), and the old one gets an end date. Whatever serves your anchors then refuses to serve the dead one without pointing at its successor. Your date-plus-reason rule is half of it; the missing half is that the relationship between old and new is DATA, not something the next reader has to reconstruct. Two frontmatter fields would do it in your setup.

Where I landed after measuring both directions: files are the right substrate for decisions a team reviews in PRs. The service earns its cost only where files stop - recall across machines mid-incident, and refusing to serve me my own confidently-wrong anchor.

Collapse
 
heinrichneb profile image
Heinrich Neb

The token number, closed out: I reproduced your measurement at the wire - 27 schemas, 32,778 bytes, ~8,859 tokens. Your 8,866 was exact to within tokenizer rounding. For context, that was already the slimmed state: the full surface is 123 tools (~27,700 tokens), cut to 27 in August behind a dispatcher that lists names without schemas.

Your post pushed it one step further: 0.10.152 adds CACHLY_PROFILE=lean - eight tools plus the dispatcher, measured 3,712 tokens per request. Everything else stays callable through the dispatcher, just without schemas riding along on every request.

Still not 579, and it never will be: the largest remaining block is the field documentation of a single write tool, and that prose is doing work - it is what teaches a model to put the key fact into the first hundred characters of a lesson instead of burying it. A one-line file index doesn't pay that tax because it doesn't buy that behavior. Your framing stands - where you put the state prices every request. Because you measured it, the price here dropped 58% in an afternoon.

Closing the loop on the autopilot claim from our other thread: verified, you were right - the file-writing code appended safely, but the printed instructions said cat > and would have flattened an existing CLAUDE.md by hand. Fixed in 0.10.152 (appends, and the same sweep caught an identical cat > in the git-hook guide). A bug report inside a comment, confirmed and shipped also same day.

Collapse
 
bojantomic profile image
Bojan Tomic

The correction is fair, and I should have caught it. 579 vs 8,866: measured index against the catalog, not files against a service. The catalog scales with tool surface, so a lean profile was always the fix, and my table implied an architectural conclusion that the numbers do not actually support. 3,712 with eight tools and a dispatcher is the honest comparison, and it will never be 579 for the reason you give: the field documentation buys behavior my one-line index does not even attempt to buy. Different things, priced differently.

The supersedes point answers the question I left open. My rule put the date and the reason on the anchor and still left the next reader to work out that a newer note had killed an older one. Making that relationship data rather than inference is the part I was missing, and two frontmatter fields is a fair price.

It also complements what someone else raised in this thread: store the probe that re-verifies a claim, and the date it last ran clean. Supersedes handles the deaths you noticed. The probe handles the ones you did not. I added probes to my own anchors yesterday and immediately found two already wrong, both claims about systems I do not own.

Which is where your last paragraph lands hardest. A file will hand you a confidently wrong anchor with no hesitation at all. Refusing to serve a dead record is the thing a substrate can do and a directory of markdown cannot, and it is a better argument for the service than sync ever was.

Good turnaround on the cat > fix.

Thread Thread
 
heinrichneb profile image
Heinrich Neb

Your probe line is the part I acted on - we shipped a version of it today, and the measurement that made it worth shipping was one I did not go looking for.

We checked why our own staleness badge was being ignored, and found the field underneath it was borrowed. Every read stamped verified_at with the current time, so 64 % of entries carried a verification date later than the day they were written, and the store's median age looked a week younger than it was. None of that was verification; something had merely been looked at.

With that fixed, a lot of entries went red at once - which is where your point does the work. A red with no next step gets clicked past. So the badge now carries the entry's first stored command, verify before applying, e.g. curl -s ..., from a field that already existed rather than a new one nobody would fill.

One thing worth flagging if you ever automate this: we kept the server out of it. It shows the command, it never runs it. A stored command that a service executes by itself is a back door in anything shared.

Your two already-wrong anchors after a single day is what convinced me, more than the argument did.

Collapse
 
vinhnguyenthanhdn profile image
Vinh Nguyen

Both fixes in this thread trigger on someone noticing: a supersedes record needs a person to know the old anchor died, and the invariant-versus-feature-status split assumes the drift shows up in your repo. The class that defeats both is a claim about something you do not own — "this endpoint omits field X", "that port is dead". No commit contradicts it, so git history holds no evidence either, and it is not feature status. I had one this week saying a dead debug port caused a silent fallback; re-measuring showed the dead port exits loudly with a discovery error and the missing flag was the quiet one, which is roughly the opposite instruction. Keeping the command that produced the anchor beside it is what helped, since re-running beats re-reading, though only while re-running stays cheap.

Collapse
 
bojantomic profile image
Bojan Tomic

Your category is the one that bit me, and I found out only because of your last point.

I attached the producing command to every anchor that records a state rather than a constraint, then ran two. Both were wrong, and both were claims about someone else's system, so none of my commits could have contradicted them.

One said a third-party site returns 403 to server requests. It now returns 308 to a renamed path that serves 200. The conclusion in my note still held, but only because my scraper does not follow redirects, so the mechanism had inverted while the instruction stayed accidentally correct. That is your debug-port case exactly, and it is worse than plain staleness because it survives review.

Your caveat is where I am stuck too. One of my anchors has no command; its check is asking a human whether he tried the product yet. It is also the oldest one still open, which I doubt is a coincidence.

Collapse
 
vinhnguyenthanhdn profile image
Vinh Nguyen

There is a structural reason that one is the oldest still open, separate from the human being expensive. The two anchors that bit you were reversible claims - a site returns 403, a port is dead - and a reversible claim emits a signal whenever reality flips, so a probe has something to catch even when nobody is watching. "He has not tried it yet" is monotone: it is only ever falsified by an event happening, and the event not happening produces nothing at all. Its wrong state is silent by construction, so no amount of probe discipline makes it detectable.

Which suggests the instrument is different for that class. Decay-from-last-clean-run assumes a check exists to run; for a monotone claim the honest field is an expiry - a date after which the anchor is void whether or not anyone looked - because the cost is one-sided too. Acting on "not yet" after it became "yes" is the only direction that hurts, and an expiry is the one rule that does not need someone to notice first.

Collapse
 
izgorodin profile image
Edward Izgorodin

Bojan, the working rule keys trust to the wrong date. An anchor has two dates, when it was written and when its claim was last verified, and the staleness discount belongs on the second. A year-old anchor re-checked this morning is fresher than yesterday's anchor nothing has looked at since, and the file's own metadata cannot tell the two apart. That is also the sharp half of the trust asymmetry you name: the model discounts by whatever date it is shown, so a file that carries only its write date teaches the reader exactly the wrong confidence, in both directions at once. The fix composes with the keep-the-command idea from the thread instead of competing with it: next to the claim, store the command that would re-verify it and the date it last ran clean. Then the anchor is not a statement with a birthday, it is a claim with an attached probe and a verification date, and the discount rule becomes mechanical: trust decays from the last clean run, not from the write. Rot does not disappear, but it becomes measurable file by file, and the files that matter most, the ones read on every session start, are the cheapest to keep re-verified, because their probes run anyway. And the per-request measurement is the right kind of honest: an always-loaded index should be priced per request, and more comparisons in this space should start there.

Collapse
 
bojantomic profile image
Bojan Tomic

You are right, and I have only built half of it. The probes I added today record what to run, but not when they last ran clean, so the reader still discounts by the write date.

What convinced me is finer than per-file. One of my anchors carries a single date over two claims: a convention, still true months on, and a coverage count that was false within four days. Same file, same birthday, completely different decay. Dating the file cannot express that, and I had already quoted the rotten half as current to argue a decision.

So the probe wants to sit next to the claim, not the file, with its own last-clean-run date. Trust then decays per claim, which is where the variance actually lives.