Multi-agent crews and runaway spend anyone solved delegation without a shared budget ceiling?

Running a crew where one agent delegates sub-tasks to others, and a couple of those sub-agents have tools that call paid services. The delegation pattern is great for capability, but it also means I’ve got N agents each independently deciding to spend money, with no shared awareness of a budget across the crew.

Concretely: agent A delegates a research task to agent B, B decides it needs three paid API calls to do it well, and there’s nothing stopping B from deciding that three more times if the first pass isn’t satisfying. Multiply that across a crew of five and the blast radius if something misfires is a lot bigger than a single-agent chain.

Has anyone built (or found) something like a shared spend ceiling that the whole crew respects, independent of which agent is making the call? Interested in whether people are handling this at the crew config level or bolting on something external.

The reason this bites specifically in a delegating crew is that the budget lives in the wrong place. Each agent’s LLM only reasons about its own task — there’s no natural spot in the delegation flow where “how much has the whole crew spent so far” is visible. So no individual agent can respect a ceiling it can’t see, no matter how you prompt it.

The pattern that’s actually held up is to move the budget out of the agents entirely and into a single shared object the tools consult, not the LLMs:

  • A crew-scoped ledger (one counter, created per run) that every paid tool wraps. Before B’s API tool fires, it does a check-and-reserve against that shared counter — estimate the call’s cost, atomically reserve it, and refuse if the reservation would breach the crew ceiling. Reconcile to the actual charge after the call returns.
  • Because it’s the tool enforcing it, not the agent, it doesn’t matter which agent (A, B, or B’s third retry) is calling — they all debit the same pool. That’s what makes it a genuine crew-wide ceiling instead of five independent per-agent limits that sum to 5× your intended cap.
  • The retry-storm you describe (B deciding it needs three more calls) is exactly what this catches: each retry hits the same shrinking pool, so the third pass gets denied by the ledger even though B’s reasoning still “wants” it.

CrewAI doesn’t hand you this at the config level today as far as I can tell — you bolt it on by wrapping your paid tools with the reserve-check. A dependency-injected budget object passed into the crew and closed over by each tool wrapper is the least-invasive way I’ve found to do it.

One thing worth separating out: a chunk of a crew’s spend is predictable before the run even starts — the system prompts + full tool schemas get re-sent and re-billed on every turn of every agent, and that part is countable statically. Bounding it up front (I use npx @wartzar-bee/tokenscope for the static estimate, Apache-2.0) means your runtime ceiling only has to police the genuinely variable spend, which makes the ceiling much easier to set at a sane number.

Genuine question back: when a reservation is denied mid-crew, are you aiming to hard-fail the whole crew, or degrade the delegating agent onto a cheaper path? The “deny gracefully instead of crashing the run” behavior is the part I’ve found hardest to get right.

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.

The “budget lives in the wrong place” framing is the key insight in this whole thread — once the ceiling is something the tools consult instead of something the LLM is supposed to remember, most of these failure modes stop being possible instead of just being handled well.One thing I’d add: does this ledger persist only for the duration of a single crew run, or across runs too? Concurrent crews sharing one budget pool is where I’ve seen this get genuinely hard — the atomic-reserve pattern works cleanly within a process, but once you’ve got multiple crew instances (or scheduled runs) drawing against the same account-level ceiling, you need the ledger to live outside any single run’s memory entirely.

That’s basically the generalized version of what you’ve built here — instead of a per-crew object, each agent gets an actual wallet with a hard limit and a kill switch, so the reservation logic is infrastructure the crew calls into rather than something you re-wire per project.

Persistence is exactly the seam where this stops being a code pattern and becomes an infrastructure decision, so it’s worth splitting into two ledgers that people tend to conflate:

  • The reservation ledger (the atomic hold @Matthewjon is describing) only needs to live as long as the inflight calls it’s guarding. Its whole job is the estimate→reserve→execute→reconcile window, so it can be per-run and in-process — as long as the reserve is genuinely atomic.
  • The spend ledger — “how much of the ceiling is gone” — is the one that has to outlive any single run the moment you have concurrent or scheduled crews. Once two crew instances draw against the same account-level cap, an in-process counter can’t see the other’s spend, so the hold has to be taken against a shared durable store, not a Python object.

The mistake I made first was trying to make one object do both jobs. Separating them means the durable part can be dumb: a single row you decrement under a real lock (SELECT … FOR UPDATE, or a Redis INCRBY/Lua check-and-reserve) so the atomicity @Matthewjon flagged holds across processes, not just within one. The reconcile step then writes the true charge back to that same durable row. The “each agent gets an actual wallet with a hard limit and a kill switch” generalization you’re pointing at is basically this: the wallet is the durable ledger, and the per-run reservation object is just a short-lived lease against it.

One thing that makes the durable ceiling much easier to set at a sane number: a big chunk of a crew’s spend is the fixed overhead I mentioned above — system prompts + full tool schemas re-sent every turn of every agent — which you can bound statically before the run even starts. If the predictable part is already accounted for, the durable ledger only has to police the genuinely variable spend, which is a far smaller and less jumpy number to threshold on.

Question back at you: for the account-level wallet, are you reconciling against the provider’s actual billed cost (post-hoc, from usage records) or holding against your own pre-call estimate and never truing it up? The drift between estimate-at-reserve and actual-at-bill is the thing I’ve found decides whether a hard kill-switch fires too early or too late.

Yes, I think the reserve should stay an estimate rather than being treated as the final charge. The important part is that the reservation gives us a hard upper bound before the call, while the actual provider usage becomes the source of truth afterward.

My preferred flow is still estimate → atomic reserve → execute → reconcile. The reserve protects the shared ceiling while the call is in flight, and once the provider returns the actual usage/cost, the difference gets settled back into the durable spend ledger.

That also makes the distinction between the two ledgers useful. The reservation is temporary protection against concurrent calls, while the durable account wallet represents the actual accumulated spend. If the estimate was higher than the final charge, the unused amount is released; if the actual charge is higher, the reconciliation accounts for that difference before another reservation is allowed.

I agree that this is where the kill-switch behavior gets interesting. I wouldn’t want an estimate to permanently consume budget, because conservative estimates could otherwise stop a crew unnecessarily. At the same time, I wouldn’t want reconciliation to happen only at the end of an entire crew run, because concurrent calls could overshoot the account ceiling in the meantime.

So for me the key invariant is that the reservation is authoritative while work is in flight, but actual provider usage is authoritative once the call completes. The durable wallet then remains the shared source of truth across crew instances and scheduled runs.