$ emrebener
home blogs ai & ml a field guide to repository context files

A Field Guide to Repository Context Files

author: emre bener read time: 15 min about: ai agent, software documentation, architecture decision record
published: updated: mentions: agents.md, claude code, readme, domain-driven design, monorepo, markdown

1. Two Questions That Sort Every File

A repository can accumulate a dozen Markdown files that all look like documentation, but only two questions decide how an AI agent actually treats any of them: does the agent load the file automatically, and does anything enforce its existence or format? Answer those two and every file sorts cleanly into a tier.

The first question is about context injection. Some files are read by the agent’s harness at the start of a session and prepended to the model’s context with nobody asking. Most files are not. They are ordinary text the agent reads only if something steers it there.

The second question is about enforcement. A few files are checked by tooling: a platform validates their shape, gives them special UI, or fails a build without them. Most files are checked by nothing at all. They exist purely by convention, and a stale one fails silently.

Cross those two axes and you get three tiers:

TierAuto-loaded into agent context?Enforced by tooling?Examples
Agent instruction filesYes, by the agent harnessYes, by the harnessCLAUDE.md, AGENTS.md
Platform-recognized filesNoYes, by the host platformREADME.md, CONTRIBUTING.md, LICENSE
Convention filesNoNoARCHITECTURE.md, ADRs, domain glossaries

This guide is about the bottom row. The convention layer is where most of a codebase’s hard-won knowledge can live, and it is the tier people most consistently misunderstand. The single most common mistake is writing a file with an authoritative-sounding name, a CONTEXT.md or a glossary, and assuming the agent will find and honor it. It will not. Nothing loads a convention file, nothing reminds you it exists, and nothing tells you when it has drifted out of date.

The top row matters here mainly as contrast, because the misunderstanding runs in both directions: people also assume a file is not special when it is. So start with the one tier that does load itself.

Two questions, three populated tiersPlatform-recognized filesREADME.md, CONTRIBUTING.md,LICENSEAgent instruction filesCLAUDE.md, AGENTS.mdConvention layerARCHITECTURE.md, ADRs,glossaries(nothing lives here)enforcedby toolingnot enforcedby toolingnot auto-loadedauto-loadedTwo questions, three populated tiersPlatform-recognized filesREADME.md, CONTRIBUTING.md,LICENSEAgent instruction filesCLAUDE.md, AGENTS.mdConvention layerARCHITECTURE.md, ADRs,glossaries(nothing lives here)enforcedby toolingnot enforcedby toolingnot auto-loadedauto-loaded

2. The Auto-Loaded Layer: CLAUDE.md and AGENTS.md

Exactly one tier of files enters an agent’s context for free: the agent instruction files. For Claude Code that file is CLAUDE.md. The cross-tool convention the ecosystem is converging on is AGENTS.md. The harness reads these at session start and splices their contents into the model’s context before your first message.

Injection has a concrete cost: the file’s full text occupies context on every single session. That is the central design constraint for these files. They should hold instructions the agent needs constantly, build commands, conventions, hard rules, and not reference material it needs occasionally. Reference material belongs in the convention layer, reached a different way (section 3).

2.1. CLAUDE.md works at several levels

The detail most people miss is that CLAUDE.md is not a single file at the repo root. Claude Code looks for it at several scopes and loads all of them together:

  • Enterprise or managed scope. A system-level file an organization deploys to every machine. It carries policy that should apply regardless of project or user, and it has the highest authority.
  • User scope. ~/.claude/CLAUDE.md in your home directory. Your personal preferences, applied to every project you open on that machine.
  • Project scope. CLAUDE.md at the repo root, checked into version control, shared by everyone who clones the repo.
  • Project-local scope. CLAUDE.local.md, also at the repo root but gitignored. The same project as the file above, but yours alone: personal instructions and customizations you want for this repo and do not want to commit.
  • Directory scope. A CLAUDE.md inside a subdirectory, picked up when the agent works with files in that subtree.

