rosneri/writing
all posts
EngineeringMar 2026 · 13 min read

Deterministic guardrails for AI-written code

Neri Rosner
Neri Rosner
backend-oriented full-stack engineer

When an autonomous agent writes a large share of your code, the bottleneck moves from writing to reviewing — and careful review does not scale. You can read the first ten diffs closely. By the fiftieth, your eye is sliding over confident-looking code that is wrong in a way you would have caught on day one. So I stopped relying on attention for anything a machine can decide, and turned every standard I care about into a deterministic guardrail .

A guardrail, here, has a precise meaning: a program that walks the repo, finds violations, and exits non-zero. Not a doc, not a lint suggestion, not a line in a style guide nobody reads. A check that fails the build. The standard is encoded once and enforced forever — identically, regardless of whether a human or a model wrote the diff, and regardless of how tired either of them was.

This is the system I run on ralphy, my autonomous-agent harness. There are about thirty of these guards now. This is what they are, and — the part I’d missed until recently — where they have to run.

Enforcement layers

The familiar enforcement points — pre-commit, pre-push, CI — are the right backbone for human teams. But they share a blind spot when the author is an agent: they all fire after the code exists. By the time a pre-commit hook rejects a bad edit, the model has already spent tokens writing it, and you’re paying for a retry loop instead of preventing the mistake.

So the earliest layer isn’t pre-commit, and it isn’t even a check. It’s the agent’s own instructions. From earliest to last:

  1. AGENTS.md — every rule is also written into the agent’s instructions, injected into each prompt, so the model aims to comply from the first token.
  2. AI hooks — intercept the model as it works, before the code is even written.
  3. pre-commit — fast structural checks on staged files, so the inner loop stays tight.
  4. pre-push — the heavier suite: duplication, test integrity, typecheck, build.
  5. CI — the same commands again, with a parity check that proves local and remote agree.

The interesting work of the last two years was moving guardrails leftward — first into hooks, and ultimately into the prompt itself.

Layer zero: write the rules into the prompt

Every check in this post has a twin: a plain-English rule in AGENTS.md. This matters more than it looks. A guardrail that only fails the build is correction — the model writes the violation, hits the wall, and burns a round-trip rewriting it. The same rule stated up front in the agent’s instructions is prevention — the model reads “names are spelled out, no abbreviations” and simply doesn’t write cfg in the first place.

That’s the difference between a tight loop and a thrashing one. Left to a wall alone, an agent can spend three or four iterations bouncing off the same guard. Stated in AGENTS.md, most of those violations never happen, and the deterministic check becomes a backstop for the rare miss rather than the primary teacher. Cheaper in tokens, faster in wall-clock, and far less infuriating to watch.

The risk, of course, is drift: the prompt says one thing while the build enforces another, and now the agent is being lied to. That’s exactly what the prompt-rule-sync guard (below) exists to prevent — the instructions and the checks are kept provably in step, so writing the rule down twice can never mean writing it down two different ways.

The AI hooks

The prompt asks for compliance; hooks enforce it the moment the model acts. These are Claude Code hooks — small scripts the agent runtime calls on specific events. They don’t review the code after the fact; they shape what the agent is allowed to do in the first place.

The point of gate one is economic as much as architectural: the cheapest violation is the one the model never writes. Every guardrail you can express as a pre-tool-use block is one the agent learns to design around instead of repeatedly bouncing off.

The catalog

The thirty-odd guards, grouped by what they protect. Each is a single script with one job; most are a few dozen lines.

Types & data shapes

Architecture & boundaries

Structure & layout

Naming & comments

Runtime & platform

Duplication

Tests

Dependency hygiene

Meta — guards on the process itself

Ratchet, don’t rewrite

A rule you can only adopt with a big-bang migration never gets adopted. So several of these guards are ratchets: they grandfather the violations that already exist and enforce the rule only on new and changed code. The no-abbreviation rule, the Bun-native rule, the single-config-pipeline rule — each carries a frozen baseline of known exceptions and fails only when that baseline grows.

That changes the economics of raising a standard. You don’t need a clean weekend and a 400-file PR. You merge the guard with today’s debt frozen in place, and from that commit forward the number can only go down. The codebase improves monotonically without ever blocking a feature.

The guards guard themselves

The subtle failure mode of any quality gate is that it erodes from the inside — someone weakens a rule to land a deadline, local and CI drift apart, the agent edits a validator to get unstuck. Three guards exist only to prevent that:

Without this layer the whole system is theatre — a wall with a door the model props open the first time it’s inconvenient.

The honest version

None of this makes the code correct. A type system stops any; it does not stop a logic error in a perfectly-typed function. No folder-size cap ever caught a wrong business rule, and no duplication detector knows whether the thing you wrote should exist at all. Correctness is still mine, and still a reviewer’s — that part doesn’t delegate.

What the guardrails do is hold the floor. They let me hand a large amount of code to an agent and trust that the result clears the same bar as the code I wrote by hand: same types, same layering, same structure, same naming, no smuggled console logs, no second copy of an enum that already exists. The model writes fast; the guardrails make sure fast doesn’t mean the codebase quietly rots — and they do it deterministically, so the standard never depends on how carefully anyone reviewed that day.

The principle underneath all thirty: if a standard matters, don’t write it in a doc. Write it as a check that fails the build — and run that check as early as the author will let you.


Filed under engineering. Building your own guardrails — or think I’ve over-engineered mine? Compare notes.

Built on this in: ralphy — the autonomous-agent harness these guardrails protect.More writing
Keep readingDDD for people who actually ship12 minDesigning a dead-letter queue you can trust11 minSpecBite: an honest postmortem14 min