Budgeting a run: the quote and the cap
What a cost estimate is, what a budget is, who decides them and on what basis, what happens when a run reaches the ceiling, and how quotes get better over time.
Two numbers ride on every dispatch, and they are not the same kind of thing.
| the number | what it is |
|---|---|
--cost-estimate-usd — the quote | a person's prediction of what this work should cost. It changes nothing about how the run behaves. It exists so the outcome can be compared against an expectation somebody actually held |
--budget-usd — the cap | the ceiling, written onto the run line the dispatch composes. ⚠ It stops a run only on the claude-code backend, where it is handed to the agent as its own spend limit and a run that reaches it ends; on every other backend it is recorded and nothing stops the run at a dollar figure — the wall clock does (which ceiling is enforced by whom) |
The cap is derived from the quote, not chosen separately
The cap is twice the quote. saphan dispatch command requires the quote and composes the cap from
it — a quote of 1 gives a cap of 2 — and it refuses --budget-usd as a flag of its own, because
the cap is derived, not chosen.
Why a derivative rather than a free choice: a cap picked independently of a quote is a number with no argument behind it. Either it is so high it never binds — in which case there is no ceiling — or it binds at a value nobody can defend, and the run dies for a reason its author cannot explain. Tying the cap to the quote means every ceiling has a stated prediction behind it, and a run that hits the ceiling is evidence the prediction was wrong.
⛔ The composer does not compose a run without a quote. ⚠ The lower-level saphan run
takes both flags as optional and independent: a run started by hand can carry a cap with no
quote, or neither. A run with no quote records its quote as absent, never as zero.
Who decides the quote, and on what basis
The person composing the dispatch declares the quote, and the engine derives the cap from it. The basis is not intuition:
- the shape of the work — a fix in one place, a rewrite across a package, and a measurement that has to be re-run on several hosts are three different prices;
- the model the work is routed to, since the same task costs differently on different models — which is why routing is a stated decision and not a habit;
- what comparable work actually cost last time — the part that makes this a practice rather than a guess. See keeping a calibration register below.
What happens when a run reaches the cap
On claude-code the run ends, and the record says why in its own word —
budget-exhausted: the run reached the ceiling the dispatch composed — not a vendor failure,
not a permission problem, not a rate limit. ⚠ On every other backend nothing ends at the cap;
the run goes on until it finishes or its wall clock kills it (killed-timeout), and the cap is
only a recorded number to compare against afterwards. ⚠ That distinction matters when you are reading a red run: a cap hit sends you to
your own cap table, and a vendor error sends you to the vendor. Confusing the two wastes the
next hour.
A cap hit is not a bug. It is a measurement: the work cost more than someone predicted, and now you know by how much.
Three ways a run can disagree with its quote
Compare the actual against both numbers at run close, and there are exactly three interesting outcomes:
| outcome | what it means | what to do |
|---|---|---|
| actual ≤ quote | the prediction held | nothing |
| quote < actual < cap | the run completed, and the quote was wrong — nobody was stopped, nothing failed, and no event announces it | ⚠ this is the one that hides. saphan cost <stream> prints each run's quote, actual and drift; read it, or the next quote repeats the error |
| actual reached the cap | on claude-code, the run was stopped by the ceiling; elsewhere, the run overran a number nothing enforced | decide deliberately: re-quote and re-run, or split the work |
The middle row is the reason budgeting is a practice and not a flag. A quote that is quietly wrong every time still produces green runs — and a fleet whose quotes drift produces caps that stop useful work while never catching the expensive kind.
Keeping a calibration register
The habit that makes quotes improve is small: for every finished piece of work, record the pair — what was quoted, what it actually cost — with a one-phrase reason whenever the two diverge, and a reference to the run so the number can be checked.
- Newest first, append-only. A register that gets rewritten is a register nobody can audit.
- ⛔ No number without a reference. A cost figure whose run cannot be found is a rumour.
- What it is for: the next quote, and the routing decision behind it. Over a few hundred rows the register stops being a log and becomes a price list for classes of work.
⚠ Two figures in this product are not interchangeable, and mixing them makes a register worthless. A quote is a prediction; an actual is a measurement taken at run close. A running leg has no actual yet — reading one before the run is finalised gives you an estimate wearing a measurement's clothes.
Where the numbers live afterwards
Per-stream rollups are read with saphan cost and the ledger with
saphan ledger; the console projects the same record. See
Every command for the other read verbs, and
Audit and compliance for what an auditor gets out of it.