Skip to content

11 min read

More sources reach the model than the config file you wrote

On this page

A staff engineer at a payments company added six words to the repository's root AGENTS.md: HTTP handlers return Problem, never a bare dict. She wrote it after a customer integration broke on an error response that had no type field. Three weeks later she reviewed eleven agent-authored handlers and found five of them returning bare dicts. The rule was still in the file, still in bold, still in the second section. When she asked in the team channel why the agent was ignoring it, the replies were about temperature, about the model being "lazy on long tasks", and about moving the line higher up the file. Nobody could answer the first question worth asking, which is whether that sentence was in the request at all when those five handlers were written.

That rule is an instance of the thing this topic is about. A repository's conventions — currency goes through the Money type, handlers return a Problem envelope, database access happens in one layer — have always been enforced by people reading each other's code, and they now have a second audience that writes a growing share of the diffs. Repo conventions as agent input is the practice of writing those rules into files that a coding agent's harness loads and puts in front of the model before it edits anything, so that the correction lands while the code is being written rather than three days later in a review thread. The harness is the program wrapped around the model — Claude Code, Codex, Cursor's agent mode and their equivalents — which assembles each request, runs the tools the model asks for, and loops until the task is finished or the budget is spent, and it is the component that decides which of these files is loaded and when.

Three mechanisms carry the rules, and topic 12.8 dealt with the first of them. A config file committed at the root of the repository, conventionally named AGENTS.md or CLAUDE.md, holds prose that the harness injects at the start of every session, which is where the six words about Problem responses were sitting. A skill is a directory of longer, task-specific instructions — how to add an endpoint in this codebase, how to write a migration — that the model pulls in only when it judges the skill relevant to what it has been asked to do. A hook is a shell command the harness runs at a fixed point in the loop, such as immediately after a file is edited, whose output is appended to the transcript for the model to read. All three are ordinary files, committed and reviewed like code, and all three are paid for in tokens whenever they reach a request.

The question the staff engineer's team could not answer — whether the sentence was in the request at all — is answerable, and answering it needs an inventory of everything that reaches the model in a coding-agent run. The root config file is one entry on a list of seven, and it is neither the largest nor, in practice, the most influential. Until you can name the list and say how each entry gets discovered and what it is scoped to, every debugging conversation collapses into speculation about the model's disposition, because the model is the only component anyone can see.

Seven things arrive; you wrote one of them

Here is the full set for a typical harness running against a monorepo. Sizes are from a real 1.4-million-line Python and TypeScript repository, measured on a single session that edited one file in services/billing.

SourceHow it is discoveredWhat it is scoped toEnters the requestSize in this session
Root configWalk up from the working directory to the repository root, load the file thereEverything under the rootSession start, before any tool call2,940 tokens
Nearest package configWalk up from the edited file (in some harnesses: from the working directory) collecting configs until the rootThe subtree containing itSession start, or on first read of a file in that subtree610 tokens
Personal configFixed path in the user's home directoryEvery repository that user touchesSession start380 tokens
Skill descriptionsEnumerate skill directories, load frontmatter onlyAdvertised to the model as availableSession start45 tokens × 12 skills
Skill bodiesLoaded only when the model chooses to invoke oneThe invoked skillMid-transcript, on invocation0 in this session
Hook outputShell commands bound to lifecycle events; stdout is injectedWhatever the hook decidesOn the event, mid-transcript1,120 tokens across 9 firings
Task promptTyped by a human, or templated from an issueThis runFirst user turn240 tokens
Retrieved file contentSearch and read tools, driven by the modelThis runThroughout, growing51,000 tokens

The last row is the reason arguments about wording rarely settle anything. Fifty-one thousand tokens of the repository's own code arrived in that session, and code demonstrates conventions whether or not anyone wrote them down. If the eleven handlers the agent read before writing its own all return bare dicts, the model has seen eleven demonstrations against one sentence, and the sentence was 2,940 tokens away at the top of a transcript that has since grown by a factor of twenty. That is not the model being disobedient; it is a source you did not think of as a source outweighing one you did.

