Skip to content

02 · Working with AI agents in a real codebase

What belongs in an instruction file — and what does not

One rule: include what the agent cannot discover by reading the repository, and leave out everything it can. Plus the discovery and truncation behaviour worth knowing.

6 min read

Instruction files fail in a predictable way: they grow, they duplicate what the repository already says, and the two constraints that actually matter end up on line 340. Here is a concrete rule, the discovery mechanics you need to know, and the limits that truncate your file without telling you.

One test decides everything

For each line, ask: could an agent with filesystem access and ten minutes have worked this out?

If yes, delete it. Not because it is wrong, but because it costs tokens on every single request to tell the agent something it will discover anyway, and it displaces the material that only you know.

This test is not a preference. The ETH Zurich study on repository context files found that repository overview sections — the most commonly recommended content — provided no measurable benefit, while machine-generated files, which consist almost entirely of discoverable content, reduced task success and raised inference cost by a fifth. Discoverable content is the category that measurably does not help. The full argument is in why generated instruction files can make things worse.

What to include

The content that survives the test is knowledge that exists only in people's heads or in institutional history.

  • Package manager quirks. That this repository uses pnpm and npm installs will corrupt the lockfile. That one workspace has to be built before another. That a postinstall script fails on Apple Silicon unless a flag is set.
  • Non-obvious conventions. Not "we use TypeScript" — visible. Rather: all dates cross service boundaries as ISO 8601 UTC strings, never Date objects. Errors from the API layer are always instances of one class. New endpoints go through the existing request wrapper even though nothing enforces it.
  • Deprecated but still live dependencies. The old HTTP client that fourteen call sites still use. Do not add new ones, do not migrate the existing ones in passing. This is the single highest-value category, because it is invisible from the code — the old library looks exactly as legitimate as the new one.
  • Custom middleware and hidden machinery. Anything that runs implicitly: a request wrapper that injects tenant context, a serialiser that transforms every response, a build step that generates files nobody should edit by hand.
  • Things that will get you a failed review. The direct version. Raw SQL outside the repository layer. Console logging in production paths. Any new dependency without approval. Editing generated files. Write these as flat prohibitions, not as guidance.
  • Commands that are not in package.json. Real test invocations with the environment they need, the migration command with its flags, how to run one test rather than the suite.

What to leave out

  • Directory listings. One glob. Worse, they go stale within weeks and then actively mislead.
  • Stack descriptions. package.json is definitive and always current.
  • Restated framework documentation. The model knows React. Only note where you deliberately deviate.
  • Aspirational standards. "We aim for 90% coverage" is not a constraint, and an agent that treats it as one will produce test theatre.
  • Historical narrative. The migration you completed last year belongs in a decision record, not in a file loaded on every request.
  • General coding advice. "Write clean, maintainable code" costs tokens and changes nothing.

Discovery mechanics, and a size limit that truncates silently

Where the file is read from is documented and worth knowing precisely. OpenAI's AGENTS.md convention, as implemented in the Codex CLI, resolves in a defined order: a global file can override, then the search runs from the git root down to the current working directory, with files closer to the working directory winning over more distant ones.

The consequence people trip on is that a nested AGENTS.md deeper in the tree takes precedence for work in that subtree. That is useful when deliberate and confusing when someone adds one to a package directory and forgets.

The important detail: there is a size cap, controlled by project_doc_max_bytes, defaulting to 32 KiB — and content beyond it is truncated silently. No warning, no error, no indication in the agent's output. Your file is simply cut off, and the material at the bottom stops existing from the agent's point of view. If your instruction file has grown past roughly 32,000 bytes, some of it is already not being read.

Claude Code has a different mechanism with its own limits. Memory files support @ imports of other files, with a maximum recursion depth of 4. The critical point is that imports do not reduce context cost — an imported file is loaded in full, exactly as if its contents were pasted inline. Splitting a 900-line instruction file into six imported files makes it easier for humans to maintain and changes nothing about what the model has to process.

Write constraints, not aspirations

How a line is phrased changes whether it is followed. The same fact written two ways produces different behaviour, and the difference is not subtle.

Compare "we try to keep database access in the repository layer" with "database access happens only in src/repositories/; any query elsewhere will be rejected in review". The first is a description of a tendency, and an agent weighing it against a locally convenient shortcut will often take the shortcut, because a tendency permits exceptions. The second is a rule with a stated consequence, and it gets followed.

A few phrasing rules that carry their weight:

  • Imperative, not descriptive. "Use X" beats "we generally use X". Hedging reads as optional because it is optional.
  • Name the path. A rule that references a concrete directory or file is checkable. A rule about "the data layer" requires interpretation, and interpretation is where the behaviour drifts.
  • State the consequence. "This will fail CI" and "this will be rejected in review" both give the constraint a reason to be respected over local convenience.
  • Give the alternative. A prohibition without a replacement leaves the agent to invent one. "Do not use the legacy client; use httpClient from src/lib/http.ts" resolves in one line what a bare prohibition leaves open.
  • Delete anything you would not enforce. A rule nobody enforces teaches the reader that the file's rules are advisory, which degrades every other line in it.

Length discipline

Practitioner consensus, consistent across tools, lands in roughly the same place: keep the file under about 200 lines, and put hard constraints in the first 40.

Both halves matter. The 200-line ceiling is about cost and maintainability — a file that exceeds it is generally accumulating rather than being curated, and nobody prunes a document they did not write. The first-40-lines rule is about position: attention is not uniform across a long context, and material buried mid-document is measurably less reliably used than material near the start. The prohibitions that must never be violated go at the top, stated flatly.

A useful structure: hard constraints first, then the non-obvious conventions, then commands, then everything else — with everything else being short enough that it does not push the rest out of reach.

When the file should point elsewhere

Some content is genuinely necessary and genuinely too large for a file loaded on every request: domain rules, architectural decisions with their rationale, the current state of a long migration.

That material belongs in a structured layer the agent reads on demand, with the instruction file acting as a router — a short statement of where authoritative knowledge lives and how to navigate to it, rather than a container for the knowledge itself. This keeps the always-loaded file small while leaving the depth reachable.

This is what Codee3 does with the instruction files for the agent surfaces it supports: they become routing documents pointing into a governed .ai/ layer where documents declare their authority class and confirmation date, and guards fail CI when that layer's structure breaks. The instruction file stays short because it is not trying to be the knowledge base. What goes into either one is still written by you — and on the available evidence, that is the part worth keeping human.