Rules files: AGENTS.md as the source of truth
Lesson 1 showed that a rule typed in chat fades. This lesson moves your rules into one short file that every tool loads on its own, and commits it before any feature code.
- AGENTS.md is one rules file that Claude Code, Codex and Antigravity all read.
- Keep it an index of about 40 lines. Detail goes in linked files that are opened only when a task needs them.
- Commit it first, then check a fresh session loaded it. A stray
CLAUDE.local.mdcan switch it off without a warning.
You ran /clear in a long session. Which rule is still in force?
/clear empties the conversation, summaries included. Files on disk load again. A clicked approval is a permission setting, not an instruction the agent reads.
Same task, but the context window is nearly full. What do you run?
/compact <focus> keeps you in the task and tells the summary what matters. /clear starts a new chat, for unrelated work, and /context only shows what fills the window.
What happened
Failed run
A developer kept nine prompt templates and pasted them into the chat by hand. They still named the wrong project and stack: ExamDomain and Angular.
Writing rules down early was right. Relying on a human to paste them was the weak link.
A real session, anonymised
With the harness
The rules live in one file in the repo that the tool loads by itself at the start of every session. Nobody pastes anything.
A wrong line is fixed once, in git, and every new session gets the fix.
Same idea as the templates, made automatic and reviewable.
One file, every tool
AGENTS.md is "a README for agents": a plain Markdown file of instructions for coding agents, an open format stewarded by the Agentic AI Foundation under the Linux Foundation (agents.md). Codex and Antigravity read it. Claude Code reads it natively too v2.1.277+ · verified 2026-10-01, but only when no CLAUDE file is in the way (Memory: AGENTS.md):
The check covers your working folder and every folder above it; your personal ~/.claude/CLAUDE.md and .claude/rules/ don't count. How to tell it loaded: An interactive session prints a line such as no CLAUDE.md found; AGENTS.md loaded: /home/you/projects/stockapi/AGENTS.md, and /memory lists the file's path. No line? Look for a CLAUDE file. To keep a CLAUDE.local.md and still read AGENTS.md, open /config and set Project instructions to claude-md-and-agents-md.
Going deeper: older versions and the @AGENTS.md fallback
Some sessions read CLAUDE.md only: versions before v2.1.277, sometimes the first session after upgrading to it, and before v2.1.281 some Amazon Bedrock or telemetry-off sessions. There, add a CLAUDE.md that imports AGENTS.md. An import (@path) pastes another file's text in at launch:
@AGENTS.md ## Claude Code Use plan mode for changes under `src/StockApi/Services/`.
"Keeping the import never makes Claude read AGENTS.md twice." Don't write "read AGENTS.md" in words instead: Claude then sees it only if it decides to open it (Memory: earlier workarounds). Claude Code never reads AGENTS.local.md, AGENTS.override.md or .agents/.
Keep it an index
The root file loads into every session, so every line costs context on every task. Anthropic's guidance: "target under 200 lines per CLAUDE.md file. Longer files consume more context and reduce adherence" (Memory). Aim much lower. Your root file holds what every task needs, one line per rule, and points to the rest:
Here is the shape for the practice repo. Your wording will differ; the sections shouldn't. Link only files that exist: a dead link sends the agent hunting.
# StockApi: agent instructions A small inventory API: products with a unique SKU and a stock count; orders reserve stock. .NET 8, ASP.NET Core controllers, EF Core + SQLite, xUnit. Brief: `BRIEF.md`. ## Commands - Build: `dotnet build -warnaserror` - Test: `dotnet test` - Format check: `dotnet format --verify-no-changes` - Run: `dotnet run --project src/StockApi` ## Definition of done (every slice) 1. Format check, build and test all pass. 2. The human has read and approved the commit message. Never commit or push otherwise. ## Invariants (never break; the why is in docs/ai/invariants.md) - Stock never goes below zero, including under concurrent orders. - Every stock change is ONE guarded UPDATE (`ExecuteUpdateAsync` with `Stock >= n` in the WHERE); never read-then-write. - Target framework is `net8.0`. Do not change it. - Out of scope: auth, UI, payments. Decline and say so if asked to add them. - No secrets in git: config comes from environment variables; `.env` is git-ignored. ## Workflow Plan first, then one slice at a time: docs/ai/workflow.md ## More context (read when relevant) - Invariants and why: docs/ai/invariants.md - How to test (real SQLite, never EF InMemory): docs/ai/testing.md
Two limits. Imports don't shrink anything: imported files "also load at launch", so link detail files as plain paths, without @. And the file is advice, not a lock: Claude treats it "as context, not enforced configuration". It makes the right behaviour the default; lesson 7's guards are what block.
Going deeper: nested files, path-scoped rules, other tools
A nested AGENTS.md, such as tests/AGENTS.md, loads when Claude reads a file in that folder, deeper subfolders included (we checked: reading tests/StockApi.Tests/ApiTests.cs loaded it, v2.1.286).
Claude Code also has .claude/rules/*.md. A rule whose frontmatter (the YAML block between --- lines at the top) lists paths: globs (filename patterns; ** means any folders) loads only when Claude reads a matching file. Without paths: it always loads (Path-specific rules).
--- paths: - "src/**/Data/**/*.cs" - "src/**/Services/**/*.cs" --- # EF Core rules (Claude Code only) - `DateTimeOffset` columns need `DateTimeOffsetToBinaryConverter` on SQLite before any ORDER BY.
| Claude Code | Codex | Antigravity | |
|---|---|---|---|
| Root rules | AGENTS.md when no CLAUDE file exists | AGENTS.md from ~/.codex, then every folder from the git root down to where you start it, nearer files later | AGENTS.md or GEMINI.md, always on |
| Scoped rules | Nested AGENTS.md; .claude/rules/ with paths: | Only via that folder chain | .agents/rules/*.md with a trigger: always_on, model_decision, glob or manual |
| Size | Under 200 lines advised | 32 KiB combined by default | Not in the sources we checked |
Sources: Codex AGENTS.md · Antigravity rules
Where instructions live: the layers
The repo's rules file is one layer of several. Claude Code reads them in order, broadest first, and adds them all together: nothing overrides anything. If two layers disagree, Claude "may pick one arbitrarily", so the job is to keep each fact in exactly one layer (Memory: where to put CLAUDE.md files verified 2026-10-01).
- Organisation
/etc/claude-code/CLAUDE.md(Linux/WSL; macOS and Windows have their own path)Set by IT for every repo on the machine. You can't exclude it. - You, everywhere
~/.claude/CLAUDE.mdand~/.claude/rules/Your personal style and habits, in every project. Never project facts: your teammates don't have this file. - The project
AGENTS.mdat the repo root (orCLAUDE.md,.claude/CLAUDE.md)The team's rules, committed. Files in folders above where you start also load. - You, this project
CLAUDE.local.md, kept out of gitYour sandbox URL, your test data; add it to.gitignore. Careful: it switches AGENTS.md off (step 3 of the diagram above). - A folder or module
src/Orders/AGENTS.md,tests/AGENTS.mdLoads only when Claude reads a file in that folder, so other tasks don't pay for it. - A file type
.claude/rules/efcore.mdwithpaths:Loads only when Claude reads a matching file.
| The fact | Layer |
|---|---|
| "I prefer early returns", in every repo you touch | You, everywhere |
| Build and test commands; the stock invariant | The project |
| "Order tests use real SQLite, never InMemory" | A folder (tests/AGENTS.md) |
| "DateTimeOffset needs a converter on SQLite" | A file type (.claude/rules/) |
| Your local connection string | Nowhere in a rules file: .env, read by the app |
| A multi-step procedure, like committing | Not a layer at all: a skill (lesson 5), loaded when used |
Three habits keep the layers useful. Check what loaded with /context, under Memory files; on Claude Code v2.1.283 or later, /doctor prompt-audit looks for contradictions across them. In a monorepo, start Claude in your module's folder: the folders above still load, the neighbours' don't, and a claudeMdExcludes list in your .claude/settings.local.json skips another team's file. Keep each file short: the docs suggest under 200 lines, because "longer files consume more context and reduce adherence".
How it talks to you: output styles
Rules files set what the agent knows. An output style sets how it answers: its length, tone and shape, for every reply in the session. It is a separate setting, not a layer of CLAUDE.md, and it stays on whichever rules load (Output styles verified 2026-10-13). Claude Code has four built in besides Default, and two of them are made for learning.
| Style | What changes | Good for |
|---|---|---|
| Explanatory | Same work as Default, plus short ★ Insight blocks in the chat explaining why it chose what it did | This course, or any codebase you are getting to know |
| Learning | The Insight blocks, and at a real design decision it leaves a TODO(human) in the file, tells you what to write, and waits for you | Hands-on practice: you write the interesting part, it does the routine part |
| Concise | Leads with the result; no preamble, narration or recap | Once you know the ground and want short answers |
| Proactive | Starts work at once and assumes routine choices instead of asking | Well-understood work you will correct as it goes |
See the difference on one task. Same repo, same rules, same work done; only the style changes. Click through them.
> Make sure an order can never take stock below zero.
I changed OrderService.PlaceAsync to use a guarded UPDATE: ExecuteUpdateAsync with Where(p => p.Stock >= qty). When no row is updated, the endpoint returns 409 Conflict. I added PlaceOrder_InsufficientStock_Returns409 to ApiTests.cs. All tests pass.
I changed OrderService.PlaceAsync to use a guarded UPDATE: ExecuteUpdateAsync with Where(p => p.Stock >= qty). When no row is updated, the endpoint returns 409 Conflict. I added PlaceOrder_InsufficientStock_Returns409. All tests pass.
- Reading the stock and then writing it can race: two orders both read 3 and both succeed. Putting the check inside the UPDATE lets the database decide in one statement.
- 409, not 400: the request itself was valid. What conflicts is the current stock.
I wired the endpoint and wrote the failing test. One decision is yours.
Context: PlaceAsync now runs the guarded UPDATE and gets back how many rows changed.
Your task: in OrderService.cs, at TODO(human), decide what happens when no row changed.
Guidance: an exception, a result type or a status code? The test expects the caller to see 409.
I'll wait. Tell me when you're done.
Done: guarded UPDATE in OrderService.cs, 409 on short stock, test PlaceOrder_InsufficientStock_Returns409 added. All tests pass.
I fixed it. Now an order can't take more than is on the shelf.
What I did: the database checks the stock and takes it away in one step. So two orders can't both grab the last one.
Did it work? Yes. All tests pass.
What you do now: nothing. Run dotnet test if you want to see it.
Switch with /output-style explanatory (with no argument it lists the styles), or /config → Output style. Either one saves "outputStyle" to .claude/settings.local.json, so it is yours and not the team's; put it in ~/.claude/settings.json to make it your default everywhere. In a settings file the name is case-sensitive: explanatory silently gives you Default. The new style starts with your next message.
Two limits. A style is an instruction, not a guarantee: anything that must happen every time is a hook (lesson 7). And it shapes the main conversation only; reviewer subagents (lesson 6) keep their own prompts. Explanatory and Learning also write longer replies, so they use more tokens.
- Doing this course? Turn on
/output-style explanatory. Every change comes with a few lines on why: why this test, why this file, why this order. That is most of what you are here to learn. - Want it to stick? Switch to
/output-style learningfor an exercise. The agent builds the routine parts and leaves the real decisions (the stock check, the error handling) to you, then comments on what you wrote. - Tired, or just want the answer? Write your own. A five-line "ELI5" style (explain like I'm five) is a common one:
--- name: ELI5 description: keep it simple keep-coding-instructions: true --- Small words, short sentences. Explain any big word right after you use it. Tell me what you did, whether it worked, and what I do now. If I must decide: two options at most, and which one you'd pick. Keep paths and commands exact.
Save it as~/.claude/output-styles/ELI5.md, restart Claude Code, then/output-style ELI5. - Know the ground already?
/output-style concisefor short answers. Switch back to Explanatory when you hit something new; it takes effect on your next message.
Going deeper: your own style
A custom style is one Markdown file in ~/.claude/output-styles/ (yours) or .claude/output-styles/ (the project's). Keep keep-coding-instructions: true when you only want to change how it talks; without it, Claude Code drops its built-in software engineering instructions.
--- name: Explain like a .NET dev description: Relate new ideas to C# I already know keep-coding-instructions: true --- When you explain a concept, compare it to the closest C# or .NET equivalent first, then say where the analogy breaks.
Claude Code reads style files when it starts, so restart it after you create or edit one.
Commit it first
Lesson 1's developer wrote CLAUDE.md at the end of a 356-turn session, so it steered nothing. Order matters twice. Steering: a rule that exists at the first prompt shapes the first slice; one written at the end only describes it. Proof: git records when the file first appeared:
git log --diff-filter=A --format='%h %ad %s' --date=iso -- AGENTS.md
That proves order only if the file arrives alone. In another session the plan landed in the same commit as the whole API, every box ticked, so nothing shows it came first.
Check yourself
Your repo relies on AGENTS.md. You add CLAUDE.local.md with private notes. What does Claude Code read by default?
CLAUDE.local.md counts as a CLAUDE file, so AGENTS.md is skipped, with no error. Delete the file, or set Project instructions to claude-md-and-agents-md in /config.
Your AGENTS.md has grown to 300 lines. Which change cuts what loads into every session?
Imported files load at launch too, so imports only reorganise. Path-scoped rules and nested AGENTS.md files load when Claude reads a matching file, so unrelated tasks don't pay for them.
How can a reviewer tell your rules steered the work, rather than being written afterwards?
Length and the agent's own summary are claims. Commit order is evidence: git log --diff-filter=A shows when the file first appeared, and whether it arrived alone.
Practice first
Quick drills with instant feedback on writing and placing rules, before you touch the repo.
A developer's first draft of the root AGENTS.md. Click the lines that don't belong in it as written.
- # StockApi: agent instructions
- - Test: `dotnet test`
- - Write clean, high-quality code and follow best practices.
- - Stock never goes below zero, including under concurrent orders.
- - Target framework is `net8.0`. Do not change it.
- - DateTimeOffset columns need DateTimeOffsetToBinaryConverter on SQLite.
- - @docs/ai/invariants.md
- - How to test (real SQLite, never EF InMemory): docs/ai/testing.md
What would you do?
A teammate ran /init, and it wrote a CLAUDE.md into a repo that already relies on AGENTS.md. What do you do with the new file?
A CLAUDE file switches AGENTS.md off. The @AGENTS.md import pulls it back in, read once. Written as words, Claude sees it only if it decides to open it.
You keep telling the agent "never weaken a test to make it pass". It only matters when it works on test files. Where does that rule go?
A nested AGENTS.md loads when Claude reads a file in that folder, so tasks that never touch tests don't pay for it. CLAUDE.local.md would switch AGENTS.md off.
A fresh Claude Code session doesn't print the "AGENTS.md loaded" line, and /memory doesn't list the file. What do you check first?
Any CLAUDE.md, .claude/CLAUDE.md or CLAUDE.local.md in the folder or above switches AGENTS.md off, with no error. Claude Code never reads AGENTS.local.md.
You want every agent session, in every repo you work on, to explain its plan in plain English before it edits anything. Your teammates don't need this. Where does it go?
It's yours and it applies everywhere: the user layer. In AGENTS.md it would bind the whole team, CLAUDE.local.md would switch AGENTS.md off, and copying it into every repo means it drifts.
You are new to a codebase. You want the agent to explain its choices as it works, and to leave the real design decisions for you to write. What do you switch on?
Learning adds Insight blocks and leaves a TODO(human) at each real decision, then waits. In AGENTS.md it would bind the whole team, plan mode only holds back edits, and a subagent's prompt never shapes the main chat.
Exercise: write the index and commit it first
Work in your practice repo from lesson 1 (~/projects/stockapi), before any feature code. Windows: use WSL2 or Git Bash (setup).
- Run
claude --version. You need 2.1.277 or later; on anything older, update Claude Code or use the@AGENTS.mdfallback above. - Write
AGENTS.mdin the repo root by hand, about 30 lines, following the shape above. Nothing enforces "the human approves the commit" yet; you do. - Create the detail files, starting from the course's reference solution:
mkdir -p docs/ai cp ../agent-harness-course/reference-solution/docs/ai/invariants.md docs/ai/ cp ../agent-harness-course/reference-solution/docs/ai/testing.md docs/ai/ printf '# Workflow\n\nPlan first, then one slice at a time.\n' > docs/ai/workflow.md grep -nE 'ConcurrencyTests|decisions/|1-3-1' docs/ai/*.md
Thegrepfinds lines about things your repo doesn't have yet (a concurrency test, decision records, lesson 3's 1-3-1). Delete or reword them, then read both files end to end. - Write
tests/AGENTS.md: three or four test-only rules, such as "real SQLite, never EF InMemory", "never weaken a test to make it pass", and a last line pointing to../docs/ai/testing.md. - Keep scratch files, secrets and your private notes out of git:
printf 'tmp/\n.env\nCLAUDE.local.md\n' >> .gitignore, thentail -3 .gitignoreto check. - Commit, naming the paths so nothing else sneaks in:
git add AGENTS.md docs tests/AGENTS.md .gitignore git commit -m "chore(harness): add agent rules" git show --stat HEAD
Expect six files:AGENTS.md,.gitignore, three underdocs/ai/andtests/AGENTS.md. - Verify in a fresh session. Start
claudein the repo root and look for theAGENTS.md loadedline. Ask: "Without opening any files: what are this repo's invariants, and which file did your project instructions come from?" Then run/memoryand find the AGENTS.md path. (On your first session after an upgrade it may not load yet; start one more.) - Nested file. Ask: "Open tests/StockApi.Tests/ApiTests.cs. Did another instruction file load when you read it? Name it." It should name
tests/AGENTS.md. - The gotcha. Run
echo "Prefer tabs." > CLAUDE.local.md, leave with/exit, startclaudeagain and repeat step 7's question. The loaded line is gone, and the answer comes fromCLAUDE.local.md. Runrm CLAUDE.local.md, start one more session and confirm AGENTS.md is back.
Codex: tests/AGENTS.md applies only if you start codex inside tests/, so skip steps 8 and 9. Antigravity: AGENTS.md is always on; for test-only rules use .agents/rules/tests.md with trigger: glob (Antigravity rules); skip step 9.
-
git log --onelineshowschore(harness): add agent rulesdirectly after the starter commit -
wc -l AGENTS.mdis under 60, and it linksdocs/ai/*.mdas plain paths, without@ - A fresh session named
AGENTS.mdwithout opening files, and/memorylists it - You watched
CLAUDE.local.mdswitch AGENTS.md off, deleted it, and saw it load again
- /init: Built in: drafts a rules file from your repo. It writes CLAUDE.md, which switches AGENTS.md off, so make its first line
@AGENTS.mdor fold the draft into AGENTS.md. - claude-md-management: Anthropic's plugin for auditing and keeping a rules file tidy.
- csharp-lsp: C# code intelligence for the agent (go to definition, references).
- Output styles: Built in: try
/output-style explanatorywhile you do this course, orlearningwhen you want to write the key parts yourself.
Each link has its own install steps. Install only what you will use, check how recent it is, and read what a plugin adds before you enable it. Links checked 2026-10-01.
Every time you correct the agent in chat, ask: should this be a line in AGENTS.md? If so, add it now, not at the end of the work.
The AGENTS.md section of Anthropic's Memory docs, including "My AGENTS.md isn't loading", then agents.md. Quick lookup: the AGENTS.md reference sheet.
"Without opening any files: which instruction files did you load this session, from which paths, and which line in my AGENTS.md would you cut first?"