AI coding

Reserving Rate-Limit Capacity Alongside Token Spend

Antonio Evans10 min read

A single admission control desk drawn as a small booth.
Key points

Key points

  1. 1A token budget caps cumulative spend but says nothing about how many requests you have in flight, so a fan-out job can exhaust a provider's concurrency ceiling while token spend is still low.
  2. 2Reserve three things before each model call: a worst-case spend hold, a concurrency token, and a queue key that stays stable across retries.
  3. 3Derive the queue key from the logical work unit, not the attempt, otherwise every retry takes a new slot and stacks on the ceiling.
  4. 4Log which reservation blocked each rejected call (spend, concurrency, or queue) so you can tune the binding constraint instead of guessing.

Two ceilings, one bill

A token budget answers a single question: can I afford this call? Providers enforce a different question: how many calls may exist at once. OpenAI documents its limits as restrictions on how often a user or client can access the service in a period, and states that those limits are set per organization and project. Those limits are not configurable by you and come in at least two dimensions that matter here: tokens per minute (TPM) and requests per minute (RPM). A per-run dollar cap is invisible to both.

That distinction has a practical consequence. The per-run budget controller pattern prices each request's worst case, holds it against a run-scoped balance, issues the call, and refunds the difference after settlement, so the run halts before a call it cannot afford (per-run budget reservation). Notice what it does not do: it has no opinion about how many of those affordable calls you have open. If you fan out 200 workers against a plan that allows 50 concurrent requests, 150 of them are admissible on spend and blocked on throughput.

The second ceiling bites first precisely because it is cheap to hit. Fan-out multiplies request count without multiplying token volume per request. Five parallel workers reading five different files each send a modest prompt; that is five requests toward the RPM ceiling and a rounding error against the TPM ceiling. The subagent fan-out discussion makes the inverse point well: each spawned agent pays fixed per-agent context overhead, so fan-out is expensive in tokens too, but the requests are what the provider counts first when the ceiling is a request count rather than a token count.

So treat the two as separate reservations with separate failure modes:

ReservationWhat it protectsUnitTypical exhaustion signalRelease condition
SpendCumulative cost per run or tenantDollars, held at worst-case rateBudgetExceeded before dispatchSettlement, refund the delta
ConcurrencyIn-flight requests against a provider ceilingSlotsWait or reject before dispatchResponse, error, or timeout
Queue keyOrdering and de-duplication per logical unitString keyDuplicate key rejected at admissionTask terminal state

The spend hold is a number. The concurrency token is a lease. The queue key is an identity. All three must be acquired before the request leaves your process, and none of them is a substitute for the others.

Why the concurrency ceiling is the one fan-out finds first

The arithmetic is simple enough to do on a napkin. Suppose a run has a spend budget of 50 dollars at a blended rate of 10 dollars per million tokens, so it can consume 5 million tokens. Suppose the provider allows 60 requests per minute. A coordinator fans out 120 sub-tasks, each sending a 3,000 token prompt and expecting a 500 token answer. That is 3.5 million tokens, comfortably inside the spend budget, but it is 120 requests, twice the per-minute ceiling. Two thirds of the work waits, retries, or fails, and the spend budget is untouched the whole time.

Now add the retry behavior most fan-out code has by default. Each retry that re-enters the admission path without a stable key consumes a fresh request slot. If a third of the tasks retry once, the effective request count for that minute is 160 rather than 120, and the ceiling is further away from your budget than ever. The AI agent rate limiting analysis describes exactly this shape: one user prompt becomes a cascade of hundreds of internal calls, and a single logical error amplifies into a usage spike. The tricky part for the operator is that the spend dashboard shows a slow climb while the request error rate spikes.

The counterintuitive insight for search results that stop at "add a token budget": the budget is the wrong instrument for protecting throughput. Rate limits control how fast an agent can spend, not how much it can spend in total, and in-app counters that are not atomic let two concurrent requests both read the same remaining balance and both proceed (hard spend limits). Add the concurrency layer or you will be running an expensive admission test that answers a question nobody asked.

The reservation protocol

Three phases, in a fixed order. Hold spend, take a concurrency token, take the queue key, then dispatch. Release in reverse on every exit path, including timeout.

import time
from dataclasses import dataclass

@dataclass
class Slot:
    queue_key: str
    spend_hold: float

def reserve(run, queue_key, worst_case_usd, concurrency, lease_seconds=90):
    # 1. identity first: a duplicate key means the work is already queued
    if not run.keys.claim(queue_key):
        raise RuntimeError("duplicate work unit")
    # 2. spend hold at worst-case rate
    run.budget.hold(worst_case_usd)          # raises BudgetExceeded
    # 3. concurrency lease, bounded wait
    deadline = time.monotonic() + lease_seconds
    while not concurrency.acquire():
        if time.monotonic() > deadline:
            run.keys.release(queue_key)
            run.budget.release(worst_case_usd)
            raise TimeoutError("concurrency")
        time.sleep(0.05)
    return Slot(queue_key, worst_case_usd)

def release(run, slot, concurrency, actual_usd):
    run.budget.settle(slot.spend_hold, actual_usd)  # refund the delta
    concurrency.release()
    run.keys.release(slot.queue_key)

Ordering matters. Claiming the queue key first costs nothing and prevents duplicate work from ever holding a spend reservation or a concurrency slot. Taking the spend hold second means a request that fails the concurrency wait releases a hold that was never spent, which is cheap. Doing it the other way around means a fan-out burst temporarily holds budget for work that will never run, and your remaining balance understates what you can actually afford.

The release path must be in a finally block, not on the success branch. The most common production bug in this area is a token that leaks on timeout, which shrinks the effective ceiling by one slot per leak until the pool is empty and the only symptom is a queue that stops draining.

Deriving queue keys so retries do not stack

The queue key is the deduplication identity of a logical work unit. Get it wrong and retries consume new slots, which is the failure the concurrency layer exists to prevent. Get it right and a retried call is indistinguishable from the first attempt, so it either waits behind itself or is dropped as already handled.

Three rules cover most cases:

  1. Key on the work, not the attempt. Include run ID, task ID and the input content hash. Exclude the attempt number, the timestamp and the worker ID.
  2. Hash the inputs, not the prompt string. Prompts get reformatted, system messages get edited, and a formatting change should not turn a retry into a new unit of work. Hash the structured inputs the prompt is rendered from.
  3. Scope to the provider and model family. A fallback to a different provider is a different quota, so the key should carry the target so a failover does not collide with the primary path.
import hashlib, json

def queue_key(run_id, task_id, provider, inputs):
    payload = json.dumps(inputs, sort_keys=True, separators=(",", ":"))
    digest = hashlib.sha256(payload.encode()).hexdigest()[:16]
    return f"{run_id}:{task_id}:{provider}:{digest}"

A worked comparison shows why this matters. Take a run of 40 tasks, each with a 10 percent chance of a transient failure that triggers one retry. With attempt-scoped keys, expect about 44 request slots consumed on the first pass, because the 4 retries each take a new slot. With input-scoped keys, the retry rejoins the same key, and if the original call is still leased, the retry waits rather than opening a second call. That is roughly a 10 percent reduction in request volume at the ceiling, which converts directly into headroom for the next batch. On a 60 RPM ceiling, 40 tasks plus 4 stacked retries is 73 percent utilization on a single pass, leaving little room before the queue backs up.

There is a second benefit that shows up in incidents. When every request carries a stable queue key, your logs group by logical unit rather than by socket, so a stuck task looks like one key with many leases rather than a sea of anonymous 429s. The OpenTelemetry semantic conventions for generative AI give you the vocabulary for those spans: model parameters, response metadata and token usage as standard attributes across tools. Put the queue key on the span as an attribute and you can filter traces by work unit without touching application code.

Logging which reservation ran out first

The instrumentation question that determines whether you can tune the system at all is: when a call was rejected, which reservation said no? Four outcomes, four different fixes.

OutcomeWhat you logLikely fix
Spend rejectedreservation_blocked=spend, remaining balance, worst-case estimateRaise the run budget, lower the estimate, or reduce context per call
Concurrency rejectedreservation_blocked=concurrency, wait duration, pool size, in-flight countRaise the ceiling, reduce fan-out width, or stagger dispatch
Queue duplicatereservation_blocked=queue, key, lease ageInvestigate the upstream task; the retry is behaving correctly
Provider 429reservation_blocked=provider, response headers, retry-afterYour local ceiling is set above the provider's; lower it

The last row is the one that costs teams the most time. If your local concurrency pool is set higher than the provider's actual ceiling, you will pass your own admission test and still receive 429s. That means the number in your configuration is a guess, and the right move is to drive it down from observed 429s rather than up from intuition. A recent analysis of LLM rate limiting tooling notes that providers enforce RPM, TPM and spend at the account level while internal quotas should enforce the same dimensions per team, application and customer, and that without both layers a single runaway agent can exhaust provider capacity for everyone else (rate limiting at scale).

For replay and post-mortems, the spend reservation itself should be observable. The catalog of budget-overrun incidents documents 63 confirmed production cases across 21 orchestration frameworks, and a striking finding is that a delegation fan-out race rejected at compile time in a typed language overshot in 30 of 30 runs under plain asyncio, while three disciplined alternatives (including a properly locked Python counter) overshot in zero. The mechanism matters less than the discipline: one atomic place where the reservation is taken, one place where it is released, and a log line for every rejection.

A useful default: emit one span per logical work unit and one span per attempt, with the queue key on both and reservation_blocked on the attempt span. The practitioner guide to OpenTelemetry for AI systems lays out the span attribute set, including input and output token counts and finish reasons, which is exactly what you need to correlate a spend rejection with the context size that caused it. If agent framework telemetry already emits these spans, the gen AI instrumentation libraries attach the attributes for you and the queue key becomes the only field you add.

Calibrating the reservations

The spend hold and the concurrency pool need different calibration disciplines, and mixing them up is common.

Spend holds should over-reserve and settle. Reserving at the worst case and refunding the difference is what keeps the budget invariant true by construction; without the refund, worst-case reservations compound and the effective budget collapses after a handful of calls (per-run budget reservation). A static estimator can reserve several times actual cost, so pair the hold with honest settlement from the returned usage numbers rather than leaving the worst case in place. The token budget controls guide makes the point that a session-aware enforcement point between the orchestrator and the model API is where cumulative consumption becomes enforceable, because provider dashboards report aggregate usage and not per-session breakdowns.

Concurrency pools should be set from observed 429s, one notch below. Start at a conservative width, log every 429 with its retry-after header, and raise the pool only while the 429 rate stays at zero for a meaningful sample. Two hundred sequential requests with no 429 is better evidence than a benchmark table, and provider tier limits change without notice.

Queue keys should be treated as a schema. Once published, a key format change invalidates in-flight deduplication. Version the key with a prefix (v1:) so the migration is explicit rather than a silent double-spend.

One more calibration note from the cost side. Agent loops re-bill prior context on every call, so a naive N-step loop grows input tokens quadratically and a 20-step loop can consume more than ten times what a per-step estimate suggests (agent loop token costs). That matters for the spend reservation because the worst case per call grows with the loop's history, and a hold priced from the first call's context will under-reserve on the twentieth. Price the hold from the current context size, not from a fixed average.

When the client is an agent platform

The same three reservations apply when the caller is a coding agent or an orchestration framework rather than your own loop, and the split of responsibility shifts.

If you run a coding agent that fans out to subagents, the platform's own metering and your gateway's admission control are two different books. The subagent orchestration guide is explicit that each subagent starts with a fresh isolated context window and receives only the delegation prompt, which means each one is a distinct request that pays fixed overhead before doing useful work. That is fine for context hygiene and expensive for concurrency. A coordinator that spawns eight workers to investigate eight files should expect eight concurrent leases, and the pool must be sized for the fan-out width you actually use, not the width you imagined.

If your agents call several providers, an aggregator removes a chunk of this problem because the ceilings are managed behind one endpoint. Quanta Inference exposes text, image, video, audio and embedding models through one OpenAI-compatible API key with prepaid dollar pricing, which collapses the per-provider key rotation and multi-key pooling that otherwise sits in your admission layer. It does not remove the need for a queue key or a local concurrency pool, but it does mean the pool is one number rather than one per vendor.

For teams standing up the expert layer rather than the plumbing, Quanta Experts runs each expert against your context and returns finished deliverables, with the skill library available to install into a coding agent directly. The reservation discipline in this article is what keeps those experts inside a predictable cost and throughput envelope once several run at once. Whatever the runtime, the rule is unchanged: reserve spend, concurrency and identity before every call, and log which one said no.

FAQ

FAQ

Why does a token budget fail to stop throttling?

A token budget bounds cumulative spend, while providers enforce separate ceilings on request rate and concurrency, set per organization and project and not configurable by the caller (OpenAI rate limits). A fan-out job sends many small requests, which hits the request ceiling long before the token or dollar ceiling. You end up refusing affordable work, or receiving 429s while the budget still has headroom.

Which reservation should I take first, spend or concurrency?

Take the queue key first, then the spend hold, then the concurrency lease. The key is free to claim and prevents duplicate work from ever occupying a slot. Taking the spend hold before the concurrency wait means a failed wait releases a hold that was never spent, which is harmless, whereas the reverse order ties up budget for work that will never run.

How do I stop retries from stacking on the ceiling?

Key the queue on the logical work unit rather than the attempt: run ID, task ID, provider and a hash of the structured inputs, with no attempt number or timestamp. A retry then either waits behind the original lease or is dropped as already handled. In a 40 task run with a 10 percent retry rate, this saves roughly four request slots per pass, which is real headroom at a tight ceiling.

What should I log to make the system tunable?

Log one field, reservation_blocked, with the values spend, concurrency, queue or provider, plus the remaining balance or wait duration and the queue key. These four outcomes have four different fixes, and without the field you cannot tell whether to raise the budget, widen the pool, or lower your local ceiling to match the provider's actual limit. Put the queue key on the span so traces group by work unit.

Does a gateway replace the need for this?

No. A gateway handles multi-key pooling and failover, and it can enforce token-aware limits per virtual key, but it will not derive a stable queue key for your retries or tell you which reservation ran out first in your own code. The gateway is the provider-facing ceiling; the reservations described here are the caller-facing discipline that keeps you under it.

Sources

Sources

  1. Rate limits | OpenAI API
  2. Per-Run Budget Reservation for Coding Agent Model Calls
  3. Subagent fan-out can increase usage because each agent ...
  4. AI Agent Rate Limiting is Broken
  5. AI Agent Budget Control: Enforce Hard Spend Limits - Cycles
  6. OpenTelemetry for Generative AI
  7. 5 Tools for Rate Limiting LLM APIs at Scale
  8. Token Budgets: An Empirical Catalog of 63 LLM-Agent ...
  9. OpenTelemetry for AI Systems: LLM and Agent ...
  10. Token Budget Controls for AI Agents: Implementation Guide
  11. AI Agent Loop Token Costs: How to Constrain Context
  12. Claude Code Subagents and Multi-Agent Orchestration ...
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