They stack rather than compete. The user file carries preferences true everywhere (“prefer pnpm”, “I am on macOS”). The project file carries facts true for this repo (“tests run with make test”, “the API contract lives in proto/”); its CLAUDE.local.md companion carries the same kind of repo facts that happen to be personal rather than shared. A subdirectory file carries facts true only for that corner (“everything in legacy/ predates the lint config, do not reformat it”). A narrower scope describes a narrower truth. It adds detail rather than overwriting what the broader scopes already established.

How CLAUDE.md scopes stackEnterprise / managed · org-wide policy, highest authority~/.claude/CLAUDE.md (user) · your preferences, every projectCLAUDE.mdthis repo, shared &committedCLAUDE.local.mdthis repo, personal &gitignoredsubdirectory CLAUDE.mdone subtree onlybroad to narrowHow CLAUDE.md scopes stackEnterprise / managed · org-wide policy, highest authority~/.claude/CLAUDE.md (user) · your preferences, every projectCLAUDE.mdthis repo, shared &committedCLAUDE.local.mdthis repo, personal &gitignoredsubdirectory CLAUDE.mdone subtree onlybroad to narrow

One feature of CLAUDE.md matters for the rest of this guide: it can pull in other files with an @path import. A line reading @docs/architecture.md splices that file’s contents in directly. That is the mechanism by which a convention file, which nothing auto-loads, gets dragged into the auto-loaded tier on purpose. Hold that thought for section 3.

2.2. AGENTS.md and the convergence

AGENTS.md is the same idea without the vendor prefix. As the number of coding agents grew and each one shipped its own instruction filename, a single cross-tool name emerged: AGENTS.md, adopted by OpenAI’s Codex and read by a growing number of other agents since. The format is deliberately unremarkable: plain Markdown with no required schema and no front matter.

For a team running more than one agent, the practical move is to keep the real content in AGENTS.md and make any tool-specific file a thin pointer to it, either a symlink or a one-line @AGENTS.md import. Which file you treat as primary matters far less than not maintaining the same instructions in two places, where they will quietly diverge.

Whatever you name it, keep it lean. The instinct to pour architecture explanations, domain vocabulary, and decision history into CLAUDE.md is strong, and it is exactly the instinct this guide exists to redirect. That content has a home, and the agent reaches it a different way.

3. The Convention Layer: Files an Agent Won’t See on Its Own

Most documentation an agent could benefit from lives in the convention layer: ordinary Markdown that nothing loads, nothing validates, and nothing reminds you to write. An agent reads these files only if it goes looking or if you point it at them. This is the tier that carries the real weight for a codebase, and the tier people most often get wrong.

The mistake is specific enough to name. You write a CONTEXT.md describing your domain, or an ARCHITECTURE.md mapping the codebase, and you assume that because it has an authoritative name and sits at the repo root, the agent treats it specially. It does not. To the harness it is notes-3-final.md with a better name. CONTEXT.md in particular is not a standard at all. It is a filename some teams and skills adopt by convention, and another team’s DOMAIN.md, or docs/domain-model.md, is exactly as valid and exactly as unrecognized. There is no registry of blessed filenames here. There is only what you, or a tool you have configured, choose to read.

What lives in this tier:

  • ARCHITECTURE.md: a high-level map of the codebase, popularized by Alex Kladov (matklad) in a post titled, simply, ARCHITECTURE.md. It names the major components, says where each one lives, and marks the important boundaries, so that a newcomer, human or agent, knows which file to open first. It describes structure as it currently is.
  • Architecture Decision Records (ADRs): the recorded rationale behind specific decisions. Section 4.
  • Domain glossaries: the project’s vocabulary, defined precisely. Section 5. The filename varies (GLOSSARY.md, DOMAIN.md, CONTEXT.md) and none of them is canonical.
  • RFCs: proposals under discussion, written before a decision is made. An RFC argues for a change; once the change is accepted, an ADR records the outcome. RFC, then discussion, then ADR is a common pipeline. Teams keep RFCs in docs/rfc/.
  • Onboarding docs, design docs, runbooks: same tier, same property, read only when something points to them.

