AI coding

Proxying Reservations for Sub-Agents on an Agent Platform

Antonio Evans8 min read

A cutaway view of a message queue where a broker process sits between a parent agent on one side and five sub-agent workers on the other.
Key points

Key points

  1. 1A reservation broker holds capacity in a shared pool, assigns a fixed share to each sub-agent when it spawns, and moves every allocation and release through the same atomic ledger.
  2. 2Without per-sub-agent ledger entries, provider dashboards only show aggregate spend, so you cannot attribute fan-out cost to the agent that caused it.
  3. 3Platform overrides (a parent raising or lowering a child's allowance) must go through one endpoint that writes the same ledger entries, never a side channel to the downstream service.
  4. 4A fixed decision-log schema (request ID, sub-agent ID, action, amount, reason, resulting balance) lets the platform reconcile its own records against the broker without guesswork.

Where the broker sits, and what it holds

The reservation broker is a thin hop between the agent platform and every downstream service that has no spend reservation of its own. Downstream might be a model API with only requests-per-minute and tokens-per-minute caps, a search provider billed per call, or an internal microservice. Hosted providers set those limits at organization or project scope and expect you to handle finer grain yourself agentgateway's analysis of provider limits. The broker adds the missing layer: it sees call history, session identity, and cumulative consumption before dispatch the token budget controls guide.

The broker keeps four things:

  1. The pool. A single tracked balance for the parent run, plus whatever the platform has granted the tenant above it.
  2. Allocations. A fixed number of capacity units held per sub-agent, created at spawn and closed at termination.
  3. The ledger. Append-only entries for every grant, hold, settle and release, keyed by sub-agent ID.
  4. The decision log. One structured record per admission decision, including refusals.

Two properties matter more than the data model. First, admission must be prospective: price the next request at its worst case, check it against the remaining balance, and refuse before the money leaves the per-run budget reservation pattern. Second, the check and the deduction must be one atomic operation, because two concurrent workers that both read "remaining: 10" will both proceed and spend 20 Cycles' write-up on hard limits.

A minimal schema for the allocations table:

CREATE TABLE reservation (
  reservation_id   TEXT PRIMARY KEY,
  parent_run_id    TEXT NOT NULL,
  subagent_id      TEXT NOT NULL,
  currency         TEXT NOT NULL,          -- ledger unit, e.g. "usd_micro" or "tokens"
  granted          BIGINT NOT NULL,        -- lifetime ceiling for this sub-agent
  held             BIGINT NOT NULL DEFAULT 0,
  spent            BIGINT NOT NULL DEFAULT 0,
  state            TEXT NOT NULL,          -- open | draining | closed
  version          BIGINT NOT NULL,        -- optimistic concurrency
  spawned_at       TIMESTAMP NOT NULL,
  closed_at        TIMESTAMP
);

granted - held - spent is the sub-agent's headroom. Every decrement of headroom goes through one UPDATE ... WHERE version = ? statement so the read and write cannot interleave.

Sizing allocations at spawn time

Do not divide the pool evenly. Fan-out multiplies fixed overhead: every spawned sub-agent is a fresh model context that may need to carry system instructions, repo instructions, tool schemas, the skill catalogue, and the delegated task before it does any useful work the Codex subagent fan-out issue. Five agents investigating five small slices of the same repository can each reload substantially the same baseline, so an even split over-provisions agents that will never spend it.

A workable decision rule, expressed as constants you tune per provider:

Sub-agent typeAllocation formulaRationale
Research or search worker2.5 x worst-case single call, floored at 3 callsWorst-case reservations over-reserve by roughly 2.1x median to 4 to 6x actual depending on the estimator, so the floor keeps small workers usable while unused hold is refunded at settlement per-run budget reservation
Coding or test runner6 x worst-case single callRetries and file reads dominate; loop detection alone will not stop a productive-looking retry storm
Reviewer or auditor1.5 x worst-case single callBounded input, one pass, no tools that write
Background pollerhard wall-clock cap, 60 to 120 seconds, plus a small token allowancePrevents a low-priority poller from consuming capacity a parent needs for blocking work, the priority inversion failure Dresher documents at nine agents 9 AI Agents, One API Quota

A worked example. Suppose the parent run is granted 500,000 capacity units in USD micros, and the platform spends 14,600 per worst-case call at cache-write rates for a 1,200-input, 250-output request, which settles near 10,000 once the real token mix returns per-run budget reservation. Spawn four research workers:

  • Each gets 36,500 (2.5 x 14,600), flooring to 3 calls.
  • Total allocated: 146,000, or 29 percent of the pool.
  • The parent keeps 354,000 as unallocated headroom.
  • If two workers finish having spent 21,000 each, 31,000 returns to unallocated headroom at close.

That last step is the whole point of settlement: without a refund, worst-case holds compound turn over turn and the effective budget collapses to maximum / worst-case ratio after a handful of calls per-run budget reservation.

Writing per-sub-agent ledger entries

Every state change is an entry. Treat the ledger as the system of record; the reservation row is a materialized view you can rebuild from it.

{
  "entry_id": "led_01J8Z4M2QK",
  "request_id": "req_01J8Z4M1AB",
  "parent_run_id": "run_7741",
  "subagent_id": "research-agent-3",
  "action": "hold",
  "units": 14600,
  "currency": "usd_micro",
  "reason": "admission_worst_case",
  "balance_after": 231400,
  "ts": "2026-01-14T09:12:03.881Z",
  "idempotency_key": "req_01J8Z4M1AB:hold"
}

Three conventions keep attribution clean.

Units, not floats. Sub-cent arithmetic on floats accumulates rounding across hundreds of settlements, so the reference implementation runs on exact fractions per-run budget reservation. Store integer micros or tokens and format at read time.

Idempotency keys on every write. A retried HTTP call to the broker must not create a second hold. Key on request_id plus the action, and reject duplicates with the original entry returned.

Four actions only. grant (allocation created at spawn), hold (admission check passed), settle (actual cost moved from held to spent, delta refunded), release (allocation closed, remainder returned to headroom). If you are tempted to add a fifth action for a special case, you probably need a new reason on an existing action instead.

Then reconcile on a schedule. Aggregate the ledger by subagent_id for the run and compare against what the parent platform believes it consumed. When they disagree, the ledger wins, because it was written before the spend happened rather than sourced from a provider dashboard that reports only aggregate usage token budget controls.

A practical note on where the numbers come from: two writers have reported changing the arithmetic of multi-agent spend more by narrowing context and tiering models than by any cap. One team running six agents described going from roughly 75 dollars a month to 35 with tiered models, then to 18 with prompt caching and sub-agent timeouts the Dify discussion thread. Those figures are self-reported and workload-specific, but the direction is the same as the per-run reservation guidance: cap first, then optimize what you cap.

Handling platform overrides

Overrides arrive from the parent platform, not from the sub-agent. A parent might raise a child's ceiling because the child found real work, lower it because a fan-out is burning capacity, or zero it to halt a looping agent. The mistake to avoid is letting the platform call the downstream service directly to "top up" allowance, which silently desynchronizes the ledger.

Expose exactly one endpoint:

POST /v1/reservations/{subagent_id}/override
{
  "override_id": "ovr_9912",
  "requested_by": "parent:run_7741",
  "new_granted": 60000,
  "reason": "worker_found_scope_beyond_plan",
  "expected_version": 4
}

Rules that make it safe:

  • Writes a ledger entry, always. Every override produces a grant or release entry with the reason string, so the audit trail never has an unexplained balance change.
  • Version check required. expected_version makes concurrent overrides fail loudly rather than last-writer-wins. A parent and an operator policy engine overriding the same child at the same instant is a real scenario once you have any automated guardrails.
  • Deny by default below spent. A new ceiling lower than spent is legal, but it immediately puts the child in draining: in-flight requests settle, new admissions are refused. This is graceful degradation rather than hard termination, so partial task progress is retained token budget controls.
  • Cap the override itself. The parent cannot raise a child beyond the parent's own unallocated headroom plus the child's current hold. Otherwise overrides become a way to breech the pool the broker exists to protect.
  • Rate limit overrides. Not because they are dangerous individually, but because an agent loop that discovers the override endpoint will call it. The thundering herd pattern applies to overrides as much as to 429 retries: nine agents retrying at the same instant triggered a second and third wave of rate limit errors and burned a 5,000 request hourly quota in 90 seconds in Dresher's logs 9 AI Agents, One API Quota.

One more override to support: platform-level kill of a whole parent run, which closes every open reservation in one transaction and writes a release entry per child with the same reason. Cleaning up twenty orphaned reservations by hand at 3 a.m. is the failure mode you are preventing.

A decision-log schema for reconciliation

The ledger answers "what was spent by whom." The decision log answers "why did the broker say yes or no." Keep them separate but joinable on request_id.

{
  "decision_id": "dec_01J8Z4M1AB",
  "request_id": "req_01J8Z4M1AB",
  "parent_run_id": "run_7741",
  "subagent_id": "research-agent-3",
  "action": "allow",
  "priced_worst_case": 14600,
  "available_before": 246000,
  "available_after": 231400,
  "reason": "admission_worst_case_cache_write_ceiling",
  "pricing_table_version": "pt_2026_01",
  "decided_at": "2026-01-14T09:12:03.879Z"
}

Field discipline worth adopting:

FieldWhy it must be stableCommon mistake
request_idJoin key to ledger and to the platform's own tracesGenerating a new ID at the broker, breaking the join
subagent_idAttribution for chargeback and fan-out analysisReusing the parent ID for children
actionFilterable enum: allow, deny, degrade, override_applied, closedFree-text outcomes that cannot be aggregated
priced_worst_caseShows whether a refusal was correct or over-pricedOmitting it, so denials cannot be audited
reasonExplains the decision to a human at 3 a.m.Logging the exception class instead of the business reason
available_afterDirect reconciliation against the platform's balanceLogging only the delta

Refusals are the records you will read most. A spike in denials for one subagent_id is the earliest signal of a runaway loop, earlier than a provider dashboard, which reports after the fact. Alert on anomaly rather than outright failure: a sudden jump in token consumption can indicate a reasoning loop even when every individual call succeeded Fastio's observability checklist. Pair that with a loop signal based on call signatures (tool name plus arguments) rather than raw call volume, because a legitimate planning agent issues many distinct calls while a broken one repeats a few token budget controls.

Shipping this to production usually happens in two steps. First, run the broker in shadow mode: it prices and logs every decision but forwards all traffic, then you compare its refusals against what actually happened. Second, turn on enforcement for one sub-agent class at a time. University of the estimate accuracy matters less than the invariant holding by construction: cumulative spend never exceeds the granted ceiling per-run budget reservation.

Common failure modes in the broker itself

Unbounded recursion. A sub-agent spawns a sub-agent that does not have a reservation because the broker only meters calls it sees directly. If your platform allows grandchildren, the broker must require a reservation ID on every spawned agent and reject spawns that do not present one, or the delegation fan-out race documented in budget-overrun incidents simply moves one level down Token Budgets: an empirical catalog.

Reservations that outlive their run. An orphaned hold is capacity nobody can use. Sweep on a timer: close any open reservation whose parent_run_id is terminal upstream, and write a release entry with reason orphan_sweep.

Trusting the model to know its budget. Agents are poor at estimating their own remaining allowance; a benchmark that trained agents to predict their budget interval found coverage capping well below certainty, so the broker must price externally from its own table per-run budget reservation. This also means the broker must fail closed: if actual cost exceeds the hold, or returned usage is malformed, treat it as an overspend and block the run rather than warning.

One counter shared by all tenants. Provider limits are velocity controls, not budget controls. A per-process counter breaks under concurrency, is lost on crash, and does not survive restarts Cycles on hard limits. Use a shared store with compare-and-swap, or an existing gateway that already does hierarchical quotas per team and per key five LLM rate-limiting tools compared.

Over-tight caps. Aggressive thresholds terminate valid multi-step work. Test enforcement against real workloads before rollout, since under-tuned caps fail to stop loops and over-tuned caps kill legitimate research runs token budget controls.

If you would rather not operate the metering and routing layer yourself, Quanta Inference puts text, image, video, audio and embedding models behind one OpenAI-compatible API key with prepaid US dollar pricing, which gives the broker a single upstream to price against: https://quantaintelligence.ai/inference. The broker pattern in this article then wraps a much smaller surface than a per-provider integration.

FAQ

FAQ

Why not just set a spend limit on the provider account?

Provider-side caps operate at organization, project, or key scope, and the limits themselves are set by the provider rather than by you agentgateway's analysis. They cannot express "this sub-agent gets 36,500 units and nothing more," and they cannot attribute a fan-out spike to the agent that caused it. The broker exists precisely because the granularity you need does not exist upstream.

Should the allocation be a hard cap or a soft target?

Hard cap on admission, soft on utilization. A sub-agent that exhausts its grant gets refused or degraded, and the parent decides whether to raise it through the override endpoint. Making the grant advisory defeats the purpose, because the whole value is a decidable precondition before the call is issued per-run budget reservation.

How do I price a call I cannot predict exactly?

Price at the highest applicable rate: ordinary input at full price, cached input at its rate, cache writes at their premium, and every output token at full rate. The reference example reserves 14,600 units against a 1,200-input, 250-output call using cache-write ceilings and settles near 10,000 once the real mix returns per-run budget reservation. The refund on settlement recovers the slack.

What happens to a reservation when a sub-agent crashes?

The same thing as a normal close, triggered by a supervisor rather than the agent. The sweep closes the reservation, releases the unused remainder to the parent's headroom, and writes a release entry with reason agent_terminated. Because settlement already moved actual cost to spent, nothing is returned that was genuinely consumed.

Can the parent platform keep its own books and still reconcile?

Yes, if the broker's log schema is stable and joinable. The minimum is a shared request_id on every call the platform makes through the broker, plus subagent_id, action, amount, reason and resulting_balance on every decision. Aggregate the ledger nightly per sub-agent and per parent run, compare against the platform's records, and treat any unexplained delta as a broker bug to investigate, not a rounding artifact.

Sources

Sources

  1. Provider Rate Limiting is Not Enough for Enterprise LLM ...
  2. Token Budget Controls for AI Agents: Implementation Guide
  3. Per-Run Budget Reservation for Coding Agent Model Calls
  4. AI Agent Budget Control: Enforce Hard Spend Limits - Cycles
  5. Subagent fan-out can increase usage because each agent ...
  6. 9 AI Agents, One API Quota, The Rate Limiting Problem ...
  7. Best practices for managing AI Agent cost and token usage in production
  8. AI Agent Production Best Practices: A Complete Guide
  9. Token Budgets: An Empirical Catalog of 63 LLM-Agent ...
  10. 5 Tools for Rate Limiting LLM APIs at Scale
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles