A field guide

Keeping code, and the LLMs that write it, on the straight and narrow

Most of the code in my projects is now written by LLM agents. Agents are fast, tireless and keen, and left alone they will copy a function instead of sharing it, swallow an error to make a test pass, and quietly add one more entry to the list of things the checks ignore.

This site explains every technique I use to stop that: the written rules, the machines that enforce them, and the tests and proofs that catch what slips past both.

Line and branch coverage
100%
Tolerated duplication
0%
Mutation kill rate on changed code
100%
Precommit gates
25

The short version

Every rule I care about lives in one place, AGENTS.md. Every rule that a machine can check has a checker. Every checker that starts with existing debt holds that debt in a list that is only allowed to shrink. The tests have to cover every line, and then a mutation tester proves that the tests would notice if a line changed. The parts that move money are drawn as state machines and checked by a model checker.

None of this is exotic. It is a pile of small, boring, mechanical checks, each of which closes one door that an eager agent likes to walk through.

What the guide covers

Each area has its own section, with worked examples from real code.

  • Instructing the agents

    One rulebook, a planning workflow that comes before any code, and guardrails that stop an agent "fixing" the things it should report.

  • Static checks and ratchets

    Linting, copy-paste detection, dead-code hunts, purity checks and baselines that can only ever get smaller.

  • Testing that bites

    Full coverage, weak-assertion gates, mutation testing, Cucumber stories and nightly runs against real payment and email sandboxes.

  • Machines and proofs

    Lifecycles declared as data, swept against the real code, and checked for every possible run by the Apalache model checker.

  • Workflow and gates

    The precommit pipeline, CI that mirrors it, stacked pull requests, review rules and an LLM grader that knows the house style.

  • A pinned environment

    Nix and devenv pin every tool, so a check that passes on my machine passes in CI for the same reason.

What a rule looks like

Rules that pre-decide the argument

The rules in AGENTS.md are written to end discussions before they start. An agent that has to ask "should I delete this unused export?" or "should I merge these two near-identical helpers?" will often ask, or worse, guess. The rulebook answers in the heading.

Each preference is one bold line and a short reason, so an agent can apply it and a person can argue with it.

Read about AGENTS.md
AGENTS.md
- **Remove dead code โ€” always the answer**: When code
  has no production caller, delete it. Never keep it
  "for symmetry" or "for future use".
- **Unify systems โ€” the answer is yes**: When two
  systems do almost the same job, find the single
  abstraction that does both.
- **Never loosen a check to close a finding**: Fix
  the code, or argue on the thread that the finding
  is wrong.

Where the examples come from

Almost every example on this site comes from chobbledotcom/tickets, an open-source ticket booking system that runs on Bunny Edge Scripting and takes real payments through Stripe, Square and SumUp. A few come from chobbledotcom/chobble-template, the Eleventy template this site is built on. Both repositories are public, so every file named here is a link you can follow and read in full.