ClawMart AI
← Back to Blog
August 18, 20269 min readClaw Mart Team

How to Set Rate Limits So Your OpenClaw Agent Doesn't Overspend

How to Set Rate Limits So Your OpenClaw Agent Doesn't Overspend

How to Set Rate Limits So Your OpenClaw Agent Doesn't Overspend

Let's talk about the moment every OpenClaw builder hits sooner or later.

You've been tinkering with your agent in development. It works beautifully. Three API calls, clean responses, exactly the behavior you wanted. You feel like a genius. So you push it to production, tell a few people about it, and go make dinner.

You come back to a $47 bill and a Slack channel full of 429 errors.

This is the OpenClaw rite of passage nobody warns you about. Your agent, left to its own devices, will make as many API calls as it possibly can, as fast as it possibly can, with absolutely zero regard for your wallet or your API provider's patience. It's not malicious. It's just doing what you told it to do — you just forgot to tell it how fast to do it.

Rate limiting is the fix. But most people either skip it entirely, implement it badly, or bolt on some half-baked retry logic that creates more problems than it solves. Today, I'm going to walk through exactly how to set up rate limits in OpenClaw so your agent behaves like a responsible adult with a budget.

Why Your Agent Overspends (And Why Retry Logic Isn't the Answer)

Here's what typically happens. A developer builds an OpenClaw agent that chains together a few LLM calls — maybe a planning step, a research step, and a synthesis step. In testing, this runs three or four times and everything's great. In production, a user asks a complex question, the agent decides it needs to explore fifteen sub-queries, and suddenly you've burned through your entire minute's quota in eight seconds.

The instinct is to add retry logic. When you get a 429 "too many requests" error, just wait and try again, right?

Wrong. Or rather, insufficient.

Retry logic is reactive. Your agent has already hit the wall. Now it's sitting there, waiting, burning user patience. Worse, if you're using exponential backoff (the default in most implementations), your agent might wait 1 second, then 2, then 4, then 8, then 16 — even though you know your rate limit resets in exactly 12 seconds. You're either waiting too long or not long enough.

And then there's the death spiral. I've seen this described on Hacker News more times than I can count: your agent gets rate limited, retries all its failed requests at once, gets rate limited again, retries the retries, and suddenly your error logs are measured in gigabytes and your agent is doing nothing but failing and retrying in an infinite loop of sadness.

The real answer is proactive rate limiting. Don't wait until you hit the wall. Know where the wall is, track how close you are to it, and slow down before you crash into it.

That's what OpenClaw's rate limiting system is built for.

Setting Up Your First Rate Limiter

Let's start with the basics. OpenClaw has a RateLimiter class that wraps around your agent calls and enforces limits before requests go out, not after they fail.

from openclaw import RateLimiter

limiter = RateLimiter(
    max_requests=50,
    time_window=60,
    strategy="fixed_window"
)

@limiter.limit()
async def agent_call(prompt):
    return await llm.generate(prompt)

That's it for the simple case. Your agent can now make 50 requests per 60-second window. Request number 51 doesn't crash with an unhandled 429 — it waits until the window resets, then proceeds. No error logs, no death spirals, no surprise bills.

But the simple case is rarely the real case. Let's talk about what you actually need.

Choosing the Right Strategy

OpenClaw gives you three rate limiting strategies, and picking the right one matters more than most people realize.

Fixed Window is the simplest. You get X requests per Y seconds. The counter resets at the end of each window. It's easy to understand and works fine for low-traffic agents. The problem? You can accidentally "burst" at window boundaries — make 50 requests at second 59, then 50 more at second 61, and you've effectively done 100 requests in 2 seconds.

Sliding Window fixes the burst problem. Instead of resetting at fixed intervals, it looks at a rolling window of the last Y seconds at any given moment. Smoother, more predictable, and what I'd recommend for most production agents.

Token Bucket is the most sophisticated. Imagine a bucket that gets filled with tokens at a steady rate. Each request consumes a token. If the bucket is empty, you wait. If you haven't made requests in a while, the bucket fills up and you can "burst" a bit. This is ideal for agents with unpredictable traffic patterns.

# For most agents, start here
limiter = RateLimiter(
    max_requests=100,
    time_window=60,
    strategy="sliding_window"
)

# For agents with bursty patterns (user-facing chatbots, etc.)
limiter = RateLimiter(
    max_requests=100,
    time_window=60,
    strategy="token_bucket",
    burst_size=150  # Allow short bursts above the sustained rate
)

My recommendation: start with sliding window. Switch to token bucket if you notice your agent has long idle periods followed by bursts of activity. Skip fixed window unless you have a specific reason to use it.

The Spending Problem: Not All Requests Cost the Same

Here's where most rate limiting tutorials stop, and where the real overspending problems begin.

A simple classification call might use 500 tokens. A RAG query with a massive context window might use 50,000 tokens. If your rate limiter treats them the same — "1 request = 1 unit" — you're going to have a bad time. Your agent could blow through your token budget in three expensive calls while your rate limiter happily says, "You've only made 3 of your 50 allowed requests!"

OpenClaw supports weighted rate limiting for exactly this reason.

limiter = RateLimiter(
    max_tokens=100000,  # Token-based, not request-based
    time_window=60
)

@limiter.limit(cost=500)  # Cheap operation
async def classify(text):
    return await llm.classify(text)

@limiter.limit(cost=50000)  # Expensive operation
async def rag_query(query, context):
    return await llm.generate(query, context)

Now your limiter understands that one RAG query eats the same budget as 100 classification calls. This single change probably saves more money than everything else in this post combined.

Pro tip: If you're not sure what cost to assign, check your API provider's token usage in their dashboard for a week, then use the 90th percentile for each operation type. Overestimating slightly is safer than underestimating.

Multi-Provider Setups (Where Things Get Real)

Most serious OpenClaw agents don't use a single provider. You might use one model for reasoning, another for long-context work, and a local model for cheap, fast calls. Each provider has different limits. Managing them separately is a nightmare — I've seen codebases where rate limiting logic accounts for more lines of code than actual agent logic.

OpenClaw's MultiProviderLimiter handles this cleanly:

from openclaw import MultiProviderLimiter

limiter = MultiProviderLimiter({
    'openai': {
        'requests_per_minute': 3500,
        'tokens_per_minute': 90000
    },
    'anthropic': {
        'requests_per_minute': 50,
        'tokens_per_minute': 100000
    },
    'local': {
        'requests_per_minute': 1000
    }
})

@limiter.route(['openai', 'anthropic', 'local'])
async def smart_call(prompt):
    return await llm.generate(prompt)

The route decorator is doing something really smart here. It checks which providers have available quota and picks the first one that can handle the request. If OpenAI is tapped out, it falls back to Anthropic. If Anthropic is also maxed, it hits your local model. Your agent never crashes, never waits unnecessarily, and always uses the best available option.

This is particularly powerful when you combine it with weighted costs. Your expensive reasoning calls go to the premium provider when available, but gracefully degrade to alternatives when limits are hit.

Observability: Know Where Your Budget Goes

You cannot optimize what you cannot measure. One of the most common complaints I see in the OpenClaw community is some version of: "My agent is hitting rate limits and I have no idea why."

Built-in metrics fix this entirely.

metrics = limiter.get_metrics()
print(f"Used: {metrics['used']}/{metrics['limit']}")
print(f"Resets in: {metrics['reset_in']} seconds")
print(f"Success rate: {metrics['success_rate']}%")

Even better, use per-operation tracking to find bottlenecks:

@limiter.limit(key="step1_planning")
async def planning_step():
    return await llm.generate("plan")

@limiter.limit(key="step2_research")
async def research_step():
    return await llm.generate("research")

@limiter.limit(key="step3_synthesis")
async def synthesis_step():
    return await llm.generate("synthesize")

# Find the bottleneck
metrics = limiter.get_metrics_by_key()
for key, data in metrics.items():
    print(f"{key}: {data['rate_limit_hits']} hits, {data['avg_wait_time']}s avg wait")

I had an agent where step 3 (synthesis) was consuming 80% of the token budget because it was receiving the full context of steps 1 and 2 every time. Without per-operation tracking, I would have spent weeks optimizing the wrong thing. With it, the fix took ten minutes — truncate the context before passing it to synthesis.

Preventing the Death Spiral

Remember the death spiral I mentioned earlier? OpenClaw has a circuit breaker pattern built in that prevents it entirely.

limiter = RateLimiter(
    max_requests=50,
    time_window=60,
    strategy="token_bucket",
    circuit_breaker=True,
    circuit_breaker_threshold=5,  # Open after 5 consecutive failures
    circuit_breaker_timeout=30    # Stay open for 30 seconds
)

After 5 rate limit errors, the circuit breaker "opens" and all subsequent requests fail immediately — no retries, no queue buildup, no log explosion. After 30 seconds, it enters a "half-open" state where it lets one request through to test the waters. If that succeeds, the circuit closes and normal operation resumes. If not, it stays open for another 30 seconds.

This is the single most important configuration for production agents. Turn it on. Always.

Priority Queuing: Users First, Exploration Second

If your agent does background "thinking" — exploring sub-queries, pre-computing answers, building context — you need priority queuing. Without it, your agent's speculative work can eat the entire rate limit, and when a real user request comes in, it has to wait.

limiter = RateLimiter(
    max_requests=50,
    time_window=60,
    enable_priority=True
)

@limiter.limit(priority='high')
async def user_request(prompt):
    return await llm.generate(prompt)

@limiter.limit(priority='low')
async def exploratory_thinking(prompt):
    return await llm.generate(prompt)

High-priority requests jump the queue. If the budget is tight, low-priority tasks get paused until there's headroom. Your users get instant responses while the agent's background work flexes around the remaining capacity.

Distributed Agents: The Shared State Problem

Running multiple agent instances? Workers, containers, serverless functions? Each one needs to share the same rate limit state. Otherwise, three workers each think they have 100 RPM available, and together they make 300 RPM and get blocked.

from openclaw import DistributedRateLimiter

limiter = DistributedRateLimiter(
    max_requests=100,
    time_window=60,
    backend='redis',
    redis_url='redis://localhost:6379'
)

Same API, shared state. All instances coordinate through Redis. This is non-negotiable for any production deployment with more than one worker.

Testing Without Burning Money

This one's underrated. You need to test your rate limiting logic, but you can't test rate limiting without... hitting rate limits. And burning through API quota on tests is both expensive and unpredictable.

OpenClaw has two solutions:

# Option 1: Test mode — tracks everything, limits nothing
limiter = RateLimiter(
    max_requests=50,
    time_window=60,
    test_mode=True
)

# Option 2: Virtual clock — control time in tests
from openclaw import VirtualClock

limiter = RateLimiter(
    max_requests=50,
    time_window=60,
    clock=VirtualClock()
)

# In your test:
with limiter.clock.freeze() as clock:
    for i in range(50):
        await agent_call()
    
    # Verify limit is reached
    assert not limiter.check_available(cost=1)
    
    # Advance time past the window
    clock.advance(seconds=61)
    
    # Verify limit has reset
    assert limiter.check_available(cost=1)

Deterministic, fast, free. Your CI/CD pipeline will thank you.

My Recommended Setup

After months of running OpenClaw agents in production, here's the configuration I start with on every new project:

from openclaw import RateLimiter

limiter = RateLimiter(
    max_tokens=80000,           # Token-based, not request-based
    time_window=60,
    strategy="sliding_window",   # Smooth limiting
    circuit_breaker=True,
    circuit_breaker_threshold=5,
    circuit_breaker_timeout=30,
    enable_priority=True
)

Token-based with sliding window, circuit breaker on, priorities enabled. This handles 90% of use cases without modification. Adjust the numbers for your provider's limits, add weighted costs for your specific operations, and you're set.

If you don't want to configure all of this from scratch, Felix's OpenClaw Starter Pack on Claw Mart includes pre-built skills with rate limiting already configured for common setups. For $29, you get a bundle of production-ready agent configurations including sensible rate limits, multi-provider routing, and the circuit breaker patterns I described above. It's saved me a few hours of boilerplate on my last two projects, and I'd especially recommend it if you're spinning up your first real OpenClaw agent and don't want to learn these lessons the expensive way.

Next Steps

  1. Audit your current agent. If you have no rate limiting, add the basic RateLimiter with sliding window today. Even the simplest configuration prevents the worst outcomes.

  2. Switch to token-based costs. If you're counting requests instead of tokens, you're probably overspending on expensive operations. Add weighted costs for your most expensive calls first.

  3. Turn on the circuit breaker. There's no reason not to. It's pure downside protection.

  4. Add per-operation metrics. Run your agent for a day, then check which operations are eating the most budget. Optimize there first.

  5. Test your limits. Use virtual clocks to verify your agent behaves correctly at the boundaries. The 10 minutes you spend writing these tests will save you hours of debugging mysterious production failures.

Rate limiting isn't glamorous work. Nobody builds an OpenClaw agent because they're excited about token buckets and sliding windows. But the difference between an agent that costs $5/month and one that costs $500/month often comes down to twenty lines of configuration. Write those twenty lines. Your wallet will thank you.

Recommended for this post

Prevent rate limit failures before they happen. Auto-failover to backup providers. ALWAYS_ON protection.

All platformsEngineering
Fault LineFault Line
$5Buy

Claw Mart Daily

Get one AI agent tip every morning

Free daily tips to make your OpenClaw agent smarter. No spam, unsubscribe anytime.

More From the Blog