At agentic speed, a rule that only lives in a document will eventually be broken.
The rules that hold are the ones the build enforces.
Write “core must not import from commands” in AGENTS.md and agents will follow it… most of the time. Across thousands of changes, “most of the time” adds up to a steady leak.
Turn the same rule into a test and it holds on every change, made by any agent with any context, reviewed or not.
Documentation still matters. It explains why a rule exists and tells an agent what the check will say before it runs into it.
The higher the rung, the earlier the agent finds out and the less anyone has to remember. At the top, the mistake can't even be written.
Our codebases use every rung. One rule can sit on several: c8ctl's layering guard runs in the pre-commit hook and in CI.
clippy.toml bans Instant::now and sleep outside one clock adapter.layering-import-boundary.test.ts in c8ctl, architecture_test.go in the Go SDK.max, README snippets that must match the compiled examples.A new rule often has existing violators. Fixing them all first stalls the rule. Waiting means new violations keep arriving.
c8ctl's test-import guard starts with a closed allow-list of the current violators, PENDING_MIGRATION. Nothing new can be added, and every migrated file comes off the list. The debt can only go down.
A ratchet turns a big clean-up into many small, safe changes. That's exactly the kind of work agents are good at.
Pattern from c8ctl tests/unit/test-import-boundary.test.ts. File names are illustrative.
The Rust SDK bans ambient time, but the clock adapter itself has to call Instant::now. The rule allows that, on one statement only, with a reason. Never for a whole module.
A module-level exception switches the rule off for everything written there later, including by an agent that has never heard of the rule. A statement-level one keeps guarding the rest of the file.
sleep: use the injected ClockEvery repo has the same policy: warnings are fatal, nothing gets suppressed to make a build pass, and no failure counts as “pre-existing” without explicit confirmation. Verify the baseline is green before an AI-assisted session starts.
A red baseline teaches agents (and people) that red is normal. After that, no check means anything.
Every repeated review comment is a rule waiting to move up the ladder. The first time, fix the code. The second time, fix the system: write the check, then document why it exists.
c8ctl goes one step further. Agents copy the pattern from a nearby file, and that copying is only reliable “when every nearby file follows the canonical shape”. So the canonical command-handler shape is written down and enforced by lints.
c8ctl.dryRun off the global.”no-runtime-global-reads-in-handlers.test.ts: now no reviewer has to remember.Write rules down to explain them. Enforce them to keep them. Push each rule up the ladder, ratchet the old violations down, keep exceptions narrow, and keep the baseline green so every red signal means something.