3.1. Make the agent find them

Because nothing auto-loads a convention file, the job is to bridge it into the auto-loaded tier deliberately. There are two ways to do it, and both are cheap.

The first is a pointer. One line in CLAUDE.md or AGENTS.md: “Domain terms are defined in GLOSSARY.md. Architecture decisions live in docs/adr/; consult the relevant ones before proposing structural changes.” Now the agent knows the files exist and when to consult them, without their full text occupying context on every session.

The second is an import. As section 2 noted, CLAUDE.md supports @path imports, so @docs/architecture.md splices the file in directly. Use this for something small and stable that you genuinely want in context every time.

Choosing between them is a token-budget decision. An import pays the file’s full cost on every session. A pointer pays one sentence and lets the agent spend the rest only when it decides the task needs it. A 600-line ADR log should always be a pointer. A 15-line “here is the shape of the repo” might be worth an import. When in doubt, point rather than import.

This is the one habit that makes the whole convention layer work. The files can be excellent, and they remain inert until something connects them to the agent.

4. Architecture Decision Records

An Architecture Decision Record (ADR) is a short document that captures a single significant decision: what was decided, why, and what it commits the project to. ADRs are the most established practice in the convention layer. Say “ADR” to most engineers and they know what you mean, which is not true of any glossary filename. The format traces back to Michael Nygard’s 2011 post Documenting Architecture Decisions, and tooling has grown around it since.

4.1. The shape of an ADR

An ADR is small, a screen or two, and follows a fixed skeleton. Nygard’s original five fields still hold up:

  • Title: numbered and specific. “ADR-0007: Keep the repository layer thin.”
  • Status: proposed, accepted, deprecated, or superseded.
  • Context: the situation and the forces in play. What made this a real decision rather than an obvious default.
  • Decision: what was chosen, stated plainly.
  • Consequences: what the decision buys and what it costs. Both halves. The cost half is the part that turns out most useful later.

A complete ADR looks like this:

# ADR-0007: Keep the repository layer thin

## Status
Accepted

## Context
Several services have grown business logic inside their repository
classes. This makes the logic hard to test without a real database
and hard to locate. We weighed moving all logic into the repositories
(rich repositories) against keeping repositories as thin data-access
adapters.

## Decision
Repositories stay thin: query construction and row mapping only.
Business logic lives in the service layer.

## Consequences
Services become testable with an in-memory repository fake. The cost:
some simple operations now touch two files instead of one, and a
developer has to know the convention to place new logic correctly.

4.2. ADRs are immutable

The rule that makes ADRs work, and the one newcomers break first, is that you do not edit an accepted ADR when the decision later changes. You write a new ADR that supersedes it, and you flip the old one’s status to superseded by ADR-0019.

The point of the log is the decision history. Editing an ADR in place destroys precisely the thing the log exists to preserve: the record that at one time, for stated reasons, the team believed something different. A superseded ADR is not dead weight. It still explains why the codebase looked the way it did last year, which is exactly the question a confused reader is asking. This is why ADRs are numbered, append-only, and kept in their own directory, conventionally docs/adr/ or docs/decisions/.

4.3. Why an agent needs them

For an AI agent, the ADR log is a guardrail against one specific failure: confidently re-proposing something the team already decided against.

An agent reviewing a codebase from scratch sees a thin repository layer and, with no other information, suggests “consolidate the data-access and business logic; this layer barely does anything.” In a vacuum that is a reasonable observation. ADR-0007 is what turns it from reasonable into wrong, because the thinness is not an accident. It is a decision, with a recorded cost the team accepted on purpose.

