Use when an agent harness must respect upstream model/tool rate limits, manage quotas across concurrent sessions, and handle 429s without dropping work. Apply when designing the queue/throttle layer in front of model and tool calls. Covers token-bucket vs leaky-bucket, per-key vs global quotas, backoff strategies (exponential, jittered), queue fairness across sessions, and 429/Retry-After handling.
Use when an agent harness must respect upstream model/tool rate limits, manage quotas across concurrent sessions, and handle 429s without dropping work. Apply when designing the queue/throttle layer in front of model and tool calls. Covers token-bucket vs leaky-bucket, per-key vs global quotas, backoff strategies (exponential, jittered), queue fairness across sessions, and 429/Retry-After handling.
Rate Limiting
Pain Signals — You Need This Pattern When:
Bursts of agent activity hit the model API's RPM/TPM ceiling and 429s spike
One greedy session starves others on a shared API key
Backoff retries pile on during incidents — making outages worse
Tool calls (external APIs, DB) have their own rate limits and the harness ignores them
Cost spikes because retries on 429 doubled the bill before respecting Retry-After
Rate limiting is bidirectional. The harness must respect upstream limits and fairly allocate its own capacity across concurrent users/sessions.
Core Principle
Treat upstream limits as shared resources the harness must schedule against. Three interacting concerns:
Predictive limiting — know your budget and stay under it
Reactive handling — when you hit a limit anyway, back off correctly
Fair allocation — when capacity is scarce, distribute it across sessions
Anthropic API (and most LLM APIs) enforces multiple concurrent limits. All matter:
Limit
Unit
What hits it
RPM
requests / minute
Many small calls
ITPM
input tokens / minute
Long-prefix calls
OTPM
output tokens / minute
Long generations
Concurrent
in-flight requests
Many parallel sessions
The harness can hit any of these. Track all four. Predict against all four.
Token Bucket
Token-bucket is the right default. A bucket of capacity C refills at rate R; each request consumes 1 token (RPM bucket) or N tokens (TPM bucket). Empty bucket → request waits or rejects.
After response, reconcile estimate vs actual; adjust bucket if undersized. Over-estimate is safer — wastes some capacity but avoids breaches.
429 Handling
429 means you exceeded the limit despite predicting. Recover correctly:
Read Retry-After — server says when to retry. Honor it precisely; do not retry sooner.
Don't immediately retry without honoring — adds load to a service already saying "back off."
Update local bucket — your prediction was wrong; recalibrate.
Don't compound — many concurrent requests retrying without coordination = thundering herd. Use a singleton bucket; failures wait on the bucket, not on independent timers.
asyncdefcall_with_retry(request, max_retries=3):
for attempt inrange(max_retries + 1):
try:
returnawait call_model(request)
except RateLimitError as e:
wait = e.retry_after or backoff(attempt)
wait += random.uniform(0, wait * 0.1) # jitterawait asyncio.sleep(wait)
raise BudgetExhausted()
Backoff
When Retry-After is missing, backoff is your fallback:
Strategy
Use
Exponential + jitter
Default. Doubles each attempt; jitter prevents synchronization.
Linear
Tighter timing requirement, low retry count
No backoff
Never. Retrying immediately is always wrong on 429.
Cap max wait. After N attempts or T total wait, fail the request; don't retry forever.
Fair Allocation
In multi-session harness, one session can monopolize the shared bucket. Defenses:
Per-session cap prevents one session from consuming >X% of the global budget at once.
Queue Discipline
When multiple requests wait on the bucket, FIFO is the default. Edge cases:
Priority — interactive turns over batch jobs; size-based (small first to reduce average wait)
Deadline-aware — drop requests whose deadline has passed
Cancellation — cancelled session's queued requests must be removable
Queue depth is a load metric; alarm when it grows.
Tool Rate Limits
Don't only think about the model API. Tool calls hit external services:
GitHub API: 5000 req/hr authenticated
Search APIs: per-key quotas
Internal services: their own RPS limits
Treat tool calls like model calls: per-tool buckets, predictive consumption, 429 handling. The agent loop fanning out tool calls can DoS your own backends.