Course · Codex · Rules (AGENTS.md)
Write rules Codex follows instead of rules it ignores
Start from an uncomfortable fact: every line in the AGENTS.md of budgetcli (the course’s running example, a small self-hosted personal-finance API in Python and FastAPI) is specific, checkable, and particular to this repo - and the importer (introduced in the audit lesson) broke two of them anyway. So “write concrete rules” cannot be the whole story, and this lesson will not pretend it is. Concreteness decides whether a rule can be followed at all: a vague rule cannot even be obeyed by a willing model, because there is nothing to obey.
What concreteness cannot do - stop a violation single-handed - is the last lesson’s subject. Between those two facts sits the craft of the line itself.
What the rent actually buys
Section titled “What the rent actually buys”Price both sides on the repo’s real numbers. The rent side first: the file costs roughly 115 estimated tokens, every session, whether the session touches money or not. The catch side: a money bug hides in small files here. The importer is 21 lines, about 210 estimated tokens; models.py, where the money types live, is 24 lines, about 240. Re-reading either is nearly free.
Which is why the float parse at line 13 cost so little to catch - a careful diff review reads the code it sat in. What made the violation expensive is what it didn’t cost. The suite was green, the sample data survived, and nothing in the loop flagged it. It shipped silently, and it is still shipping.
The date rule shows the other side of the trade, and it is the file’s best receipt. When that bug surfaced, the agent had the correct fix cheap - ISO 8601 was already named in its context, with an example timestamp, before anyone mentioned dates. The rule paid its rent in the one session that needed it most, by making the right fix the obvious one.
On a repo this small the token arithmetic is a rounding error either way. What the file earns is correctness on arrival, and what it cannot earn is prevention - and both halves of that sentence are now proven on this codebase.
What Codex follows
Section titled “What Codex follows”The rules that land share a shape: specific, verifiable, and particular to budgetcli. You can check each line of the real file after any diff. “Money is always integer cents. Never use floats for currency” - plus the value that makes it concrete, “a $5.75 coffee is stored as 575.” You can grade an edit against that in seconds, and so can the model. The same holds down the file:
- The domain fact, with the value. “A $5.75 coffee is stored as
575” beats “handle currency carefully.” The value gives the model something to copy; the adjective gives it nothing. - The exact format, with an example. “Dates are ISO 8601 UTC (e.g.
2024-03-15T00:00:00Z)” beats “use standard dates.” A named format plus one sample is checkable against any diff that touches a date. - The exact command, not the gist. “Run tests with
pytest” beats “test your changes.” A command the model can run verbatim beats an instruction it has to interpret. - A stated preference where the code is silent. “Prefer pydantic models over raw dicts” tells the agent which of several valid-looking shapes this repo wants.
Run the chapter’s proposal - the parse-path rule from the audit lesson, which reads: “When parsing an amount string, convert to integer cents without passing through a float - use Decimal or string math, rounding half up.” - through the same bar. Would you have to tell a new teammate? Yes: the float shortcut is the natural first instinct at exactly that line. Will it still be true in three months? Yes: decimal strings are what bank exports send. It names a path, not a virtue. It passes, which is why it is proposed.
One candidate the audit rejected is worth noticing. The file does not list the category names - entertainment, transport, groceries, dining, shopping, uncategorised - even though a fixed list looks like a classic good rule. Today the code states them: categorise.py holds the table, nothing calls it yet, and there is nothing for a session to get wrong.
The moment the categorizer is wired into the import path, inventable categories become a live risk and the closed list becomes a candidate. Rules follow the code’s real failure modes, not its imagined ones.
What Codex ignores
Section titled “What Codex ignores”The entries to keep out are the ones that feel responsible but move nothing:
- Generic best-practice filler. “Write clean code.” “Follow best practices.” Codex already aims for these; the words just spend tokens.
- Anything the code already states. The framework, the language, the directory names, the default
"uncategorised"on every transaction - the repo says all of it. Don’t transcribebudgetcliinto the file that describesbudgetcli. - One-off task notes. “We’re hardening the money parse this week.” True this week, stale next month, misleading the month after. If it isn’t a standing fact, it isn’t a rule - it’s a prompt.
The honest test for any candidate line stays the one from the audit: would I have to tell a new teammate this, and will it still be true in three months? Yes to both, it is a rule. Vague, obvious, or temporary - leave it out. You are curating, not dumping.
The arithmetic holds for any repo, not just budgetcli. Seven candidate lines from another project - illustrative, not this codebase - toggle each one in and watch the ledger decide which ones would actually have paid for themselves this week:
You now know what to write, where to put it, how to phrase it so Codex can follow it, and what a good one earned on this repo - one cheap fix, zero prevention. What none of that tells you is what the file cannot do: the size at which it stops loading silently, and the line you must never cross - treating guidance as if it were enforcement. That is the final lesson of the chapter.