Point the agent at docs/adr/ from CLAUDE.md and the calculus changes. Before proposing a structural change, the agent reads the relevant ADR and either drops the suggestion or argues explicitly that the recorded reasoning no longer holds. The second outcome is far more valuable to receive than a naive proposal, because it engages with the actual history. The ADR does not forbid the change. It raises the bar for suggesting it from “this looks redundant” to “here is why the original justification has expired.”

The life of an ADRRFCstatus: proposeddiscussionADR-0007status: acceptedlater: superseded by 0019ADR-0019status: acceptedWhen the decision changes, write a new ADR. Never edit the accepted one.debateddecisionrecordedThe life of an ADRRFCstatus: proposeddiscussionADR-0007status: acceptedlater: superseded by 0019ADR-0019status: acceptedWhen the decision changes, write a new ADR. Never edit the accepted one.debateddecisionrecorded

5. Domain Glossaries and the Ubiquitous Language

A domain glossary defines the real-world nouns your software is built around, precisely, in one place. It is the convention-layer file with the least standardization, no agreed filename, no format, no tooling, and the most upside in exactly the situation where a project’s vocabulary is genuinely ambiguous.

The idea predates AI agents. It comes from Domain-Driven Design, where Eric Evans calls it the ubiquitous language: a vocabulary shared word-for-word by the domain experts, the developers, and the code itself, so that “Order” means one specific thing in a meeting, in the glossary, and in the class named Order. A glossary file is simply where that shared language is written down.

5.1. What goes in

A glossary entry defines a term and, more importantly, fences it off from the terms it gets confused with. Definitions on their own are cheap. The distinctions and the invariants are the value.

## Order
A customer's confirmed intent to buy, created only after payment
authorization succeeds. Distinct from a Cart, which is mutable and
exists before payment. An Order's line items never change after
creation; corrections happen through a separate Adjustment.

## Fulfillment
The process of getting an Order's goods to the customer. One Order
has exactly one Fulfillment. A Fulfillment may span several Shipments,
because a back-ordered item ships on its own.

Look at what those entries do beyond defining. They say “distinct from a Cart” and “may span several Shipments,” heading off the two confusions that term will actually cause. They state an invariant, “line items never change after creation,” which is the kind of fact an agent or a new hire cannot infer from the code, because code shows what currently is, not what must always remain true.

Do not glossary every noun. A glossary of fifty obvious terms is noise that buries the five that matter. Restrict it to terms that carry an invariant, terms a newcomer reliably gets wrong, and terms where two parts of the business genuinely disagree about meaning.

5.2. When it’s worth writing

A glossary earns its maintenance cost only past a certain ambiguity threshold. For a small codebase with self-evident vocabulary, skip it. The code is the glossary. Write one when you start seeing the symptoms: the same word meaning two things in two meetings, code-review comments that are really vocabulary arguments in disguise, a new hire shipping a bug because they assumed “Invoice” meant what it meant at their previous company.

The honest failure mode is worth stating plainly. A glossary, like any convention file, rots. A definition that has quietly gone false is worse than no definition, because a reader, human or agent, trusts it. If you write a glossary, it has to be something the team actually corrects when reality moves. A glossary nobody maintains should be deleted, not left in place to mislead.

6. Monorepos: Scoping Files to What They Own

In a monorepo the question stops being “do we have an ARCHITECTURE.md” and becomes “at which level does each file belong.” The principle that answers it: put every document at the narrowest scope that fully owns what it describes. A decision affecting one package belongs to that package. A decision affecting the whole repository belongs at the root.

A layout that follows the principle:

repo/
├── AGENTS.md                      # repo-wide agent instructions
├── ARCHITECTURE.md                # how the packages fit together
├── docs/adr/                      # repo-wide decisions: build system,
│                                  # module boundaries, shared tooling
├── packages/
│   ├── billing/
│   │   ├── CLAUDE.md              # facts true only inside billing
│   │   ├── GLOSSARY.md            # billing's domain vocabulary
│   │   └── docs/adr/              # decisions local to billing
│   ├── fulfillment/
│   │   ├── CLAUDE.md
│   │   ├── GLOSSARY.md
│   │   └── docs/adr/
│   └── shared/

