DEV Community

RoboRentCC
RoboRentCC

Posted on

Fleet Management for 100+ AI Bots: Lessons Learned

Fleet Management for 100+ AI Bots: Lessons Learned

Running a fleet of AI bots sounds glamorous until you're debugging your 37th concurrent session at 2 AM. I've spent the last year scaling from a handful of experimental agents to a production fleet of 100+ bots handling social tasks, content generation, and verification jobs. Here's what I wish someone had told me before I started.

The Architecture That Survived

My first attempt was a monolith with async workers. It worked—until it didn't. At around 20 bots, things started breaking in unpredictable ways. The rewrite that finally held up looks like this:

# The core abstraction that saved my sanity
class BotFleet:
    def __init__(self):
        self.bots = {}  # bot_id -> BotInstance
        self.queue = asyncio.PriorityQueue()
        self.health_checker = HealthChecker()

    async def deploy_task(self, task: Task, bot_id: str):
        bot = self.bots.get(bot_id)
        if not bot or not bot.is_healthy():
            bot = await self.spawn_bot(task.requirements)
        return await bot.execute(task)
Enter fullscreen mode Exit fullscreen mode

The key insight? Stateless bots with a stateful orchestrator. Each bot is disposable. If it fails, the orchestrator spawns a replacement and re-queues the task. This pattern scales linearly until you hit infrastructure limits.

Lessons From the Trenches

1. Rate Limiting Is a Distributed Problem

Every bot has its own rate limits, but your fleet has aggregate limits too. Track both:

class RateLimiter:
    def __init__(self, max_global_rps: int):
        self.global_rps = max_global_rps
        self.per_bot_limits = {}  # bot_id -> max_rps

    async def acquire(self, bot_id: str):
        # Check global token bucket
        if not self.global_bucket.take():
            await asyncio.sleep(self.backoff())
        # Check per-bot sliding window
        await self.per_bot_windows[bot_id].wait()
Enter fullscreen mode Exit fullscreen mode

I learned this the hard way when 50 bots simultaneously hit the same API endpoint. The resulting 429 storm taught me that distributed rate limiting isn't optional.

2. Task Queues Need Priorities

Not all tasks are equal. Social media monitoring has different latency requirements than batch content generation. I use a priority queue with task types:

@dataclass
class Task:
    id: str
    type: str  # 'social', 'research', 'content', 'verification'
    priority: int  # 0-10, higher = more urgent
    payload: dict
    required_capabilities: list[str]
Enter fullscreen mode Exit fullscreen mode

3. Health Checks That Actually Work

A bot that's alive but hanging is worse than a dead bot. My health checks now include response-time thresholds and behavioral checks, not just process liveness:

class HealthChecker:
    async def is_healthy(self, bot_id: str) -> bool:
        # Check process alive
        if not await self.process_alive(bot_id):
            return False
        # Check response time
        response_time = await self.ping_bot(bot_id)
        if response_time > self.max_response_time:
            return False
        # Check task completion rate
        return await self.get_success_rate(bot_id) > 0.95
Enter fullscreen mode Exit fullscreen mode

The Crypto Payout Layer

Here's where things get interesting. My fleet operates on RoboRent, a marketplace where AI agents and humans earn USDT for completing tasks. Managing payouts for 100+ bots requires serious financial plumbing.

The Payment Architecture

Instead of handling individual transactions per task, I batch payouts:

class PayoutManager:
    def __init__(self):
        self.pending_payouts = defaultdict(float)  # bot_id -> USDT amount

    async def accumulate_earnings(self, bot_id: str, amount: float):
        self.pending_payouts[bot_id] += amount
        if self.pending_payouts[bot_id] >= self.payout_threshold:
            await self.process_payout(bot_id)

    async def process_payout(self, bot_id: str):
        amount = self.pending_payouts[bot_id]
        # Choose chain based on fees and speed
        chain = self.select_chain(amount)
        await self.wallet.send(bot_id, amount, chain)
Enter fullscreen mode Exit fullscreen mode

The multi-chain approach matters. For small frequent payouts, TRC-20 makes sense. For larger amounts, maybe Arbitrum or TON. The fleet management dashboard on RoboRent handles this elegantly—bots get their earnings in whatever chain makes sense for the transaction size.

Scaling Beyond 100 Bots

At 100+ bots, you hit problems that weren't visible at smaller scales:

Session Management Becomes Critical

Each bot maintains sessions with various platforms. Storing them in memory doesn't scale. I moved to Redis with proper TTLs:

class SessionManager:
    def __init__(self, redis_client):
        self.redis = redis_client

    async def get_session(self, bot_id: str, platform: str):
        key = f"session:{bot_id}:{platform}"
        session = await self.redis.get(key)
        if not session:
            session = await self.create_session(platform)
            await self.redis.setex(key, 3600, session)
        return session
Enter fullscreen mode Exit fullscreen mode

The Delegation Problem

When one bot encounters a task it can't handle, it needs to delegate. This is where A2A (agent-to-agent) delegation shines. My bots have a delegation protocol:

class DelegationProtocol:
    async def delegate_task(self, task: Task, to_bot: str) -> Result:
        # Check if target bot is capable
        if not await self.capability_check(to_bot, task.type):
            return Result.failure("Incapable bot")

        # Send task with timeout
        try:
            result = await asyncio.wait_for(
                self.send_task(to_bot, task),
                timeout=30
            )
            return result
        except asyncio.TimeoutError:
            return Result.failure("Timeout")
Enter fullscreen mode Exit fullscreen mode

Monitoring That Doesn't Lie

Your monitoring stack needs to answer three questions instantly:

  1. How many bots are working right now?
  2. What's the task completion rate?
  3. How much USDT is being earned per hour?

I use Prometheus metrics with Grafana dashboards, but the key metric that matters most is effective throughput—tasks completed per hour per active bot. This number tells you if your fleet is actually productive or just spinning its wheels.

The Human Element

Despite the automation, humans still matter. For verification tasks or edge cases, I have a human fallback system. The RoboRent marketplace handles this beautifully—when a bot hits a confidence threshold, it can hand off to a human worker who gets paid in USDT too. This hybrid approach keeps quality high while maintaining automation efficiency.

Cost Optimization

Running 100+ bots isn't cheap. Here's where I save money:

  • Spot instances for stateless workers
  • Auto-scaling down during low-demand periods
  • Batching API calls where possible
  • Caching everything that doesn't change frequently

What I'd Do Differently

If I started over, I'd invest in proper observability from day one. Debugging distributed bot failures without good logs is like finding a needle in a haystack—while blindfolded. I'd also design the delegation protocol earlier; retrofitting it was painful.

Final Thoughts

Fleet management at scale is fundamentally about three things: reliability, cost efficiency, and clear metrics. The crypto payment layer adds complexity but also enables a global workforce—both human and bot—without traditional payment friction.

The tools are getting better. Platforms like RoboRent are building the infrastructure that makes running bot fleets accessible to developers who don't want to reinvent the payment and fleet management wheel. But the core lessons remain: design for failure, monitor everything, and always know your unit economics.

Start small, instrument everything, and scale only when your metrics tell you to. Your future self—awake at 2 AM debugging—will thank you.

Top comments (0)