Say No
Everyone shipped the loop. What you trust to end it is the real choice.
This spring, two of the coding agents I use shipped the same feature within weeks of each other. Codex got a /goal command. So did Claude Code. You hand it an outcome and it keeps working, turn after turn, until it's met.[1]
I'd been running my take on that loop for about six months, first in Forge, then in its successor, Anvil. Watching it land as a built-in was awesome.
The loop everyone agrees on
Your shell script, my Anvil, Codex's /goal, Claude Code's /goal: same skeleton. A few months ago, when I wrote the first of these posts,[2] this still felt like a take. Now it ships in the box.
The loop only does half the job: it keeps the agent working. Something else decides the work is done, and what you hand that to is the part worth writing about.
What calls it “done”
| System | What holds the verdict | Model-free verdict? | Open-ended goals? |
|---|---|---|---|
| Codex | the working model, grading its own evidence | no | yes |
| Claude Code | a separate model, on the transcript | no | yes |
| Anvil | a command's exit code | yes | no |
Two of these are a model's opinion. The third isn't. Codex makes the working model gather the evidence by running the tests and reading the code, then lets that same model decide for itself whether the evidence is enough. Claude Code splits the job: a small, fast model--not the one doing the work--reads the transcript after every turn and rules on whether the condition holds. It never runs a command or reads the code itself, so it can only judge what the working model surfaced. Anvil's verdict is the command itself, and a command doesn't have an opinion.
The command verdict isn't Anvil's invention. In Claude Code it's a Stop hook that runs your own script, while /goal is that same hook with a model in the script's place; in Anvil it's the only path, and a run can't report success until a check says so.
The part that can say “no”
A model reading a transcript can be talked around: it can't run anything itself, so “all tests pass” with no run behind it reads the same as the truth. And even when the run is real and the exit code is zero, a model still has to decide the zero satisfies the goal and that it's time to stop. The model holds the stopping authority. That's the part that's hard to lock down.
A command's exit code can't be talked around. npm test does not care how persuasive the agent is. It runs the tests and returns a number.
A command is only as honest as what it checks, and an agent that can edit the test can make anything pass: weaken the assertion, skip the case, hardcode the answer. So you don't let it. The check it has to clear gets frozen out of its reach. Edit it, and the run is void. You can lock down a check. You can't lock down an opinion.
That's what lets Anvil run without me in the room. Nothing comes back to me until a command says it passed, so by the time I look at a run, it already builds and the tests are green. But a gate that can't be talked around can still pass a wrong change. It isn't fooled; it just can't tell whether the change is right, because it only checks what I wrote. So I still review every run.
Letting it run
For the last few weeks, Anvil has been doing actual work in our GTM monorepo: migrating cron routes, wiring trigger endpoints, the unglamorous stuff. The changes go up as pull requests like anyone else's.
One of them introduced a real bug: a cron route that would have failed in production. Review caught it before merge. The bug came from the agent doing more than I asked: I wanted one route migrated, and it reached into another. It's like hiring a painter for one room and coming home to find they'd redone the plumbing in another. So I stopped relying on review to catch that, and added a blast-radius guard: I scope a run inside a fence, and any change outside it voids the run.
The point isn't that the agent is brilliant. It's that I don't have to believe it is. Underneath, Anvil runs the cheapest model that clears the gate, Sonnet by default, escalating to Opus only when the gate keeps saying “no”. The gate isn't just the verdict; it's the escalation signal: a model that can't pass it isn't the right model for the job, and Anvil finds that out by watching it fail instead of by guessing up front. It's built on Pi, routing through Vercel's AI Gateway, but what makes it Anvil isn't the model underneath. It's the gate.
The trade
There is an honest cost to deciding “done” with a command instead of a model. A command is narrow. It only works when “done” reduces to something that exits zero: a build, a type check, a test, a script you write. “Read the diff and tell me where it's most likely to break in production, and how sure you are” has no exit code. /goal handles that fine, because a model can read it and form an opinion. Anvil can't express it at all.
The gate doesn't make judgment disappear, either. It moves it up front, into the check you write, instead of a verdict at the end. Writing a check that actually captures what “done” means is the real work, and it's exactly the work /goal's pitch lets you defer to the model.
The gate settles the mechanics; review settles the rest. For the work I hand off--make it build, make the tests pass, fix the types without breaking the suite--that layering is enough. What I won't hand the agent is the authority to declare its own work done. That stays with a command, or with me.
Forge illustration by Jon Romero Ruiz.
[1] Codex and Claude Code both document /goal as a standing objective the agent works toward across turns. Codex lets the working model mark its own goal complete only after it checks the objective against the files, tests, and logs, but the model still decides for itself whether that evidence is enough; Claude Code sends the condition and the conversation to a separate model after each turn, which runs no commands and rules only on what the agent surfaced.
[2] The third in a series, after Outcomes and Ask Again. This one is about letting a command, not a model, have the final say.
