A loop that cannot stop is an invoice
Strip the mystique and your triage agent is a while loop: look at the state, pick one tool call, validate it, run it, append the result, go again. You've written a hundred of these. The difference is that this one's iteration count is decided by a model, its per-iteration cost is a real API bill plus real CI compute, and its exit condition — if you don't write one — is "the model feels done."
The model does not reliably feel done. Here's the failure you'll actually see, from the feature you're building right now: job j-118 is red because a dependency registry is having a bad day. The agent reads the log, decides flake, reruns. Still red — the registry is still down. Reads the log again. Same log. Reruns again. Every iteration looks locally reasonable; the run as a whole is a money fire. No single step is a bug you could point to in review. The bug is that nothing above the steps is counting.
The two caps, and why they're both mandatory
- Step cap. A hard ceiling on iterations. Triage should finish in six steps; a run at step seven isn't being thorough, it's stuck. The cap converts "stuck" from a silent state into a named outcome you can alert on.
- Cost cap. Steps aren't priced equally —
read_log_tailis pennies,rerun_jobis compute. A run can be four steps long and still be too expensive if three of them are reruns. The cost cap meters dollars, not iterations, and fires before the overspending call runs, not after.
You need both. A step cap alone lets six expensive steps through. A cost cap alone lets forty cheap steps loop forever below the price ceiling. They fail differently, so they gate differently.
Stop reasons are part of the return value
The lazy loop returns its answer or nothing. Yours returns why it stopped, every time, as a string from a closed set: plan-complete, step-cap, cost-cap. This is the same discipline as exit codes — 0 versus 137 is the difference between "done" and "OOM-killed," and you'd never ship a service that couldn't tell you which.
The distinction earns its keep in the metrics, a week after launch. plan-complete trending down while step-cap trends up means your agent is getting lost more — maybe a prompt regression, maybe a new failure mode in the queue. cost-cap firing at all means some job class is eating reruns. You can only see the trend if the loop names the reason, and the loop can only name the reason if the caps are inside the loop definition — not a wrapper someone adds after the first incident, because by then the first incident has happened.