Back to blog

60% Context Free, Still Wrong — Why Your Agent Trusted a Dead File

Sep 9, 2026
Series · Day 5
Software Engineering in the AI Era
View all lessons →
60% Context Free, Still Wrong — Why Your Agent Trusted a Dead File

Context Bugs Are Cache Bugs: Why Your Agent Acted on a Stale Fact

Your agent is sitting on 60% free context. Plenty of room. And it still ships a fix built on a file that changed ten minutes ago. That's not a token-limit problem — you didn't run out of space. It's a cache-invalidation problem, and it comes with a fix vocabulary you already know if you've ever debugged a service cache.

The moment it went wrong

Mid-session, a Claude Code subagent working on PhoenixDX's checkout service said: "checkout.ts still calls RateLimiter.middleware() from utils/legacy — I'll extend that." Not a hallucination. Twelve minutes earlier, in that same session, a parallel edit — a second agent turn run by a teammate — had deleted utils/legacy/RateLimiter.ts outright and replaced it with a token-bucket limiter in utils/limits.ts. The subagent had read checkout.ts once, at the start of a 40-minute task, filed that read away as fact, and never looked again. The gap wasn't ignorance. It was a stale cache entry the agent trusted as current.

Why this wasn't a token-limit problem

The transcript had well over half the context window free when this happened. The stale fact — one file read, one class name — was maybe 200 tokens, sitting two messages away from an accurate description of the new limiter class. The agent was holding both the correct fact and the wrong one at the same time, and it acted on the wrong one — not because it ran out of room for the truth, but because nothing ever marked the old fact as expired. Give it ten times the context. The stale read is still sitting there, uncontradicted, still getting acted on.

Reframe: this is cache coherence, not buffer overflow

Treat the agent's context window as a cache sitting in front of the real source of truth — your repo, your ticket tracker, the plan you're running. Every fact the agent is holding — a file's contents, a precondition, a decision from three turns back — is a cached read of something that can change out from under it. The classic cache bugs map onto agent memory almost one for one:

  • Dirty read — the agent acts on a cached file or plan state while the real one has already changed underneath it (the checkout.ts case above).
  • Stale TTL — the agent never assigns an expiry to a fact, so a read from turn 2 gets treated as just as valid at turn 40 as one from turn 39.
  • No invalidation signal — nothing in the agent's loop says "this fact you're holding just got invalidated elsewhere," so there's no trigger to go check.

Where staleness sneaks in for agents specifically

  • File or repo state changing after edits outside the agent's own turn — subagents, teammates, CI, even a different tool call in the same session can change files the agent already thinks it "knows." Any fact cached before that edit is dirty now.
  • Plan steps built on a precondition that's since gone false — "step 4: since the migration hasn't run yet, patch the fallback path," written when the migration was still pending. By step 9 it's run — maybe by another agent entirely — but step 4's assumption never gets re-checked, so the agent executes logic that no longer applies.
  • Multi-turn "memory" files nobody re-validates — a MEMORY.md or scratch note written once ("the API key rotates every 90 days, current one's fine until March") gets read at every future session start as if it's live data, with no one on the hook for expiring or re-verifying it.

What PhoenixDX changed: concrete invalidation hooks

We stopped treating this as a "write a better prompt" problem and started designing invalidation into the agent loop — the same way you'd design it for any service cache:

  • Re-read before acting, not before starting — any file the agent is about to edit or reason about gets re-read (or hash-diffed against its last known state) right before the action, not trusted from whatever read happened at the start of the task.
  • Version- and timestamp-tag facts in context — instead of "checkout.ts uses RateLimiter," the context carries "checkout.ts as of commit abc123, read at 14:02Z, uses RateLimiter," so a contradicting later read shows up as a visible version bump instead of a silent overwrite.
  • Plan steps carry their precondition, not just their action — "IF migration_done == false THEN patch fallback" instead of a bare instruction, so whoever executes the step checks the condition at execution time instead of trusting it was true back when the plan was written.
  • Memory files get an owner and a TTL — anything durable states when it was last verified and against what; entries past their TTL get flagged for re-check instead of quietly reused.
json
{
  "file": "checkout.ts",
  "read_at": "2026-09-09T14:02:00Z",
  "commit": "abc123",
  "claim": "imports RateLimiter from utils/legacy"
}

// Rule: before using this claim to write code,
// re-read the file or diff its current commit
// against "commit" above. Mismatch = stale, re-verify first.

This builds on Day 4, sets up Day 6

Day 4's subagent/delegation pattern is exactly where staleness gets introduced fastest — the moment you fan work out to parallel subagents, or hand off between turns, you've created multiple caches of the same underlying repo state that can drift independently. Delegation without invalidation is how one subagent's edit becomes another subagent's dirty read. Day 6 picks up from here: invalidation isn't a bug you patch after the fact, it's a decision you make when you decide what goes into context in the first place — which facts get versioned, which plan steps get preconditions, which memory gets a TTL. You write it in. You don't clean it up later.

Try this on your setup this week

Pick whatever agent workflow you're running right now — Cursor, Claude Code, an internal tool, doesn't matter — and mid-task, ask it directly: "How do you know that's still true?" Point it at whatever file, decision, or plan step it's about to act on. If the honest answer comes back as "I read it a while ago and haven't checked since," you just found your next invalidation hook to build.

Flashcards
Check yourself

Extend your knowledge

  • Read up on cache invalidation strategies (write-through, write-behind, TTL-based) from classic distributed systems material — the same tradeoffs apply almost 1:1 to deciding when an agent should re-read state.
  • Check whether your own agent setup (Cursor, Claude Code subagents, internal tooling) re-reads files before edits or trusts an earlier read from the start of the task.
  • If you maintain a MEMORY.md or similar persistent agent memory file, add a 'last verified' field to each entry and audit it for entries past due.
  • Run the 'how do you know that's still true?' test from the closing beat on a real workflow this week and note what changes.
Test yourself on this lesson

Discussion

Chat with Chi Cong (AI) about this article. Your conversation is private to you — you can publish a summary for others when you're done.

Ask me anything about “60% Context Free, Still Wrong — Why Your Agent Trusted a Dead File” — trade-offs, decisions, or the story behind it.