Stage 2 of 11

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.

12 min read 30 min exercise Plaque: Rule Writer
In 30 seconds
Recall from lesson 1

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):

1AGENTS.md aloneyour repo after this lessonstockapi/✓AGENTS.mdReads AGENTS.md 2A CLAUDE.md appearscommitted by anyonestockapi/✓CLAUDE.md✕AGENTS.mdReads CLAUDE.md only 3Add CLAUDE.local.mdprivate notes, not committedstockapi/✓CLAUDE.local.md✕AGENTS.mdAGENTS.md skipped,no warning 4CLAUDE.md imports itfirst line: @AGENTS.mdstockapi/✓CLAUDE.md✓AGENTS.mdvia @importReads both,AGENTS.md once

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:

CLAUDE.md (only as a fallback)
@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:

AGENTS.mdabout 30 lines## Commands## Definition of done## Invariants→ docs/ai/invariants.md## Workflow→ docs/ai/workflow.md## More context→ docs/ai/testing.md loads at the start of every session after compaction: re-read from disk docs/ai/invariants.mdthe why behind each invariant docs/ai/workflow.mdplan first, one slice at a time docs/ai/testing.mdreal SQLite, never EF InMemory tests/AGENTS.mdloads when Claude reads a test file its last line points here

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.

AGENTS.md (the shape you write in the exercise)
# 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).

.claude/rules/efcore.md (from the course's reference solution)
---
paths:
  - "src/**/Data/**/*.cs"
  - "src/**/Services/**/*.cs"
---
# EF Core rules (Claude Code only)
- `DateTimeOffset` columns need `DateTimeOffsetToBinaryConverter` on SQLite before any ORDER BY.
Claude CodeCodexAntigravity
Root rulesAGENTS.md when no CLAUDE file existsAGENTS.md from ~/.codex, then every folder from the git root down to where you start it, nearer files laterAGENTS.md or GEMINI.md, always on
Scoped rulesNested AGENTS.md; .claude/rules/ with paths:Only via that folder chain.agents/rules/*.md with a trigger: always_on, model_decision, glob or manual
SizeUnder 200 lines advised32 KiB combined by defaultNot 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).

  1. 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.
  2. You, everywhere~/.claude/CLAUDE.md and ~/.claude/rules/Your personal style and habits, in every project. Never project facts: your teammates don't have this file.
  3. The projectAGENTS.md at the repo root (or CLAUDE.md, .claude/CLAUDE.md)The team's rules, committed. Files in folders above where you start also load.
  4. You, this projectCLAUDE.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).
  5. A folder or modulesrc/Orders/AGENTS.md, tests/AGENTS.mdLoads only when Claude reads a file in that folder, so other tasks don't pay for it.
  6. A file type.claude/rules/efcore.md with paths:Loads only when Claude reads a matching file.
The factLayer
"I prefer early returns", in every repo you touchYou, everywhere
Build and test commands; the stock invariantThe 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 stringNowhere in a rules file: .env, read by the app
A multi-step procedure, like committingNot 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.

StyleWhat changesGood for
ExplanatorySame work as Default, plus short ★ Insight blocks in the chat explaining why it chose what it didThis course, or any codebase you are getting to know
LearningThe Insight blocks, and at a real design decision it leaves a TODO(human) in the file, tells you what to write, and waits for youHands-on practice: you write the interesting part, it does the routine part
ConciseLeads with the result; no preamble, narration or recapOnce you know the ground and want short answers
ProactiveStarts work at once and assumes routine choices instead of askingWell-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.

Same task, five styles

> 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.

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.

Tip: pick a style for the job
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.

  1. # StockApi: agent instructions
  2. - Test: `dotnet test`
  3. - Write clean, high-quality code and follow best practices.
  4. - Stock never goes below zero, including under concurrent orders.
  5. - Target framework is `net8.0`. Do not change it.
  6. - DateTimeOffset columns need DateTimeOffsetToBinaryConverter on SQLite.
  7. - @docs/ai/invariants.md
  8. - 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

Exercise · about 30 min · you add: AGENTS.md, docs/ai/ (3 files), tests/AGENTS.md

Work in your practice repo from lesson 1 (~/projects/stockapi), before any feature code. Windows: use WSL2 or Git Bash (setup).

  1. Run claude --version. You need 2.1.277 or later; on anything older, update Claude Code or use the @AGENTS.md fallback above.
  2. Write AGENTS.md in the repo root by hand, about 30 lines, following the shape above. Nothing enforces "the human approves the commit" yet; you do.
  3. 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
    The grep finds 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.
  4. 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.
  5. Keep scratch files, secrets and your private notes out of git: printf 'tmp/\n.env\nCLAUDE.local.md\n' >> .gitignore, then tail -3 .gitignore to check.
  6. 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 under docs/ai/ and tests/AGENTS.md.
  7. Verify in a fresh session. Start claude in the repo root and look for the AGENTS.md loaded line. Ask: "Without opening any files: what are this repo's invariants, and which file did your project instructions come from?" Then run /memory and find the AGENTS.md path. (On your first session after an upgrade it may not load yet; start one more.)
  8. 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.
  9. The gotcha. Run echo "Prefer tabs." > CLAUDE.local.md, leave with /exit, start claude again and repeat step 7's question. The loaded line is gone, and the answer comes from CLAUDE.local.md. Run rm 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.

Done when: tick all four to earn the Rule Writer plaque
Worth installing or reading

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.

Harness it

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.

Read next

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.

Ask your agent

"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?"