# Writing an AGENTS.md for Python that agents actually follow

> Source: https://learn-python.com/ai/agents-md/
> Part of Learn Python, free to read.

`AGENTS.md` is a plain Markdown file in your repo root that coding agents read before they start work. Claude Code reads `CLAUDE.md`; Codex, Cursor, Aider, Cline and most newer tools read `AGENTS.md`. The contents are the same, so pick one file and symlink:

```bash
printf 'CLAUDE.md\n' >> .gitignore   # or commit the symlink, your call
ln -s AGENTS.md CLAUDE.md
```

The file is not documentation. It is **a standing correction list**: the things a competent Python developer would get wrong on their first week in your repo, because they are specific to your repo rather than to Python.

## The economics nobody mentions

Every token in `AGENTS.md` is prepended to every request for the whole session. A 500-line file is not free advice — it is a permanent tax on the context available for your actual code, and it dilutes the instructions that matter.

Worse, models weight instructions roughly by salience, not by position in a list. Twelve rules get followed. Sixty rules get sampled. If you have written sixty, you have effectively randomised which of your rules apply.

:::verdict The target
**Under 100 lines.** If it is longer, you are writing documentation. Move it to `docs/` and link to it — agents can read a file on demand, and an on-demand read costs nothing when it is not needed.
:::

## What belongs in it

**1. How to run things.** The single highest-value section, because it is unguessable and used every turn.

**2. Conventions a linter cannot enforce.** "Domain logic goes in `src/core/`, never in the route handlers." A model cannot infer this from the code, and `ruff` will not tell it.

**3. Landmines.** "`sync_users()` is called by a cron in production; changing its signature breaks the scheduler repo." This is the highest-value line in most files and almost nobody writes it.

**4. Corrections you have made twice.** If you have told the agent twice not to add `try/except` around everything, that is a line.

## What does not belong in it

**Generic Python advice.** "Use type hints. Follow PEP 8. Write docstrings." The model knows. You are spending context to teach it something it does better than most humans.

**Anything a tool already enforces.** If `ruff` sets your line length, do not also write the line length in prose. You now have two sources of truth and one of them will drift. Configure the tool; mention the tool.

**Long architecture explanations.** Put them in `docs/architecture.md` and write one line: *"Read `docs/architecture.md` before changing anything under `src/billing/`."* Agents follow pointers reliably.

**Aspirations.** "All code should have 100% test coverage." If it is not true today, it is a lie in the context window, and it makes every other line less credible.

:::warn The most common failure
A file that describes the codebase you wish you had. The agent believes it, writes code for that codebase, and you get a diff that does not fit the one you actually have.
:::

## A Python template worth copying

```markdown AGENTS.md
# AGENTS.md

Python 3.12. Package management is `uv` — never `pip` or `poetry`.

## Commands
- Install:      `make install`   (uv sync)
- Test:         `make test`      (pytest -q)
- One test:     `uv run pytest tests/test_x.py::test_name -q`
- Lint + fmt:   `make lint`      (ruff check --fix, ruff format)
- Types:        `make typecheck` (mypy src, strict)
- All of it:    `make check`     <- must pass before you say you are done

## Layout
- `src/core/`      pure domain logic. No I/O, no framework imports, no `requests`.
- `src/adapters/`  everything that touches the outside world: db, http, queues.
- `src/api/`       FastAPI routers. Thin. Parse, call core, serialise. No logic.
- `tests/`         mirrors `src/`. Unit tests are offline; anything needing a
                   database is marked `@pytest.mark.integration`.

## Conventions
- Money is `Decimal`, never `float`. Serialise as a string.
- All datetimes are timezone-aware UTC. `datetime.now(UTC)`, never `utcnow()`.
- Public functions in `src/core/` are fully typed. `Any` needs a comment saying why.
- Errors: raise domain exceptions from `src/core/errors.py`. Do not catch broadly.
- New dependencies need a line in the PR description justifying them.

## Landmines
- `src/adapters/legacy_sync.py` is called by the `ops-scheduler` repo via CLI.
  Do not change its arguments or its exit codes.
- `tests/fixtures/prod_sample.json` is real, anonymised customer data. Do not
  print it in test output and do not paste it into commit messages.
- Alembic migrations are irreversible in production. Write the migration, do not
  run it. A human runs migrations.

## Working style
- Small commits. One behavioural change each.
- When a test is failing, fix the code, not the test — unless the test is
  provably wrong, in which case say so explicitly in the commit message.
- If a change needs more than ~150 lines of diff, stop and describe the plan first.
```

That is 40 lines and it does more than most 400-line files. Notice how much of it is *specific facts about this repository* rather than *opinions about Python*.

:::tip The two-strike rule
Do not add a rule to this file speculatively. Wait until an agent has made the same mistake twice. This keeps the file short, keeps every line evidence-backed, and — usefully — tells you which of your repo's conventions are genuinely non-obvious.
:::

## Nested files for big repos

In a monorepo, a root `AGENTS.md` plus per-package files works better than one large file. Most tools read the nearest one and merge upward.

```text
AGENTS.md                     # commands, global conventions
services/billing/AGENTS.md    # Decimal rules, the Stripe webhook landmine
services/etl/AGENTS.md        # pandas conventions, memory limits
```

The root file should then be almost entirely commands and layout, with the specifics pushed down to where they are relevant.

## How to tell if yours is working

Run the same non-trivial task twice: once with the file, once with it renamed away. If the diffs are meaningfully different, the file is earning its place. If they are the same, you have written 100 lines of ballast.

It is also worth deleting a third of the file periodically and seeing whether anything gets worse. Usually nothing does.

:::promo manning
:::

## Common questions

### AGENTS.md or CLAUDE.md?

Write `AGENTS.md` — it is the convention most tools now converge on — and symlink `CLAUDE.md` to it so Claude Code picks it up too. Maintaining two files by hand guarantees they drift apart within a month.

### Should AGENTS.md be committed to the repo?

Yes. It is a shared artefact: everyone on the team benefits from the same landmine list, and it should be reviewed in pull requests like any other change to how the project is built. Keep personal preferences in your user-level config instead.

### Does a longer file mean better results?

No, and the relationship reverses past a point. Every line competes for attention with every other line, and a long file dilutes the rules you care about most. Under 100 lines is a good target; the specific facts about your repository matter far more than the volume of general advice.

### What if my team uses several different agents?

That is the argument for `AGENTS.md` rather than tool-specific files. Keep one file with the substance, symlink the tool-specific names to it, and put anything genuinely tool-specific — permission allowlists, hooks, model choice — in that tool's own config where it belongs.
