Rate Limiting and Quota Management for Agent Web Tools
Agents multiply API costs and quota pressure in ways traditional rate limiting wasn't built for.

A single agent task can fan out into dozens of API calls, each with its own cost and its own quota exposure, and traditional rate limiting was never built to handle that kind of multiplication. This piece lays out why the old request-counting model breaks under agent traffic, and what a layered system that accounts for tokens, cost, and coordination actually looks like in practice.
Why agent web tools break the assumptions that traditional rate limiting was built on
Traditional rate limiting grew up in a world where one user action produced roughly one API call, and that call cost roughly the same as the one before it. A login, a page load, a form submit: each maps cleanly to a single request. That is why counting requests per minute was ever a sensible way to protect a system.
Agents don't work that way. A task like "research this company and draft an outreach email" can expand into dozens of LLM requests and a very large number of tokens, all triggered by a single user click. The real load on a system is tasks per minute multiplied by the average number of steps per task, multiplied again by a retry multiplier when things go wrong. Most teams only measure the first number, and they find out about the other two during an outage or when a bill arrives that nobody can explain.
Agent traffic is bursty, high-volume, and automated by nature, which makes it look identical to a DDoS attack or bot scraping to any rate limiter that's just counting requests. That blunt counting can throttle a legitimate agent doing real work while a low-volume abuser slips through untouched.
Web-tool calls make the exposure worse, because every single fetch is a quota event against two different systems at once: the LLM provider, which has to process however many tokens the page contains, and the target site, which is tracking requests, fingerprints, and anti-bot signals of its own. The format of what gets returned changes how expensive that fetch is. Raw HTML pages carry far more tokens than clean, structured text, and a tool like Context, which converts URLs into LLM-ready Markdown before an agent ever sees the content, can cut token consumption per fetch by orders of magnitude. That single design choice moves the bottleneck away from token quotas and puts control back in the agent's hands.
The three failure modes that emerge when agents share quotas without coordination
Tamir Dresher's Squad framework, a multi-agent system orchestrating code review, architecture decisions, infrastructure, and documentation, ran into this problem directly when nine agents launched at once. Every agent retried at the same moment.
When multiple agents share the same API quota but each handles rate limiting on its own, their retry behaviors interact, and the failures that result are worse than any single agent would cause alone.
The first is a thundering herd. Every tool Dresher evaluated for this, including Azure API Management, Resilience4j, and LangGraph, treats rate limiting as something each caller handles on its own. That independence doesn't protect the system. It amplifies the collision, because none of the agents know what the others are doing.
The second is priority inversion. In the Squad case, a background agent named Ralph was running low-priority issue triage, and it consumed the Copilot completions that Picard, the lead architecture agent, needed for critical decisions. Both agents had equal retry priority, and nothing in the system could say "this one goes first".
The third is cascade amplification. When the window resets, queued work across every agent flushes at once, and that immediately re-triggers the limit. One secondary rate-limit event turned into a system-wide outage, because every agent released its buffered queue at the same instant.
Web-fetch tools sit inside all three of these failure modes at once. A web fetch that fails and retries adds to the thundering herd, competes for shared quota the same as any other call, and piles into the same cascading queue, while also risking an IP ban or a fingerprint block from the target site itself. If several agents share a quota, that's a coordination problem. If you fix one agent's backoff logic in isolation, you still haven't addressed what happens when several agents hit the same shared quota at once.
Why request counts are the wrong unit
If you count requests, a fifty-token metadata call and a ten-thousand-token reasoning call count as the same event, so a handful of expensive calls can drain a quota while the request counter still shows plenty of room left. An agent request can cost far more than a typical human request, but a request-count limiter assigns both the same weight.
Token-based rate limiting fixes this by counting what actually gets consumed: prompt tokens in, completion tokens out, total tokens. Most LLM providers already return token counts in the response, the way OpenAI's usage.total_tokens field does, so a rate limiter can read that figure after every single response and deduct it from a running allowance.
Token counts still don't capture everything that matters. Cost-based quotas extend the same logic by capping spend per hour or per task rather than capping request volume, which catches a runaway agent burning expensive model calls in a way that a requests-per-minute limit never will.
In practice, several limit dimensions fire at once: requests per minute, tokens per minute, concurrent connections, daily spend. An agent gets throttled by whichever one it hits first, so rate limiting logic has to track all of them together rather than picking one and assuming it covers the rest.
Web-tool calls make this concrete. The same page, fetched as raw HTML, can blow past a token budget that the identical content, delivered as clean Markdown, fits into comfortably. The format a web-fetch tool returns decides how fast it burns through a token quota.
One more distinction matters for how a system responds when it gets throttled. A quota-exceeded error means the client went over its own limit, and the fix is to back off and correct the rate limiter. An overloaded or service-unavailable error means the provider itself is saturated, and the right response is to back off and consider failing over to a different provider. Retry logic that treats both signals the same way is solving the wrong problem half the time.
The layered control architecture: traffic lights, shared pools, concurrency caps, and priority queues
The fix for a multi-agent quota problem is a centralized Rate Governor that every agent checks before making a call, and it needs four layers working together, not one clever algorithm.
The first layer is traffic-light throttling, and it's proactive rather than reactive. A system doesn't need to wait for an error to find out it's close to a limit. Provider response headers, like x-ratelimit-remaining-requests and x-ratelimit-reset-requests, tell you the remaining quota after every single call, and Retry-After appears specifically on rate-limit or service-unavailable responses. Dresher's pattern assigns colors to quota levels: green above 40% remaining means normal operation, amber means background agents start adding proportional delays, and red means background agents park entirely, standard agents slow to one request per second, and only critical agents pass through untouched. This is what stops a thundering herd before it starts: agents slow down before they hit the wall instead of crashing into it together and retrying in unison. CLI wrappers often don't expose rate-limit headers the way direct REST clients do, so this pattern depends on calling the API directly.
The second layer is a shared token pool, and it's what solves priority inversion. Instead of each agent keeping its own independent allocation, all agents draw from one centrally tracked quota counter, so an idle background agent's unused share can get borrowed by a higher-priority agent that needs it. Redis-backed distributed counters are the standard way to implement this across multiple hosts, and pub/sub notifications for quota availability keep agents from wasting cycles polling for updates. Per-agent limiters that each look polite on their own can still blow through an organization's limit in aggregate, so centralization has to happen per provider key, not per agent.
The third layer is a concurrency semaphore, which caps in-flight requests regardless of rate. A reasonable starting point is 5 to 10 concurrent LLM calls per provider, tuned upward from there, because concurrency limits (not just RPM or TPM figures) are what actually stop production pipelines at providers that publish per-account connection caps. A token bucket smooths burst rate down to a sustained average, while the semaphore caps how many requests can be in flight at once. Both are needed, because they constrain different dimensions of the same traffic. Setting the bucket comfortably below the actual provider limit leaves headroom for retries rather than eating into the ceiling itself.
The fourth layer is a priority queue, and it stops low-priority work from starving high-priority work. When demand exceeds capacity, interactive, user-facing tasks need to jump ahead of background jobs. Priority has to be a scheduling dimension built into the system from the start, not something bolted on after the fact. Quota should get checked before a multi-step workflow begins, not partway through it: if a task needs five calls to finish, the governor verifies quota for all five before the first one goes out, because the alternative is partial results and wasted quota on work that can't complete.
Exponential backoff with jitter still belongs in this stack, but it's a recovery mechanism, not a prevention one. On a 429, the standard pattern waits, then retries with doubling delays, roughly 1 second, 2, 4, 8, up to a configured maximum, before failing permanently. Random jitter added to each wait interval keeps agents from retrying at the same instant; leaving it out reproduces a thundering herd. Backoff handles what happens after a limit gets hit.
Budget guards and kill switches as availability controls, not just cost controls
Token and request limits exist to protect availability. Dollar caps exist to protect against runaway spend. An agent failure and a cost spike are usually the same event seen from two different angles, so both need to be enforced by the same runtime mechanism rather than split across separate systems.
A bug that drops an agent into a loop can drain a month's API budget in minutes, so the rate limiter and the cost guard both need to sit in the critical path of every single call. Neither one is optional backup for the other.
A working budget architecture layers several guards on top of each other. A per-task token cap terminates the task and writes a checkpoint once cumulative consumption crosses the ceiling. A per-task dollar cap terminates the task and sends an alert, with the acceptable range differing for interactive work versus batch work. A per-user daily cap soft-blocks the user or offers an upgrade path, so no one is left confused about why nothing's happening. A per-feature monthly budget triggers a downgrade to a cheaper model at the same capability tier before anything hard-stops. An org-level kill switch halts all non-critical agent traffic and pages whoever's on call once spend crosses a multiple of expected daily spend.
Two details separate a guard that actually works from one that just looks good in a slide deck. Cost needs to get computed incrementally on every call, not totaled up after the task ends, because token counts come back in every API response and there's no reason to wait. And the kill switch has to be a real, tested code path, a config flag checked at the top of the gateway that can be flipped in under a minute.
This connects directly back to the traffic-light logic from the layered architecture. If a system already checks token headroom before every request, it loses very little by checking dollar headroom the same way.
Web-tool calls are a specific amplifier here too. A raw HTML page fed straight into an LLM consumes far more tokens than the same content converted into clean Markdown first, which makes the extraction layer a budget control in its own right, not just a formatting nicety.
Adaptive throttling, batch APIs, and caching as quota multipliers
Everything so far is about preventing failure. Three more techniques, adaptive concurrency scaling, batch API routing, and prompt caching, multiply the effective capacity a system gets out of the same provider tier, without needing a higher limit at all.
Adaptive concurrency uses an approach called AIMD, additive increase, multiplicative decrease. Static concurrency limits are simple to reason about, but they create thundering-herd dynamics right at window boundaries, when everyone's limit resets at the same moment. AIMD instead adjusts the number of active workers continuously, based on what's actually happening: worker count increases a little at a time after a streak of successful responses where quota utilization stays under a set threshold, and it gets cut in half immediately the moment a 429 shows up. That sharp, immediate cut is what prevents a cascading failure and avoids stretching out a penalty window the way a static limit can. Tuning isn't universal across providers. One with a strict daily quota needs a slower ramp, while one with a generous per-second limit can afford to climb faster, so AIMD parameters have to be set per provider rather than copied wholesale from one deployment to another.
Batch API routing applies to any workload that doesn't need an answer immediately. Shifting asynchronous work, nightly summarization jobs, large-scale document processing, bulk enrichment tasks, onto a batch endpoint takes it out of the same real-time quota that interactive, user-facing requests depend on. That separation alone prevents a background job from ever competing with a live user for the same slice of capacity, solving priority inversion at the routing layer instead of the queue layer.
Caching works on the same principle from the opposite direction: the cheapest request a system can make is the one it never sends. A cached response to a repeated prompt, a cached fetch of a page that hasn't changed, costs nothing against the token quota and nothing against the dollar budget. Combined with batch routing and adaptive concurrency, caching reduces the number of calls that cross a fixed provider limit, turning that limit into something closer to elastic capacity.


