/ Back to notes

July 13, 2026

7 min read

ai / developer-experience / software-architecture

What Makes a Codebase AI-Friendly?

A coding agent produced a change that was locally reasonable and globally wrong. Improving the prompt fixed it once. Moving the missing knowledge into the repository, as discoverable references, official commands and executable rules, addressed the kind of mistake rather than one instance of it.

What Makes a Codebase AI-Friendly?

The change compiled, the main path worked, and the diff looked convincing. During review, I found that it was wrong for this repository.

I was using Codex to add a capability to one of my TypeScript backend projects: connect it to the existing data flow and update the relevant tests. The agent found a pattern that existed somewhere in the codebase and copied it, but it was not the pattern that module should use. It ran the most obvious validation command, but not every check the affected behavior required. It solved the request I wrote, but not the version of that request the repository actually needed.

My first fix was a better prompt. That worked until the next task needed a different piece of undocumented knowledge.

What lasted was moving that knowledge into the repository. This post is about what that means in practice. The usual first answer, "add an AGENTS.md", covers only part of it, and the available evidence about that part is more mixed than its popularity suggests.

Three things the repository could not tell the agent

Looking back at that review, each mistake pointed to something missing from the repository rather than from the model.

Which pattern is the right one. The pattern the agent copied did exist in the codebase. Nothing in the repository said it was the wrong one for that module, so choosing it was a reasonable guess. A new engineer with the same information could have made it too.

What "done" means. The agent ran the most obvious validation command, and that command did not cover everything the change affected. Without a documented answer to "how do I verify this kind of change?", it had to guess, and it guessed the obvious option.

Which boundaries are real. Some of the constraints the change violated were not written anywhere the agent would read, and none of them were checked by the build.

These three gaps map onto the properties I now look for.

Make the right example easy to find

Context is most useful when it sits near the work and stays current. Dumping every architecture doc, convention and historical note into the initial context feels thorough, but it costs tokens and dilutes attention. Thoughtworks calls the alternative progressive context disclosure: a small global contract first, with deeper context discoverable where it is relevant.

In practice:

  • a short root instruction file that routes to the rest;
  • per-module notes or scoped instruction files, where module conventions differ;
  • one reference implementation per pattern, marked as current, with older variants marked as legacy;
  • ADRs for decisions that would otherwise look arbitrary.

Good engineers work this way: nobody reads the whole repository before editing a file. The repository should let an agent work the same way.

Make "done" a command

Instructions tell an agent what you want. Feedback tells it whether it got there. A sentence like "respect the architecture" is easy to write and impossible for an agent to check. A failing test, a type error or a lint violation is a concrete signal it can act on: change, validate, interpret, correct, validate again. Thoughtworks describes compilers, tests and linters in this role as feedback sensors for coding agents.

Speed matters too. A 45-minute suite is useless inside an edit loop. Layered checks help: format and lint, then typecheck, then the tests for the touched module, then the full suite. Document which layer each kind of change requires. That missing document is exactly the gap my agent fell into.

All of this assumes the repository can be set up in the first place. Pinned runtimes, committed lockfiles, a .env.example, reproducible migrations and seeds: if a new engineer cannot reach a valid local state from short instructions, an agent starts every task by guessing.

Make important boundaries executable

An instruction file can state a boundary:

Controllers must not import repositories directly.

That helps, but it depends on interpretation. A dependency rule can reject the violation. The rule below is illustrative, in the shape of a dependency-cruiser forbidden rule. Adapt it to your tool and check that a prohibited import actually fails:

{
  name: "no-controller-to-repository",
  from: { path: "controllers" },
  to: { path: "repositories" },
  severity: "error"
}

The instruction explains intent, and the check enforces it. If a rule matters enough that a violation would block a code review, it should probably also block the build.

What the evidence says about AGENTS.md

This section was updated in October 2026 to reflect the paper's latest revision.

Instruction files are where most teams start, so it is worth checking what they actually do. In the third revision of Evaluating AGENTS.md (Gloaguen et al., September 2026), context files did not generally improve task success rates and raised inference cost by more than 20% on average. That held both for model-generated files and for files committed by developers, in the settings the authors evaluated.

I read that as a reason to keep instruction files short and specific, not as a reason to delete them. That reading is my own: the paper did not test short, routing-only files against long ones, so the structure I recommended earlier is a hypothesis to check in your own workflow. A file worth its tokens holds what an agent cannot cheaply infer: official commands, non-obvious directory boundaries, files never to edit, constraints on migrations, generated code or public APIs. It should not be a hand-maintained copy of the codebase.

A related result points at task definition. RACE-bench reports that current agents understand high-level intent well but lose accuracy turning it into concrete implementation steps. My inference, not the benchmark's, is that this gap is where task contracts help: current behavior, desired behavior, constraints, affected area, edge cases, the validation command and acceptance criteria. "Improve the endpoint" carries intent. A contract carries the shape.

The execution environment is part of the interface

Once an agent can run commands, install packages and use credentials, the repository is not the only thing that matters. Package scripts, test hooks and post-install steps all execute code, so "the repository is ours, so it is safe" is not a sound default. Sandboxed execution, least-privilege credentials, protected branches, approval for risky actions and reviewable diffs belong to the same design. The more autonomy the agent has, the more these boundaries carry.

The test I use now

When an agent makes a locally reasonable but globally wrong change, I ask which of these was missing:

  1. Could it find the current, correct example for this module?
  2. Could it run the checks that define "done" for this kind of change?
  3. Would a build have rejected the boundary it crossed?
  4. Did the task say what success looks like?
  5. Did it have more authority than the task needed?

The answer usually points at something to add to the repository, and the fix then applies to every future task, not just the next prompt. For me, an AI-friendly repository is one where the intended way of working is easy to discover and correctness can be checked by running a command.

References