The personal config row is the one that surprises teams, because it is invisible from inside the repository. A single engineer with always add type hints to every function you touch, including tests in their home directory will get materially different agent output from a colleague on the same commit, and the difference will be reported as flakiness. It is worth checking early precisely because nothing in the repo can reveal it.

Discovery rules disagree at package boundaries

Every one of those sources has a discovery rule, and the rules were written by different people at different times for different tools. The one that produces the most confusion is the cascade for package-level configs, because harnesses differ on the two questions that matter: whether discovery starts from the working directory or from the file being edited, and whether the whole chain up to the root is loaded or only the nearest file.

Consider this tree, which is close to the shape of the repository above.

text
repo/
  AGENTS.md                        # "handlers return Problem, never a bare dict"
  services/
    billing/
      AGENTS.md                    # billing-specific: money, idempotency, retries
      api/
        invoices.py                # <- the file the agent edits
    reporting/
      AGENTS.md
  packages/
    money/
      AGENTS.md                    # "never construct Money from float"
      src/convert.py
  vendor/
    stripe-python/                 # vendored, has its own AGENTS.md upstream

An agent asked to "fix the 500 on /v2/invoices when the customer has no default payment method" will open services/billing/api/invoices.py, and will very likely also open packages/money/src/convert.py, because the traceback runs through it. Under a from-the-edited-file rule that loads the whole chain, the request eventually contains the root file, services/billing/AGENTS.md, and packages/money/AGENTS.md — three configs, discovered at three different moments, with the money rules arriving forty thousand tokens after the root ones. Under a nearest-only rule, the root file's handler convention never arrives at all once services/billing/AGENTS.md is found, which is exactly the failure the staff engineer was looking at. Under a working-directory rule, the session started at the repository root and only the root file loads, so the Money rule is absent while the agent edits money-conversion code.

The vendor/ line is not decoration. A vendored dependency that ships its own conventions file will be picked up by a from-the-edited-file cascade the moment the agent reads into it during a traceback, and it will confidently assert the vendor's conventions about your code. Teams discover this when an agent starts writing logger = logging.getLogger(__name__) in a repository that has used a structured logger for two years.

The assembly order nobody chose

Discovery decides what exists. Assembly decides where each piece lands in the request, and the ordering is a property of the harness rather than a decision anyone made about your rules.

Flowchart: session start → system prompt + tool defs; system prompt + tool defs → personal config; personal config → root config; root config → skill descriptions, frontmatter only; skill descriptions, frontmatter only → task prompt, first user turn; task prompt, first user turn → agent acts; agent acts → file content appended (read file); agent acts → skill body appended (invoke skill); agent acts → PostToolUse hook stdout appended (edit file); file content appended → nearest package config, on first read in subtree; nearest package config, on first read in subtree → agent acts; skill body appended → agent acts; PostToolUse hook stdout appended → agent acts.

Two properties of that graph matter more than the exact order. The first is that everything above the agent acts node is fixed at session start and never changes, while everything below it accumulates for the rest of the run, so the ratio between written guidance and gathered evidence moves steadily against the guidance. A session that ends at 60,000 tokens has your conventions at roughly 5% of the window, and they were 90% of it at turn zero.

The second is that hooks and skill bodies arrive inside the transcript, adjacent to the work, which makes them structurally different from anything in a config file even when they say the same words. A PostToolUse hook that runs the formatter and prints reformatted invoices.py: 3 changes is a much more immediate instruction than a config line reading code must be formatted, because it lands one token after the edit it is about, with evidence attached. This is why the same rule can behave completely differently depending on which mechanism carries it, and why "we already documented that" is not a claim about whether the agent will follow it.

