Your Coding Agent Hit Its Cost Cap Mid-Refactor and Left Nothing Behind. Reserve the Last Fifth of the Budget for the Handoff.
A hard spend cap protects your bill, but when it trips mid-task it throws away everything the agent learned. Split the budget into phases so the run always ends with a usable handoff instead of a dead process.
A per-task spend cap is the first production control most teams put on a coding agent, and it usually works exactly as written: the run reaches the limit and the process stops. The trouble is where it stops. A refactor that touched nine files and had two left dies with no summary, no list of what changed, and a branch half-migrated to a new interface, and the next run starts from zero because nothing it learned was written down.
The cap did its job on the bill. It failed on the work. The fix is not a bigger cap; it is a cap the agent can see coming, with a slice of it set aside for ending well.
Why a flat cap produces the worst possible ending
A flat cap is checked by the harness, not by the agent. From inside the loop, the budget is invisible: the model plans as if it has unlimited turns, explores freely, and is cut off at whatever step happens to cross the line. That step is almost never a good stopping point, because a good stopping point is something the agent has to choose.
Look at what gets lost when the process is killed:
- The working map. Which files were read, which call sites were found, which ones turned out to be irrelevant. This is often the most expensive thing the run produced.
- The state of the diff. Which edits are complete, which are partial, whether the tests were green at any point.
- The reason for the last action. The agent was halfway through something. Nobody knows what.
A person picking this up has to reverse-engineer the branch. A second agent run has it worse: it re-reads the same files and very likely spends the same budget to land in the same place.
💡 The technique: a phased budget the agent is told about
Split one task budget into three phases, and put the current phase in front of the model on every turn.
| Phase | Share of budget | What the agent may do |
|---|---|---|
| Work | first ~70% | Explore, edit, run tests, anything in scope |
| Converge | next ~10% | Finish the edit in progress, run tests, no new files or new investigations |
| Handoff | last ~20% | Write the handoff, commit or stash, then stop |
The shares are a starting point, not a rule. What matters is that the handoff slice is reserved: work cannot borrow from it. Twenty percent sounds generous until you notice that the handoff is the only part of a capped run guaranteed to be useful to someone.
The harness side
The loop tracks spend after every model and tool call and computes the phase. When the phase changes, it does two things: it narrows the tool list, and it injects a short system message saying so.
def phase_for(spent, cap):
ratio = spent / cap
if ratio < 0.70:
return "work"
if ratio < 0.80:
return "converge"
return "handoff"
TOOLS_BY_PHASE = {
"work": ALL_TOOLS,
"converge": ["read_file", "edit_file", "run_tests"],
"handoff": ["write_handoff", "git_commit", "git_stash"],
}
def next_turn(state):
phase = phase_for(state.spent, state.cap)
if phase != state.phase:
state.messages.append(system_msg(PHASE_NOTICE[phase]))
state.phase = phase
return call_model(state.messages, tools=TOOLS_BY_PHASE[phase])
Narrowing the tools matters more than the message. A model told "please wrap up" while still holding a search tool will often run one more search. A model that no longer has the search tool cannot.
The hard cap stays in place underneath all of this, as the backstop for the case where the handoff itself runs long. If that backstop ever trips, treat it as a bug in the phase shares, not as normal behaviour.
The agent side: what a handoff contains
write_handoff should take structured fields, not free prose, so the next reader (human or agent) can rely on the shape:
{
"goal": "Replace LegacyClient with HttpClient across billing/",
"status": "partial",
"done": ["billing/invoice.ts", "billing/refund.ts", "..."],
"in_progress": {"file": "billing/ledger.ts", "state": "imports changed, call sites not"},
"not_started": ["billing/export.ts", "billing/reconcile.ts"],
"tests": "last run: 3 failures, all in ledger.spec.ts, expected until ledger.ts is finished",
"findings": ["reconcile.ts calls LegacyClient via a wrapper in utils/net.ts"],
"next_step": "Finish call sites in ledger.ts, then rerun ledger.spec.ts"
}
The findings field is the one that pays for the reserve. It carries forward what exploration discovered, so the next run does not pay to rediscover it.
Walking through the refactor again
Same task, same cap, phased this time.
- Work. The agent migrates seven files and runs tests twice. Spend crosses 70% while it is in
ledger.ts. - Converge. The notice arrives and search tools disappear. The agent finishes the imports in
ledger.ts, judges the call sites too large for the remaining slice, and runs tests to record the current state. - Handoff. It writes the JSON above, commits the seven finished files as one commit and stashes the partial one, then stops.
Nothing extra was done. The difference is that the next run starts from next_step with a clean commit, and a reviewer can see from status: partial that this branch is not ready to merge.
⚠️ Three ways this goes wrong
- Estimating spend only on model calls. Tool calls that hit paid APIs, or that return huge outputs the model then has to read, move the budget too. Count everything the task pays for, or the phase changes come late.
- Letting the converge phase start new edits. "Finish the edit in progress" has to mean one file. If the agent can open another, converge becomes a second work phase and eats the reserve.
- Hiding the phase in logs only. If the model never sees the phase, it cannot plan around it. The notice has to be in the conversation.
A quick check for your own harness
- ✅ Can the agent tell, from its own context, how much budget is left?
- ✅ Does the tool list shrink as the budget runs out?
- ✅ Is there a budget slice that work is not allowed to spend?
- ✅ Does every capped run end with a structured handoff and a committed or stashed state?
- ❌ Is the hard cap the only thing that ever ends a long run?
If the last box is the one you tick, your cap is protecting the invoice and throwing away the work you paid for. A cost limit in production should decide how a run ends, not just when.