By David Kowalski, Developer Tools & Agents Editor

Most agent demos fail the same way in production: the loop keeps going. The model “almost” finishes a refactor, then invents a second refactor, then a third, until the bill and the diff both look embarrassing. I used to debug that by rewriting prompts. These days I start somewhere colder: stop conditions.
If your agent cannot prove it is done, it is not a loop. It is a chat session with cron.
What I mean by a stop condition
A stop condition is a machine-checkable predicate the agent does not get to grade itself on. Examples I trust:
- Test suite exit code is zero on a fixed commit SHA.
git status --porcelainmatches an allowlist (or is empty).- A typed checker reports zero errors on touched paths.
- Token spend for the run is under a hard ceiling.
Examples I do not trust:
- The model says “all done.”
- A summary markdown file claims success without linking to a command transcript.
- “Looks good” from a second agent that shares the same context window.
I think self-graded success is the fastest way to launder unfinished work into a green dashboard.
My default maturity ladder

I only promote a loop one rung after the previous rung is boring for a week.
- Report-only. The agent writes a plan and a risk list. No file writes outside a scratch directory.
- Propose diffs. It opens a branch and leaves uncommitted patches. A human reviews.
- Commit with checks. It may commit only if the stop predicates pass.
- Merge with policy. Merge bots still own the merge; the agent never presses the button alone on protected branches.
Skipping from 1 to 4 is how teams discover their secrets are in a random worktree at 3 a.m.
A concrete checklist before autonomy
Before I let a loop touch a real repo, I write answers in a LOOP.md that lives with the project:
- Goal: one sentence, no compound “and then also.”
- In scope paths: glob list. Everything else is forbidden.
- Out of scope: migrations, lockfile churn, generated assets—unless explicitly named.
- Verifier command: a single shell command whose exit code is the truth.
- Budget: max steps, max tokens, max wall clock.
- Human interrupt: how to kill the run and who owns the page.
If I cannot fill that file without hedging, the task is not ready for a loop.
Failure modes I keep seeing
Silent partial success. Tests pass because the agent deleted the failing test. Fix: verifiers must include git diff size caps and forbid deleting test files unless the goal says so.
Context rot. Mid-run the agent forgets the original ticket and invents a cleanup project. Fix: pin the goal at the top of every turn; refuse goal rewrites without a new run id.
Tool thrash. Dozens of tiny shell calls that burn tokens and teach nothing. Fix: require batched plans; rate-limit tool calls per step.
On tooling, I prefer boring harnesses over clever frameworks. A shell script plus a worktree beats a multi-agent orchestra I cannot replay.
How I test a new loop
I run three dry scenarios on a throwaway fork:
- Happy path with a known-green verifier.
- Broken verifier (I sabotage one assertion) to confirm the loop stops and reports failure.
- Budget kill where I set tokens absurdly low to confirm it exits cleanly instead of thrashing.
Only after those three do I schedule the job.
Bottom line
Autonomy is not a personality trait you unlock with a longer system prompt. It is a contract: goal, scope, verifier, budget, kill switch. I design the quit path first. Everything else is commentary.
If you take one habit from this piece, make it this: refuse to schedule an agent until you can name the command that is allowed to say “done.”
Comments
Sign in to join the discussion and leave a comment.
Sign in with Google