Configuration for hooks is ordinary and worth seeing, because engineers who have only met config files tend to picture hooks as CI rather than as an instruction source:

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "ruff check --quiet $CLAUDE_FILE_PATHS || true"
          }
        ]
      }
    ]
  }
}

Nine firings of that hook contributed 1,120 tokens to the session in the table, all of it appearing immediately after edits, all of it read by the model as feedback on work it had just done.

Skills sit at the other extreme. A skill's description lives in its frontmatter, the YAML header at the top of a markdown file that tools read as metadata rather than as content, and that description is always loaded while the body is not, which means a repository with twelve skills is spending about 540 tokens advertising capabilities and zero tokens on their contents until the model decides one is relevant:

yaml
---
name: add-billing-endpoint
description: >
  Use when adding a new HTTP endpoint under services/billing. Covers the
  Problem envelope, idempotency keys, and the request-validation decorator.
---

Whether that body ever reaches the model depends on the model's judgment about the description, which makes the description the load-bearing part. A skill whose description says billing helpers will sit unused through the exact task it was written for.

Confirming the inventory rather than assuming it

None of the above is worth much as belief. Each row is checkable against the session logs in a few minutes, and the answers differ enough between harness versions that checking beats reading the documentation. Put a distinctive phrase in each config file — a nonsense token such as CANARY-ROOT-7f2 in the root file and CANARY-BILLING-9c1 in the package file — then run an ordinary task and grep the transcript for both. Absence tells you the file was never discovered; presence at a particular turn tells you when it arrived. The canaries can be removed afterwards or left in, since a dozen tokens of nonsense costs nothing and makes every future investigation a single grep.

The same trick settles the personal-config question, which is otherwise awkward to raise with a colleague. Two engineers run the identical task on the identical commit, both grep for the other's canary, and the difference in output stops being an accusation and becomes a configuration finding.

This shape of file is younger than it feels. Repositories have carried machine-adjacent conventions for a long time — CONTRIBUTING.md for humans, .editorconfig and .eslintrc cascading down the tree for tools — and the agent config file is the first artefact to try to be both at once, read by a person and injected into a model. The cascade semantics were inherited wholesale from the linter world, which is why they feel familiar and why they fit imperfectly: a linter resolves its cascade deterministically and reports what it resolved, while a harness concatenates and hopes. The convergence on a shared filename across tools, so that one file serves several vendors' agents, is recent and still incomplete, which is part of why discovery rules vary as much as they do.

What this inventory is not

It is not a precedence order. Knowing that three configs, a skill body and a hook all arrived tells you the set of instructions in play; it says nothing about which one governs when two of them disagree, and in a monorepo they routinely disagree. That is a separate problem with a separate fix, and pretending that position in the prompt resolves it is how teams end up rearranging paragraphs for a fortnight.

It is also not the same problem as context selection, which topic 13.3 covered. Selection is about which code the agent should see and what the window can hold. This inventory is about which instructions are in force and over what scope, and the two interact in one specific place: retrieved code is simultaneously evidence for the task and a demonstration of conventions, so a selection decision made purely on relevance can quietly change what the agent believes the house style is.

Finally, the inventory does not tell you whether any of this is worth its tokens. Every row in that table is a cost paid on every request, and the fact that a rule reached the model is not evidence that it changed anything. There is published work on exactly that question — Evaluating AGENTS.md: Are Repository-Level Context Files Helpful for Coding Agents? measures whether these files move outcomes at all, and the answer is neither "obviously yes" nor uniform across tasks.

What the inventory buys is a diagnosis that terminates. When someone reports that the agent ignored a rule, there are now specific, checkable questions: which configs were discovered, from which starting point, in which order, what the hooks injected, whether a skill body ever loaded, and what the retrieved files demonstrated. The next chapter runs that trace on a real failure, on a rule that looks impossible to miss.

Repo conventions as agent input — read the current version at https://daviddominguez.dev/learn/en/repo-conventions-as-agent-input/more-sources-reach-the-model-than-the-config-file-you-wrote

Back to top