I think moving the ceiling into the tool layer is the right direction. If the LLM is responsible for deciding whether it is “okay” to spend more, the budget control is always going to be somewhat advisory.
The part I’d be careful with is the reservation itself when several agents are running concurrently. A simple shared counter can still race if two agents both see enough remaining budget and reserve it at nearly the same time. I’d make the reserve operation atomic and treat the reservation as a temporary hold rather than immediately treating it as final spend.
I’d probably structure each paid tool call roughly like:
estimate → reserve → execute → reconcile
If the reservation succeeds, the tool gets a bounded amount it is allowed to spend. When the call completes, the actual charge is reconciled against the reservation. If it fails before making the external request, the reservation can be released.
I’d also put a per-call maximum alongside the crew-wide ceiling. Otherwise, one unusually expensive tool call could consume most of the remaining crew budget even though the total ceiling itself is working correctly.
The other issue is what happens when the reservation is denied. I would avoid simply throwing an exception and letting the delegating agent retry the same action, because that can turn a budget-control mechanism into another retry loop.
Instead, the tool could return a structured result such as:
budget_exceeded
remaining_budget
estimated_cost
retry_allowed: false
Then the delegating agent can make an explicit decision: use a cheaper tool, reduce the scope of the task, return a partial result, or stop that branch of the crew.
That also makes observability much better. At the end of a run you can see not just “the crew spent $X,” but which agent requested the spend, which tool reserved it, how much was estimated, how much was actually charged, and how much was rejected.
I’d probably treat the budget as a runtime policy enforced outside the agent reasoning, with the agent only receiving enough information to choose a fallback. That keeps the safety boundary deterministic while still allowing the crew to degrade gracefully when it hits the ceiling.
The static token estimate you mentioned could be useful as an initial reservation too. Then the runtime ledger mainly has to deal with the unpredictable part rather than trying to control everything after the fact.
The interesting question for me would be whether a denied reservation should propagate as a normal tool result that the agent can reason about, or whether certain budget thresholds should terminate the branch immediately. I suspect having both behaviors configurable per tool would be useful.