Repository navigation
Conversation
A run's cap is the run's alone: one unit whose agent loops spends what the others were to have, and the run stops for all of them. --max-usd-per-unit, and the builder's Limits step, give each unit its own. The run's ledger keeps what each unit spent and has in flight; a call that does not fit its unit's cap is refused, and so is every later call of that unit, while the run goes on. A unit's own calls and its simulated user's count: they are the task. Its judge's do not: the judge scores it. The unit ends as UnitSpendingCapError, an agent error counted against the agent, as one that spends its time is. A call in flight is now booked at the average reply the run has had from its model, not the largest: one long reply raised the booking of every call after it, and a real run cut eleven tasks of 24 off at a cap they had spent an eighth of.
3 of 10 tasks
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
- A call refused for want of a price was recorded as the cap's: its unit ended as SpendingCapError and the run stopped with a ledger still open. It is now recorded apart (refused: price) and reads as an API error; the run goes on. - Every POST was booked as a model call, and one that reported no usage kept its booking for good: a token count, a cancellation. Only a call that names its model on a billed route is booked. - A refusal on the benchmark's own relay (its driver's embeddings) was never read: the unit it cut off is now the cap's. - The Limits step asked the price of fewer models than the run calls: an agent's side models and a control's simulated user are asked too. - The live board said the agents' cost "of the cap", which counts the judge's calls too: the cap is said on its own. - A run with a cap per unit alone could be recorded stopped and then fail to format a cap it never had: only the run's cap stops a run. - The cap's stage line had no state and showed as a stage that never starts: the units' stage says why it ended. - A merged job said the whole launch stopped at the summed cap when one shard had: it says how many shards stopped at their share. - The ledger read parts it always writes as if they could be missing, and was read three times a call: a booking is made under the one lock, and a unit's count goes when the unit ends.
The run page's section was 673 words in rows of up to 585 characters, beside rows of about 90. It is 257 words in nine rows; the other pages point to it.
This comment has been minimized.
This comment has been minimized.
This was referenced Nov 11, 2026
… found A cap closed for good on estimates alone: fifteen first calls, booked before any reply, could end a run nothing had been spent of. A call that would fit with nothing in flight now waits for the calls in flight to settle (two minutes at most); only one that does not fit, or waited in vain, closes the ledger. On a real provider, fifteen at a time under 0.006 USD stopped at 0.0059 spent where it stopped at the start. Also: - a call that timed out or was dropped after it was sent keeps its booking: it may have been billed. Only one never sent gives it back; - a ledger that fails is recorded apart (refused: ledger), not as the cap; - a call that names a model is booked on any route but a token count, and one with nothing to price is not sent once the cap is reached; - a failed driver is the cap's only when the cap refused the driver's own call: an agent's own error stays the agent's; - a cap per unit under a millionth of a dollar is refused at Limits; - a control is not asked the price of a simulated user it never calls, and the run and the Limits step read the models to price from one place; - the price list is read at most every five seconds while a recipe is edited.
🔴 PR judge · 4 of 8 yes · #49 ↔ #47
Acceptance criteria, 3 of 3 met
Checks the script runs itself, 4 of 4 pass
some of the diff was cut before the judge read it |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #47
What
A run can be given a spending cap per unit beside the run's:
nullius standard run --max-usd-per-unit USD, and "Spending cap per problem (USD)" on the builder's Limits step. It is kept in the run's ledger: what each unit spent and has in flight, beside the run's. A call that does not fit its unit's cap is refused by the relay the same way (HTTP 402, recordedrefused: unit), and so is every later call of that unit; the run's ledger stays open and the other units run. As decided on the issue:UnitSpendingCapError, an Agent error counted against the agent, as one that spends its time is. It had its attempt. A unit the run's cap cut off stays Spending cap, outside the rate.The benchmark's own relay (an option's embeddings) books for no unit.
result.json'sbudgetcarriesunit_max_usdandover_unit; the job page shows the cap per unit and the units that reached it;mergekeeps both. From the builder every job is given the cap per unit whole, where the run's is shared. The parse's cache schema moves to 15.One change to the run's cap comes with it. A call in flight was booked at the largest reply the run had had from its model. The first real run with a cap per unit showed the flaw: one long reply raised the booking of every later call, and eleven of 24 tasks were cut off at a 0.0009 USD cap having spent as little as 0.0001. A call in flight is now booked at the average reply the run has had from that model (the ledger keeps their count and sum), still 4,096 tokens before any reply and never more than the limit the call names or the model's own.
A review of the three cap pull requests together (#46, #48 and this one) found ten defects; they are fixed here, in 856e6f4, so the three are best merged together:
A second review found ten more, fixed in 78d0932. The main one: a cap closed for good on estimates alone, so fifteen first calls booked before any reply could end a run nothing had been spent of. A call that would fit with nothing in flight now waits for the calls in flight to settle, two minutes at most; only one that does not fit, or waited in vain, closes the ledger. Also: a call that timed out after it was sent keeps its booking; a ledger that fails is recorded apart, not as the cap; a call that names a model is booked on any route but a token count, and nothing goes out once the cap is reached; a failed driver is the cap's only when the cap refused the driver's own call; a cap per unit under a millionth of a dollar is refused at Limits; a control is not asked the price of a simulated user it never calls, and the run and the Limits step read the models to price from one place (
ledger.models); the price list is read at most every five seconds while a recipe is edited. One finding predates the cap and is recorded as #50: a call's estimated cost prices tokens written to a cache at the cache-read rate.The cap's documentation is also cut to the size of the rows around it (28383e4): the run page's section from 673 words to 257, its longest row from 585 characters to 140.
Stacked on #48.
Touches
backend · routes · templates and static · docs and site · the standard
Before and after
Taken at 1400 px in the dark theme; the images are not attached here.
UnitSpendingCapErrorbeside the labelVerified
test_standard_ledger.py: a unit that reaches its own cap is refused from then on and the run goes on; a call that is no unit's has none; the run's cap and a unit's are held together and only the run's stops the run; a unit's calls are its own and its simulated user's, never its judge's; the ledger keeps the average reply per model, eight short answers and a long one giving 534 tokens, not 4,000.test_standard_relay.py: a unit's own cap refuses its calls and its simulated user's (402,unit,unit_spending_cap), not its judge's nor another unit's, and the benchmark's own relay books for no unit; the refused unit readsUnitSpendingCapError, an Agent error outside the infrastructure outcomes.test_standard_run.py: eight units, one of them with a prompt of a thousand tokens over its cap of 0.0005: it alone is cut off, against its agent, the seven others resolve, the run is not stopped, and the result and the live feed say so.test_classify.py,test_standard_shard.py,test_standard_recipe.py,app/ui_tests/test_capped_job.py: the outcome on both sides; a merged job keeps a cap per unit and the units that reached it; a recipe's cap per unit goes whole to every job, asks for prices alone, and names Limits when it is no amount; the job page of a run with a cap per unit; the builder keeps it and offers it once.docs-driftscript: 0 findings.--max-usd-per-unit 0.0009 --max-usd 0.05: 21 passed and 3 reached their cap, having spent 0.00073, 0.00076 and 0.00108 (a reply longer than was booked); the run was not stopped and spent 0.0118 of its 0.05. The same run on the largest-reply booking cut 11 tasks off.--max-usd-per-unit 0.0009 --max-usd 0.008: one task reached its own cap and went on record as an Agent error; later the run's cap stopped the run, 3 tasks cut off as Spending cap and 5 not run; 15 passed. The job page shows the notice, both caps and the three outcomes. The run spent 0.0087574 USD, 0.00076 over its cap of 0.008: replies in flight longer than the average they were booked at. Billed 0.0087574.Risk
UnitSpendingCapErrorsays which it was.Undone by a revert: a recipe saved with
max_usd_per_unitthen reads as one without, and a job'sbudgetkeeps its extra keys, unread.