6.1. Glossaries belong to bounded contexts

The reason glossaries go per-package rather than at the root is the most important idea in this section: in a monorepo, the same word legitimately means different things in different packages.

“Order” in billing is a financial object with a total, a tax breakdown, and a currency. “Order” in fulfillment is a list of physical items with weights and a shipping address. They are not the same noun. A single root glossary forces a false reconciliation: one entry vague enough to satisfy both packages, and therefore useful to neither.

Domain-Driven Design has a name for the boundary at play. Each package is roughly a bounded context, and a term’s definition is only valid inside its context. So packages/billing/GLOSSARY.md defines billing’s “Order,” packages/fulfillment/GLOSSARY.md defines fulfillment’s, and a root glossary, if it exists at all, holds only the genuinely repo-wide terms that mean exactly one thing everywhere, like “Tenant” or “Environment.”

6.2. ADRs split by blast radius

ADRs scope by how far the decision reaches. “We use a single shared build configuration across all packages” affects everyone, so it is a root ADR in docs/adr/. “Billing models refunds as negative-amount transactions rather than a separate Refund entity” affects only billing, so it lives in packages/billing/docs/adr/. Filing it locally keeps the record next to the code it governs and spares every other team from scrolling past a decision that does not concern them.

The placement test is one question: who would be wrong-footed if they did not know this? If the answer is “anyone in the repository,” the ADR is root-level. If it is “anyone touching this package,” it is package-local.

6.3. Deliberate use of nested CLAUDE.md

This is the directory-scope CLAUDE.md from section 2, used on purpose. A packages/billing/CLAUDE.md loads when the agent works inside packages/billing/, and it should carry exactly the facts true there and nowhere else: billing’s test command if it differs from the repo default, billing’s local conventions and pointers to billing’s own glossary and ADR directory.

The division of labor is clean. The root AGENTS.md stays general. The package-level files stay specific. The agent receives the right context for wherever it currently is, instead of the union of every package’s quirks for every task. In a large monorepo that difference is the gap between an instruction file the agent can use and one so broad it is closer to noise.

7. What to Actually Adopt

Do not adopt all of this. The correct number of convention files for a brand-new repository is close to zero, and the correct way to acquire each one is to let a specific, recurring pain pull it into existence. Every file in this guide is a standing maintenance cost. The failure mode of the convention layer is not an empty directory. It is a directory of confidently wrong documents that nobody updates.

A realistic progression looks like this:

  • Day one: a real README.md and one agent instruction file, AGENTS.md or CLAUDE.md, with the other one pointing at it. That is enough.
  • When the codebase grows past the point where a newcomer can find their way unaided: add ARCHITECTURE.md. One page, the map, nothing more.
  • When you have explained the same past decision for the third time, or watched someone nearly undo it: start docs/adr/, and write that decision down as ADR-0001.
  • When a single word has caused two real misunderstandings: start a glossary, with those terms and no others.
  • In a monorepo: apply section 6’s scoping only to the files you have already decided you need. Do not pre-create per-package glossaries for packages whose vocabulary has not diverged yet.

The test underneath every line above: a convention file is worth creating once the cost of not having it has already been paid at least once, visibly. Before that point it is speculative documentation, and speculative documentation is the kind most likely to be wrong, because nothing has yet tested it against reality.

And whatever you do create, connect it. A glossary the agent is never pointed at, an ADR log no instruction file mentions: that is work done and then hidden. The two-question framing from the start of this guide cuts both ways. Knowing a file is not auto-loaded is not a limitation to lament. It is the instruction to do the one extra thing, a single pointer line in CLAUDE.md, that turns an inert file into one the agent actually uses.