Repository Structure for Agents
A human engineer navigates your repo with tribal knowledge: they know the billing logic is "really" in utils/helpers2.ts because Priya mentioned it in standup last March. An agent has none of that. It navigates by exactly two mechanisms — convention (predictable names in predictable places) and grep (finding strings that match what it's looking for). Every deviation from boring, guessable structure is invisible to you and expensive to the agent: it burns context window on exploration, or worse, doesn't find the thing and reimplements it.
This inverts a common instinct. On human-only teams, structure debt is paid slowly, in onboarding time. With agents, it's paid on every single task, because every session starts with zero memory. Repo structure is now agent-performance tuning.
The recommended layout
Not every repo needs all of this. But every fact should have exactly one home, and these are the homes agents (and Claude Code specifically) look for first:
.
├── CLAUDE.md # Agent instructions — Claude Code loads at session start
├── AGENTS.md # Cross-tool equivalent (or symlink/pointer to CLAUDE.md)
├── README.md # Human entry point: what this is, how to run it
├── TASKS.md # Work state: in-progress, blocked, next — survives sessions
├── CHANGELOG.md # What shipped, when, and why it mattered
├── docs/
│ ├── ARCHITECTURE.md # System shape, boundaries, invariants
│ ├── specs/ # Product/feature specs — one file per feature
│ │ ├── billing-proration.md
│ │ └── csv-export.md
│ ├── decisions/ # ADRs — numbered, immutable once accepted
│ │ ├── 0001-postgres-over-dynamo.md
│ │ └── 0002-no-orm-in-payment-path.md
│ └── runbooks/ # Operational procedures (deploy, rollback, oncall)
├── packages/ # (monorepo) each package gets its own CLAUDE.md
│ └── billing/
│ ├── CLAUDE.md # Package-specific rules only — inherits root
│ └── src/
├── src/
├── tests/
└── .claude/
├── commands/ # Slash commands — repeatable workflows as prompts
└── settings.json # Hooks, permissions, tool configThree placements matter more than the rest:
docs/specs/— specs live in the repo, not in Notion/Confluence. The agent can readdocs/specs/billing-proration.md; it cannot read your wiki. See Keeping Specs Alive.docs/decisions/— ADRs stop agents (and new humans) from relitigating settled questions. "Why not just use an ORM here?" is answered by0002-no-orm-in-payment-path.md, not by you, again, in chat. See Decision Records.CLAUDE.mdat root + per-package — the root file carries repo-wide rules; package files carry only the delta ("this package targets Node 18, not 22; tests use vitest, not jest"). Claude Code merges them by directory. Full treatment in CLAUDE.md and AGENTS.md.
The repository as shared memory
Each document exists because a specific agent activity reads it. If a doc has no consuming activity, it's decoration; if an activity has no feeding doc, the agent invents the missing input.
The two return edges are the ones teams forget: coding updates TASKS.md, and review findings that repeat become CLAUDE.md rules. Memory that only flows outward goes stale. Docs as Memory covers the maintenance loop.
Principles
One obvious place per fact. If deploy steps live in README.md, docs/runbooks/deploy.md, and a wiki page, the agent will read one of them — a coin flip weighted toward the stale one. Pick a home, and make the other locations one-line pointers.
Boring, predictable naming. docs/specs/csv-export.md is findable by an agent that has never seen your repo, because it would guess that path. docs/planning/2024/Q3/exports_v2_final.md is findable only by grep luck. Optimize for guessability: an agent told "implement the CSV export spec" should locate the file without searching.
Colocate contracts with code. The OpenAPI spec lives next to the routes it describes; the schema migration lives next to the model; the package's constraints live in the package's CLAUDE.md. Agents read locally first — when they open packages/billing/, everything governing billing should be reachable within that subtree or explicitly pointed to from it.
Keep files under ~500 lines. Agents read files in chunks. A 3,000-line services/api.ts means the agent reads the first chunk, forms a wrong model of the file, and edits accordingly. Files under ~500 lines fit in a single read and a single coherent mental model. This applies to docs doubly: a 500-line ARCHITECTURE.md gets skimmed; five 80-line focused docs get read.
Structure encodes boundaries. If packages/billing/ must never import from packages/admin/, say so in CLAUDE.md and enforce it with a lint rule — agents respect visible structure and stated rules, but only tooling catches the exceptions. Unenforced boundaries erode a few hundred lines at a time; see Architecture Erosion.
Anti-patterns
| Anti-pattern | What the agent does with it | Fix |
|---|---|---|
utils/, helpers/, misc/ dumping grounds | Can't guess what's inside; reimplements existing functions with new names | Split by domain (lib/retry.ts, lib/currency.ts); index them in CLAUDE.md per Navigability |
| Specs in Notion/Confluence/Google Docs | Never reads them; invents requirements confidently | Move to docs/specs/; wiki keeps a link, repo keeps the truth |
| Same fact in three files, two stale | Reads whichever it finds first; ships behavior matching the 2023 version | One canonical home; other mentions become links |
| 2,000-line "god files" | Partial reads → wrong mental model → edits that break the part it didn't read | Split under ~500 lines along seams the file already has |
Clever/ironic names (ninja.ts, wat.py, TheManager) | Grep for "retry" or "auth" finds nothing; duplicates the logic | Rename to what it does; agents search by literal meaning |
| Dead code left "for reference" | Treats it as a live pattern and imitates it | Delete it; Git is the reference. See Navigability |
Config sprayed across .env, config/, hardcoded constants | Changes one of four places; bug ships | Single config module; CLAUDE.md names it as the only place |
Empty scaffold dirs from a template (middlewares/, interceptors/) | Infers you use those patterns and starts filling them in | Delete unused scaffolding; empty structure is a false signal |
When you can't restructure
Legacy repos rarely earn a big-bang reorganization. The compensating move is documentation: a CLAUDE.md section titled "Where things actually live" that maps concepts to their real, ugly locations ("rate limiting: src/middleware/throttle_v2.js — ignore throttle.js, it's dead"). Ten lines of honest map beat a month of file-moving, and every agent session benefits immediately. Then fix structure opportunistically, one PR at a time, updating the map as you go.
Related
- CLAUDE.md and AGENTS.md — the deep dive on the most-read file in the layout above
- Docs as Memory — maintaining ARCHITECTURE.md, TASKS.md, ADRs so they stay true
- Navigability — naming, capabilities index, and preventing duplicate logic
- Context and Instruction Hierarchy — how these files enter the agent's context and in what precedence
- Repository Doc Templates — copy-paste starters for every file in the tree above
- Architecture Erosion — what happens when structure exists but nothing enforces it