# Preventing AI context rot: why agents follow outdated instructions > Instruction files describe a moment and the project moves on. How to tell what rots, how to keep the durable part small, and where to keep the part that changes. Source: https://autoplans.dev/guides/context-rot ## What context rot is An instruction file describes a moment. It is written on a Tuesday, when the test command is `npm test`, the work is happening on `feature/checkout-v2`, and `src/legacy/` still exists. Then the project moves. The runner becomes Vitest, the branch is merged and deleted, `src/legacy/` goes in a cleanup. Nothing in the file changes, because nothing forces it to. Six weeks later an agent reads it and does exactly what it says. That is context rot: the gap between what the instruction file asserts and what the repository is, plus the fact that the agent cannot tell a live line from a dead one. Every line arrives with the same authority. "Run `npm test` before committing" and "the payment flow is being rewritten on `feature/checkout-v2`" look identical to a model — both are project instructions, in the project's own file, in the same voice, with no timestamp and no way to check. A human notices that the branch does not exist and moves on. The agent recreates it. The phrase carries a second meaning too. Chroma's report *Context Rot: How Increasing Input Tokens Impacts LLM Performance* uses it for degradation as input length grows, finding that "model performance varies significantly as input length changes, even on simple tasks". The two compound. This guide is about the staleness half, which you can fix by editing files. ## Why a coding agent follows outdated instructions Three properties of how these files load, none of them accidental: 1. **They load unconditionally.** The file is read at the start of every session, before anyone knows what the session is about. A line that is wrong is wrong in every session until someone edits it. 2. **Nothing verifies them.** No tool checks that the commands run, that the directories exist, or that the branch is still open. A README that lies fails a human's sniff test; an instruction file that lies fails nothing. 3. **The agent is meant to obey them.** Anthropic's documentation is direct about what that means: memory files are "context, not enforced configuration", and "if two rules contradict each other, Claude may pick one arbitrarily." That documented behaviour is about two rules inside the instruction files; the documentation says nothing about a file contradicting the repository. Nothing makes that case resolve any better, though: the file is already in the context window, the repository has to be looked up, and no step forces the check. ## The symptoms | What you see | What rotted | |---|---| | The agent recreates a directory you deleted | A layout section listing `src/legacy/` | | The wrong test runner: it runs `jest`, finds no tests, then starts guessing commands | A commands section written before the migration | | Obsolete branch names — it branches from one merged three weeks ago | A workflow section naming the branch of the day | | Work that is already shipped gets proposed again, or re-done | A task list pasted into the instruction file | | The agent works around a bug that was fixed in April | An undated gotcha | | Two agents on the same repository disagree about the conventions | `AGENTS.md` and `CLAUDE.md` copied rather than imported, then drifted apart | The common tell is confidence. The agent is specific, cites the file, and is wrong. A stale CLAUDE.md does not produce hedging; it produces a clean, well-argued patch against a codebase that no longer exists. ### The AI agent forgets project context between sessions The opposite complaint has the same root. A session carries nothing forward from the last one on its own — "Each Claude Code session begins with a fresh context window", as the memory documentation puts it, and only the files loaded at start cross the gap — so anything established in conversation rather than written down is gone by morning. The natural fix is to write more into the file, and what people write down is exactly what changes: what is in progress, what is next, which branch, what was decided yesterday. Curing the forgetting by enlarging the file accelerates the rot. Both failures come from asking one static document to hold two kinds of information. ## What rots fastest | Line | Goes wrong when | Rough half-life | |---|---|---| | `pnpm test` runs the suite | CI or the toolchain changes | Months | | Two-space indent, no default exports | A team decision, rarely | Years | | Route handlers live in `src/api/handlers/` | A refactor moves them | Quarters | | Current work is on `feature/checkout-v2` | The branch merges | Days | | Next: wire up the webhook, then the retry queue | The next commit | Hours | | `sharp` 0.34 breaks the Linux build | Someone fixes it, and says nothing | Weeks, silently | A line rots at the speed of the thing it describes. Conventions change by decision, and decisions are rare and loud. State changes by work, and work is constant and quiet. The gotcha row is the dangerous one: the highest-value line in the file the day it is written, misinformation a month after the fix, with nothing to signal the transition. ## Durable conventions belong in the file, state belongs in a system One test sorts almost every line: **could this be wrong tomorrow without anyone editing the file?** If no, it is a durable convention and belongs in the file — commands, code conventions, layout that survives refactoring, dated gotchas, and one line saying where the plan lives. If yes, it is state, and belongs in a system with an API the agent can query when it needs the answer. Every tool's documentation pushes the same way, by capping the file: | Tool | Stated limit | |---|---| | Claude Code | "target under 200 lines per CLAUDE.md file. Longer files consume more context and reduce adherence" | | GitHub Copilot | "Instructions must be no longer than 2 pages", and "Instructions must not be task specific" | | Cursor | "Keep rules under 500 lines"; "Split large rules into multiple, composable rules" | A budget that small only works if state lives elsewhere. Copilot's "must not be task specific" is the same rule stated as a prohibition: task-specific content is state, and state does not belong in the file. ## Context window vs project state Two different stores, and most rot comes from treating them as one. | | Context window | Project state | |---|---|---| | Lifetime | One session | The life of the project | | Written by | Whatever was loaded at start, plus the conversation | Everyone, continuously | | Freshness | Accurate as of load time | Accurate as of the last write | | Capacity | Bounded, and degrades as it fills | Effectively unbounded | | Shared between agents | No | Yes | | Checkable | No — it is text | Yes — a call returns the current answer | An instruction file pre-fills part of the context window. It is a snapshot, and snapshots age. Project state is not a document; it is whatever answers "what is true right now", and the only honest way to get it into a context window is to fetch it during the session. So the file should carry *pointers* to state rather than state. "The backlog is in the tracker, reach it like this" stays true through a hundred status changes. "These are the eight tasks left" is wrong by Thursday. ## Where the state goes: something with an API The requirements are unglamorous: reachable from inside a session without a human in the loop, current when read, writable so the agent can record what it did, and able to identify *this* project unambiguously. An issue tracker with an API meets all four. GitHub Issues through the `gh` CLI or its MCP server, Jira or Linear through theirs — any of them is a correct answer, and the one your team already uses is usually the best one. The file then holds a pointer: ```markdown ## Where the plan lives Work is tracked in GitHub Issues. Ready work carries the `ready` label: gh issue list --label ready --state open Take the top one, comment on it when you start, close it when the tests pass. Do not keep a task list in this file or in a TODO.md. ``` That block cannot go stale from work happening, because it contains no work. Autoplans is one instance of the same shape, built for this case. A folder is linked to a project by a committed `.autoplans/project.json`, described in [Projects](/docs/projects): ```json { "projectId": "1fb3fe7d-e3f9-4992-ae18-29b2bade4bd4", "name": "My project", "url": "https://autoplans.dev/dashboard/projects/1fb3fe7d-...", "linkedAt": "2026-08-26T10:00:00.000Z" } ``` The file holds identity, not status. It travels with the repository, so a teammate cloning the repo is pointed at the same backlog, and every Autoplans app reads it, so "which project is this?" has one answer rather than one per tool. The backlog lives behind the [MCP server](/docs/mcp) at `https://autoplans.dev/api/v1/mcp`, which speaks Streamable HTTP and authenticates with an API key as a bearer token. An agent reads the project id from the marker and calls `list_tasks` with it — one of around forty [tools](/docs/tools) — instead of listing every project and guessing. None of this depends on Autoplans being the tracker. The shape is what matters: identity committed to the repository, state behind an API, and a file that names the first and fetches the second. The case for giving an agent a queryable backlog at all is in [why a coding agent needs a backlog, not a prompt](/guides/ai-coding-agent-backlog). ## Persistent context for coding agents Four stores are in play, each holding a different kind of thing: | Store | Holds | Rots | |---|---|---| | The committed instruction file | Durable conventions and commands | Slowly, if you keep state out | | A committed marker or config file | The project's identity — an id, a repository, a tracker URL | Almost never | | A system behind an API | Everything that changes | Never; it is the source of truth | | Tool-managed memory | Corrections and preferences the tool learned | Managed by the tool | The fourth is worth understanding because its design agrees with the argument here. Claude Code's auto memory keeps per-repository notes in `~/.claude/projects//memory/`: a `MEMORY.md` index loaded at the start of every session — its first 200 lines or 25KB — and topic files read on demand. It is machine-local, a personal aid rather than something the team shares. Of the four kinds of note it records, one is `reference`: "where to find information outside the project, such as an issue tracker or dashboard". A pointer, not a copy. It also stamps a `modified` timestamp into a memory file's frontmatter, which "shows how current the fact is" — dating a note is the mitigation, and worth doing by hand where nothing stamps it for you. ## AI agent context files, tool by tool These files rot the same way whichever you use. What differs is the loading rule, and a narrower rule means a smaller blast radius when a line goes bad. | File | Read by | Loaded | Guide | |---|---|---|---| | `CLAUDE.md` | Claude Code | Ancestors at launch, subdirectories on demand | [CLAUDE.md](/guides/claude-md) | | `AGENTS.md` | OpenCode, Codex, Amp, Cursor, Copilot | At session start; nearest file wins, or files concatenate | [AGENTS.md](/guides/agents-md) | | `.cursor/rules/*.mdc` | Cursor | Always, on a glob match, when the agent asks for it, or when @-mentioned | [.cursorrules](/guides/cursorrules) | | `.github/copilot-instructions.md` | GitHub Copilot | Repository-wide, plus `.instructions.md` files scoped by glob | [copilot-instructions.md](/guides/copilot-instructions) | Path-scoped rules — Cursor's four application types, Copilot's `.github/instructions/*.instructions.md`, Claude Code's `.claude/rules/` with a `paths` field — are the structural defence. A stale line about the API layer that only loads when someone opens a file under `src/api/` cannot misdirect a session about the frontend. ## How to keep AI agent instructions up to date - **Change the file in the pull request that changes the fact.** When the test command moves, the commands section moves in the same commit. It is the only rule with a trigger, and so the only one that reliably works. - **Date every gotcha.** `(2026-06)` at the end of a warning makes it retirable. Undated warnings live forever. - **Delete the "current work" section.** It is the single largest source of rot, and everything in it belongs in the tracker. - **Prefer commands to descriptions.** A stale command fails loudly the first time it runs; a stale description of the architecture misleads silently for months. - **Run the file, quarterly.** Paste each command into a shell; check each path exists. Ten minutes finds most of the dead lines. - **Use the tool's own trimming.** Claude Code's `/doctor` proposes trims for a checked-in `CLAUDE.md`, cutting "content Claude can derive from the codebase, such as directory layouts, dependency lists, and architecture overviews" and keeping "pitfalls, rationale, and conventions that differ from tool defaults". - **Re-read after every large refactor.** A refactor invalidates layout claims wholesale, and nothing else will catch it. - **Watch for contradictions across files.** A root file, a package file and a personal global file can disagree, and the outcome is arbitrary when they do. Preventing AI context rot comes down to a filing decision made once and then held: durable things in the file, changing things behind an API, a pointer between them. The same split, argued at length, is in [CLAUDE.md rots because it holds the plan](/blog/claude-md-agents-md-context-rot).