The new way to code: guardrails and tests, not line-by-line control
Where the leverage went
For most of computing history, the core skill of a programmer was writing every line correctly. You wrote the line, you read the line, you reasoned about whether it was right. That is still a valuable skill — but it is no longer where the most leverage is. Once tools can generate code and even run it for you, reading every line stops scaling. There is too much code, produced too fast, to verify by eye.
The skill that compounds now is different: building guardrails and test cases so that faulty output is caught and minimised. You stop asking "is this line correct?" and start asking "can this system be silently wrong?" If the answer is no — because a schema, a contract, or a test would force the error into the open — then it barely matters whether every individual line is pristine. The system is correct by construction, or wrong in a way you will hear about.
Here is the part that surprised me: this is not a new idea. It is a very old idea, from typed functional programming and 1980s language design, that the AI era has made newly relevant. Let me show you what I mean.
The old way: correctness by inspection
The traditional mental model is correctness by inspection. The program is a glass box; you make it correct by reading it carefully. Reviews exist to put more eyes on the glass. This works when humans write all the code and the codebase grows at the speed a human can type. It breaks down when a model can emit a few hundred lines in seconds. You simply cannot inspect your way to confidence at that volume.
Notice the assumption hidden inside inspection: the human is the test. Every line is trusted only because a person looked at it. If nobody looks, nothing is verified.
The new way: correctness by construction and detection
Replace that with two complementary moves:
- Correctness by construction — design the system so that certain wrong states cannot be expressed at all. The error is prevented, not detected.
- Correctness by detection — for the wrong states you cannot prevent, make sure they are caught loudly and early, before they can cause harm.
Together these let you trust a body of code you did not personally read, because the system itself is doing the checking. The human stops being the test; the system becomes the test.
This is older than AI
If you have been around software a while, none of this is new. The vocabulary is.
Yaron Minsky, of Jane Street, famously framed the construction half as "make illegal states unrepresentable." The argument is that you should design your types so that an invalid state cannot even be written down in the language — not "validated at runtime," but impossible to express. If your type system makes a negative bank balance unconstructable, you do not need a runtime check for it; the bug category is gone.
The detection half is Design by Contract, Bertrand Meyer's framework from Eiffel: every function carries a precondition (what it demands from its caller), a postcondition (what it guarantees), and every type carries an invariant (what must always hold). Violations are bugs, and they fire loudly at the boundary. Here is the shape of it, in plain Python so the idea is clear regardless of the language you use:
def deposit(account, amount):
assert amount > 0, "precondition: amount must be positive"
before = account.balance
account.balance += amount
assert account.balance == before + amount, "postcondition"
assert account.balance >= 0, "invariant: balance never negative"
The precondition is the caller's obligation; the postcondition and invariant are the function's promise. Contracts are executable documentation — they say precisely what a function requires and delivers, and they fail fast when someone lies.
What AI-assisted coding did was not invent these ideas. It made them stop being optional.
Four guardrails, from real projects
I keep arriving at the same four moves. Each one converts "I hope this is right" into "this is verifiably right, or loudly wrong."
1. Schemas at the boundary
In my LLM journaling app, nothing a model produces is stored until it passes a Zod schema. Malformed JSON physically cannot reach the database — there is no code path that writes unvalidated data, because the write sits behind the validator. This is "make illegal states unrepresentable" applied to a data store: the illegal row cannot be constructed, so it cannot be persisted. The check lives at the boundary, which means the code inside the boundary does not have to be defensive about model output. It can assume well-formed data, because bad data cannot get in.
2. Contracts as specs
In my multi-agent swarm, a file-based data contract is the specification the builder agent implements against and the verifier agent checks against. The contract names the grain, the columns and types, the freshness SLA, and the acceptance criteria. This is Design by Contract at the scale of a whole system: the contract is the invariant, and the verifier's job is to check that the running pipeline satisfies it. The agents do not have to agree on what "done" means — the contract defines it.
3. Deterministic, seeded tests
In my reinforcement-learning project, the agent, the environment, and the feature extraction each have unit tests with fixed random seeds that assert exact numeric values. A regression is not a hunch that the agent "seems worse" — it is a red test with a number attached. This is the testing analogue of the same principle: pin the behaviour you care about with a check that a machine runs, so that "did this change break something" has an objective answer instead of a vibe. (Take this further with property-based testing — tools like Hypothesis generate hundreds of inputs and assert invariants over all of them, which catches edge cases no human would think to write a test for.)
4. Restricted tools that limit blast radius
In the swarm, the planning agent has no tool to edit code, and the verifying agent has no tool to write anything. They cannot break what they cannot touch. The constraint is structural, not a polite request in a prompt. This is the principle of least privilege applied to agents: give each component the narrowest capability it needs, so that a failure or hallucination in one place cannot reach the things it was never supposed to touch.
The meta-skill: knowing where to put the guardrail
The hard part is not knowing that guardrails exist. It is knowing where to put them. The cheapest place to put a check is almost always at a boundary — the edge of a system, a module, a trust domain — rather than scattered through the interior. Boundaries are chokepoints: there are few of them, all traffic passes through, and validating once at the boundary lets everything inside stay simple.
Think about where untrusted data enters your system. User input, file uploads, third-party API responses, and — now — model output are all untrusted data. If you validate each of them exactly once, at the moment they cross in, the rest of your code can treat them as trusted. If you skip the boundary check, you end up re-checking the same thing in a dozen interior functions, inconsistently, and inevitably missing one. The guardrail at the boundary is a force multiplier; the guardrail sprinkled through the interior is a maintenance burden.
This is also the right frame for thinking about agents. An LLM is, technically, an untrusted subprocess that produces text. Treat its output the way you treat user input: validate it at the boundary before it touches anything that matters.
Where this still breaks (be honest)
Guardrails are not a complete substitute for reading code, and pretending otherwise is dangerous. A few places still demand a human eye:
- Security and auth. A schema confirms a token is well-formed; it does not confirm the auth logic is correct. Read the security-critical lines yourself.
- Currency and irreversible actions. Anything that moves money, sends email, or deletes data deserves inspection plus guardrails, not guardrails instead of inspection.
- Currency of meaning. An output can be schema-valid and still be wrong — a model can return a plausible-but-fabricated value that passes every type check. Schemas prevent malformed data; they do not prevent confident nonsense. For high-stakes fields, you still need a human or a second, independent check.
There is also a recursion problem: someone has to write the guardrails, and that code can itself be wrong. You cannot solve trust entirely by adding checks; you can only push the trusted core smaller and smaller until it is something a human can actually hold in their head. The goal is a small, audited trusted core surrounded by generative machinery whose failures are bounded and visible.
How to start, if this is new
If you want to apply this to an existing codebase, don't try to add every guardrail at once. Find the boundaries first — the places untrusted data crosses in (user input, file uploads, API responses, model output) — and put one strong validation check at each. That alone removes a large class of bugs, because everything inside can start assuming well-formed input.
Then pick the one invariant your system most depends on — the thing whose violation would cause the worst failure — and write a deterministic test for it with a fixed seed or a fixed input. You now have a tripwire: if anyone, human or model, breaks that invariant, a red test tells you.
Then, only if a component is risky enough to justify it, restrict its tools. Most code does not need capability limits; agents and external integrations do. Match the guardrail to the blast radius.
The recursion, and the trusted core
Here is the honest limit of the whole approach. Guardrails are code, and code can be wrong — so who validates the validator? You cannot solve trust entirely by adding checks; you only push the problem down a level. The practical resolution is the trusted core: keep shrinking the part of the system that must be correct-by-human-review until it is small enough to hold in your head. Everything around that core can be generated, probabilistic, or agent-produced, because its failures are caught at the edges before they reach the core. The goal is not to eliminate trust. It is to concentrate it into the smallest, most auditable place possible — and then defend that core by hand.
The mindset shift
Line-by-line verification asks the human to be the test. Guardrail-first design makes the system the test. You invest once in the scaffolding that makes correctness cheap to check — a schema at a boundary, a contract between agents, a seeded test, a restricted toolset — and then the generative part is free to be generative, because its failures are caught at the edges rather than hidden in the middle.
The layer that is hard to replace is not writing code. It is designing the guardrails well enough that you can stop reading every line and still trust the result. That is the skill worth building — and, conveniently, it is the skill that was already valuable long before the models showed up.
Further reading:
- Yaron Minsky, Effective ML (talks) — the original framing of "make illegal states unrepresentable," from Jane Street's OCaml practice: blog.janestreet.com
- Bertrand Meyer, Object-Oriented Software Construction — the definitive reference for Design by Contract (preconditions, postconditions, invariants).
- Hypothesis — property-based testing for Python, to assert invariants over generated inputs: hypothesis.readthedocs.io