Topic: Agentic software engineering — constraining AI coding agents
Approach: A finite-state workflow with hard gates and test-driven delivery
Why it matters: AI can move fast; a harness is what keeps it from moving fast in the wrong direction

AI coding agents are genuinely useful and genuinely dangerous, often in the same session. They can implement a feature in minutes — and they can also delete something you did not ask them to touch, invent a file path that does not exist, or confidently “fix” a bug by masking it. The raw capability is real. So is the risk. The difference between an agent that accelerates your work and one that quietly corrupts it comes down to one thing: the harness you wrap around it.
Over the past year, across my own products and my client work, I have converged on a way of working with agents that I trust on production systems. It is not a prompt or a clever model setting. It is a constrained workflow: a finite state machine that an agent must pass through, with hard gates that block it from doing the risky things until the safe preconditions are met. This post explains how it works and why each piece exists.
The Problem with Unconstrained Agents
Give a capable agent an open-ended instruction and a full set of tools, and it will do something immediately. That eagerness is the problem. Without structure, an agent tends to skip the parts of engineering that are boring but essential: reading the existing documentation, understanding the surrounding code, writing a test that proves the change is needed, and stopping to get sign-off before doing something irreversible.
Left to its own devices, an agent will happily guess a file path rather than look it up, patch a symptom rather than diagnose a cause, or push code the moment it appears to work. Each of these is a small failure of discipline. On a personal project they are annoying. On a regulated, fleet-wide platform they are unacceptable. The fix is not a smarter agent — it is a workflow the agent cannot talk its way out of.
The Harness: A Gated State Machine
The core idea is to model the work itself as a state machine. Every task moves through a fixed sequence of states, and the transitions are enforced by automated hooks rather than trusted to the agent’s good intentions. If a precondition for a state has not been satisfied, the tools the agent would use are simply blocked until it is.
The stages are deliberately ordered so that the cheap, reversible thinking always happens before the expensive, irreversible doing.
1. Classify the task
Before anything else, the agent has to state what kind of task this is and how large its blast radius is: a one-line fix, a refactor, a new feature touching many files. Classification decides how much ceremony the rest of the workflow demands — a typo fix should not require the same gates as a new subsystem, and a new subsystem should never be treated like a typo fix.
2. Load context — documentation first
This is the gate that prevents the most common category of agent error: acting on a guess. Before the agent may touch code, it must load the relevant documentation and prior knowledge for the feature it is working on. The rule is simple and absolute — consult the documentation before searching the code, and never assume where something lives. Edits stay locked until that context is actually in memory.
3. Plan, and get approval
For anything beyond a trivial change, the agent presents a plan and waits. A human reviews the approach before a single line is written. This is where architectural mistakes get caught — cheaply, in prose, instead of expensively, in a diff that has already rippled through six files. Approval in one context does not silently extend to the next; each significant step earns its own sign-off.
4. Write a failing test first
Every bug fix and every feature begins with a test that fails for the right reason, exercised through the real code path. This is classic test-driven development, and it does something important when an agent is involved: it forces the agent to prove the problem exists before it is allowed to solve it. A test that goes from red to green is objective evidence the change did what it claimed — not the agent’s word for it.
5. Implement the smallest change that passes
Only now does the agent write implementation code, and only as much as it takes to turn the failing test green. Constraining the change to the smallest thing that satisfies the test is what keeps an agent from “improving” code it was never asked to touch. Scope creep is a discipline problem, and the workflow removes the opportunity for it.
6. Verify against tests and standards
Finally, the change has to pass the full test suite, the linters, and the project’s coding standards — read end to end, not spot-checked. If verification fails, the workflow does not allow a blind patch; it loops back to a failing test that captures the new problem. Failure sends you backwards through the gates, never forwards past them.
The Gates That Make It Real
A workflow is only advisory unless something enforces it. What turns this from a nice diagram into a dependable system is a layer of automated hooks that intercept the agent’s actions and refuse the ones that violate the current state’s rules. In practice that means a handful of non-negotiable gates:
- Documentation gate: no file edits until the relevant context has been loaded for this specific task
- Approval gate: no significant change proceeds without an explicit human yes on the plan
- Deploy gate: no push and no deploy without express permission — approval to commit is never approval to ship
- Verification gate: nothing is called done until its tests and standards actually pass
Because the gates are mechanical, they do not depend on the agent remembering the rules, wanting to follow them, or interpreting them charitably. The agent can be as eager as it likes; the door stays locked until the key exists.
Why This Is Worth the Overhead
The obvious objection is that all of this sounds slower than just letting the agent run. In practice it is the opposite. The time an unconstrained agent saves you on the happy path, it takes back with interest when you have to unpick a confidently-wrong change, restore something it deleted, or debug a fix that masked the real fault. The harness front-loads the discipline so you are not paying for it later at a worse exchange rate.
What you get in return is the thing that makes AI assistance viable on real systems: trust. Every change is specified, tested, reviewed, and traceable. The agent moves at machine speed inside a boundary it cannot cross, and the human stays where humans belong — setting direction, approving the plan, and owning the outcome.
That is the whole philosophy in a sentence: give the agent all the speed you can, and none of the authority it has not earned. Rails, not a leash — and definitely not a blank cheque.