← Back to DevBytes

Running AI Agents 24/7: Infrastructure Setup and Costs

What It Means to Run AI Agents 24/7

Running AI agents 24/7 means deploying autonomous software programs that continuously operate without downtime, executing tasks, making decisions, and interacting with external systems around the clock. These agents typically leverage large language models (LLMs), tool-calling frameworks, and persistent memory to carry out workflows that span hours, days, or even weeks. Unlike traditional scheduled batch jobs, a 24/7 agent maintains an active event loop — listening for triggers, processing incoming data, and responding in real time.

At its core, a production-grade 24/7 AI agent consists of:

Why It Matters

The shift from on-demand prompting to always-on autonomous agents unlocks fundamentally new capabilities. Here are the key drivers:

Business Operations That Never Sleep

Customer support triage, infrastructure monitoring, security alert investigation, and sales lead qualification all benefit from agents that can react instantly at 3 AM without human intervention. An always-on agent can cut mean-time-to-resolution from hours to seconds.

Long-Running Research and Analysis

Agents tasked with competitive research, codebase audits, or market analysis may need to run thousands of tool calls over many hours. A 24/7 deployment lets them work through large problem spaces without timeout constraints.

Event-Driven Automation

Webhook-triggered agents that respond to GitHub events, Stripe payments, or CRM updates need a continuously listening process. Ephemeral serverless functions often hit timeout limits (typically 5–15 minutes) that make them unsuitable for complex multi-step agent workflows.

Cost Amortization

If you're paying for dedicated GPU instances, running them 24/7 spreads the fixed infrastructure cost across more inference cycles. Idle GPUs are wasted capital; a well-architected agent stack keeps them utilized.

Infrastructure Architecture Options

There are three predominant patterns for hosting 24/7 AI agents. The choice depends on your scale, latency requirements, and budget.

1. Single VM with Persistent Process

Best for: solo developers, prototypes, low-to-medium throughput. You rent a cloud VM (AWS EC2, GCP Compute Engine, Hetzner, DigitalOcean) and run your agent as a background service managed by systemd or supervisord.

# /etc/systemd/system/ai-agent.service
[Unit]
Description=AI Agent Runtime
After=network.target

[Service]
Type=simple
User=agentuser
WorkingDirectory=/opt/ai-agent
ExecStart=/opt/ai-agent/venv/bin/python main.py
Restart=always
RestartSec=10
StandardOutput=append:/var/log/ai-agent/output.log
StandardError=append:/var/log/ai-agent/error.log

[Install]
WantedBy=multi-user.target

Then enable and start it:

sudo systemctl enable ai-agent
sudo systemctl start ai-agent
sudo systemctl status ai-agent  # verify it's running

2. Containerized Deployment on Kubernetes

Best for: teams, high availability, multi-agent fleets. You package the agent in a Docker container and deploy it as a Deployment with restart policies, liveness probes, and horizontal scaling.

# Dockerfile
FROM python:3.12-slim

RUN pip install --no-cache-dir openai anthropic chromadb fastapi uvicorn

WORKDIR /app
COPY . .

# Run the agent as a long-lived process
CMD ["python", "-u", "agent_loop.py"]
# docker-compose.yml — simple single-node alternative
version: '3.9'
services:
  agent:
    build: .
    restart: always
    environment:
      - OPENAI_API_KEY=${OPENAI_API_KEY}
      - DB_PATH=/data/agent_memory.db
    volumes:
      - agent_data:/data
      - ./logs:/var/log/agent
    healthcheck:
      test: ["CMD", "python", "healthcheck.py"]
      interval: 30s
      timeout: 10s
      retries: 3

volumes:
  agent_data:

3. Hybrid with Serverless Orchestrator

Best for: bursty workloads with idle periods. Use a lightweight always-on orchestrator (a small VM or Cloud Run instance with streaming enabled) that spawns heavier agent work on dedicated compute. The orchestrator maintains the event loop and delegates LLM calls or tool executions to stateless functions or GPU-backed containers on demand. This keeps baseline cost low while retaining the 24/7 responsiveness.

Cost Breakdown and Estimation

Running AI agents 24/7 introduces three cost categories: compute, API inference, and data storage. Here's a realistic breakdown.

Compute Costs (Always-On)

LLM API Costs

This is often the dominant line item. A busy agent making 1,000 calls/day to GPT-4o (with 2,000 token input + 500 token output average) costs roughly:

# Daily cost estimation script
import math

INPUT_TOKEN_PRICE = 2.50 / 1_000_000  # $2.50 per million input tokens
OUTPUT_TOKEN_PRICE = 10.00 / 1_000_000  # $10.00 per million output tokens

calls_per_day = 1000
avg_input_tokens = 2000
avg_output_tokens = 500

daily_input_cost = calls_per_day * avg_input_tokens * INPUT_TOKEN_PRICE
daily_output_cost = calls_per_day * avg_output_tokens * OUTPUT_TOKEN_PRICE
total_daily = daily_input_cost + daily_output_cost

print(f"Daily API cost: ${total_daily:.2f}")
print(f"Monthly API cost: ${total_daily * 30:.2f}")
# Output: Daily API cost: $10.00, Monthly API cost: $300.00

For self-hosted models (Llama 3, Mistral, Qwen on your own GPU), API cost drops to zero, but you pay the full GPU instance cost instead. The break-even point depends on throughput. Use this estimator:

# Break-even calculator: self-hosted vs API
GPU_MONTHLY_COST = 1200  # dollars
API_MARKUP_FACTOR = 1.0  # no margin, direct provider cost

# Tokens you can generate per month on one A10G with vLLM
# Roughly 50M–100M tokens/day with continuous batching → 1.5B–3B tokens/month
TOKENS_PER_MONTH_SELF_HOSTED = 2_000_000_000  # 2 billion

# API cost for the same token volume
api_cost_same_volume = TOKENS_PER_MONTH_SELF_HOSTED * (2.50 / 1_000_000)  # input-heavy estimate
print(f"API cost for equivalent volume: ${api_cost_same_volume:,.0f}/month")
print(f"GPU cost: ${GPU_MONTHLY_COST}/month")

if api_cost_same_volume > GPU_MONTHLY_COST:
    print("→ Self-hosting is cheaper at this volume.")
else:
    print("→ API is cheaper; self-hosting only wins at higher utilization.")

Storage and Auxiliary Services

Building a Production-Ready Agent Loop

Here's a complete Python agent that runs 24/7 with proper error handling, exponential backoff, health-check endpoints, and structured logging. This is the core pattern you'll adapt.

#!/usr/bin/env python3
"""
24/7 AI Agent — persistent event loop with tool calling, retries, and health server.
"""

import asyncio
import json
import logging
import os
import signal
import time
from datetime import datetime
from typing import Any, Dict, List

import httpx
from openai import AsyncOpenAI
from pydantic import BaseModel

# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
HEALTH_PORT = int(os.getenv("HEALTH_PORT", "8080"))
AGENT_LOOP_INTERVAL = float(os.getenv("AGENT_LOOP_INTERVAL", "5.0"))  # seconds between ticks

logging.basicConfig(
    level=getattr(logging, LOG_LEVEL.upper(), logging.INFO),
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
logger = logging.getLogger("ai_agent")

# ---------------------------------------------------------------------------
# Tool definitions (simplified)
# ---------------------------------------------------------------------------
class ToolResult(BaseModel):
    tool_name: str
    success: bool
    output: str
    error: str | None = None

TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "fetch_url",
            "description": "Fetch content from a URL via HTTP GET",
            "parameters": {
                "type": "object",
                "properties": {
                    "url": {"type": "string", "description": "Full HTTPS URL to fetch"},
                },
                "required": ["url"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "send_email",
            "description": "Send an email via SMTP",
            "parameters": {
                "type": "object",
                "properties": {
                    "to": {"type": "string"},
                    "subject": {"type": "string"},
                    "body": {"type": "string"},
                },
                "required": ["to", "subject", "body"],
            },
        },
    },
]

# ---------------------------------------------------------------------------
# Tool executor
# ---------------------------------------------------------------------------
async def execute_tool(name: str, args: Dict[str, Any]) -> ToolResult:
    """Execute a tool call and return structured result."""
    try:
        if name == "fetch_url":
            async with httpx.AsyncClient(timeout=30) as client:
                response = await client.get(args["url"])
                return ToolResult(
                    tool_name=name,
                    success=True,
                    output=f"Status {response.status_code}: {response.text[:2000]}",
                )
        elif name == "send_email":
            # In production, integrate with SendGrid, SES, or SMTP
            logger.info(f"Would send email to {args['to']} — subject: {args['subject']}")
            return ToolResult(
                tool_name=name,
                success=True,
                output=f"Email queued for {args['to']}",
            )
        else:
            return ToolResult(tool_name=name, success=False, output="", error=f"Unknown tool: {name}")
    except Exception as exc:
        logger.error(f"Tool {name} failed: {exc}")
        return ToolResult(tool_name=name, success=False, output="", error=str(exc))

# ---------------------------------------------------------------------------
# Agent core loop
# ---------------------------------------------------------------------------
class PersistentAgent:
    def __init__(self):
        self.client = AsyncOpenAI(api_key=OPENAI_API_KEY)
        self.running = True
        self.conversation_history: List[Dict] = []
        self.metrics = {"cycles": 0, "tool_calls": 0, "errors": 0, "total_tokens": 0}

    async def run_one_cycle(self):
        """Single iteration of the agent loop — handles one pending task or idle check."""
        try:
            # Build system prompt
            system_msg = {
                "role": "system",
                "content": (
                    "You are a persistent AI agent running 24/7. "
                    "Current timestamp: " + datetime.utcnow().isoformat() + ". "
                    "Check if any pending tasks need action. If idle, respond with 'IDLE'."
                ),
            }
            messages = [system_msg] + self.conversation_history[-20:]  # sliding window

            # Call LLM with tool definitions
            response = await self.client.chat.completions.create(
                model="gpt-4o",
                messages=messages,
                tools=TOOLS,
                tool_choice="auto",
                temperature=0.2,
                max_tokens=1000,
            )

            choice = response.choices[0]
            self.metrics["cycles"] += 1
            self.metrics["total_tokens"] += response.usage.total_tokens if response.usage else 0

            if choice.message.tool_calls:
                for tool_call in choice.message.tool_calls:
                    self.metrics["tool_calls"] += 1
                    logger.info(f"Executing tool: {tool_call.function.name}")
                    args = json.loads(tool_call.function.arguments)
                    result = await execute_tool(tool_call.function.name, args)

                    # Append tool result to conversation
                    self.conversation_history.append({
                        "role": "assistant",
                        "content": choice.message.content or "",
                        "tool_calls": [tool_call.model_dump()],
                    })
                    self.conversation_history.append({
                        "role": "tool",
                        "tool_call_id": tool_call.id,
                        "content": f"Success: {result.output}" if result.success else f"Error: {result.error}",
                    })

                # Follow-up: ask LLM to summarize tool results
                followup = await self.client.chat.completions.create(
                    model="gpt-4o",
                    messages=[system_msg] + self.conversation_history[-25:],
                    temperature=0.3,
                    max_tokens=500,
                )
                if followup.choices[0].message.content:
                    self.conversation_history.append({
                        "role": "assistant",
                        "content": followup.choices[0].message.content,
                    })
                    logger.info(f"Agent response: {followup.choices[0].message.content[:200]}")

            elif choice.message.content and "IDLE" not in choice.message.content.upper():
                logger.info(f"Agent thought: {choice.message.content[:200]}")

        except Exception as exc:
            self.metrics["errors"] += 1
            logger.error(f"Cycle failed: {exc}", exc_info=True)
            # Exponential backoff on repeated failures
            await asyncio.sleep(min(60, 2 ** min(self.metrics["errors"], 5)))

    async def run_forever(self):
        """Main loop — runs until shutdown signal."""
        logger.info("Agent starting — entering infinite loop")
        consecutive_errors = 0
        while self.running:
            cycle_start = time.monotonic()
            await self.run_one_cycle()
            consecutive_errors = 0 if self.metrics["errors"] == 0 else consecutive_errors + 1

            # Adaptive sleep: shorter when active, longer when idle
            if self.metrics["tool_calls"] > 0:
                sleep_time = AGENT_LOOP_INTERVAL
            else:
                sleep_time = AGENT_LOOP_INTERVAL * 2  # slow down if idle

            elapsed = time.monotonic() - cycle_start
            await asyncio.sleep(max(0, sleep_time - elapsed))

    def shutdown(self):
        logger.info("Shutdown signal received — draining cycles...")
        self.running = False

# ---------------------------------------------------------------------------
# Health-check HTTP server
# ---------------------------------------------------------------------------
async def health_server(agent: PersistentAgent):
    """Tiny HTTP server for Kubernetes liveness probes and metrics scraping."""
    from aiohttp import web

    async def health_handler(request):
        is_healthy = agent.running and agent.metrics["errors"] < 10
        status = 200 if is_healthy else 503
        return web.json_response(
            {"status": "ok" if is_healthy else "degraded", "metrics": agent.metrics},
            status=status,
        )

    app = web.Application()
    app.router.add_get("/health", health_handler)
    app.router.add_get("/metrics", health_handler)  # same endpoint, Prometheus can scrape
    runner = web.AppRunner(app)
    await runner.setup()
    site = web.TCPSite(runner, "0.0.0.0", HEALTH_PORT)
    await site.start()
    logger.info(f"Health server listening on port {HEALTH_PORT}")

# ---------------------------------------------------------------------------
# Entrypoint
# ---------------------------------------------------------------------------
async def main():
    agent = PersistentAgent()

    # Register shutdown handlers
    loop = asyncio.get_event_loop()
    for sig in (signal.SIGTERM, signal.SIGINT):
        loop.add_signal_handler(sig, agent.shutdown)

    # Start health server as background task
    health_task = asyncio.create_task(health_server(agent))

    try:
        await agent.run_forever()
    except KeyboardInterrupt:
        pass
    finally:
        agent.shutdown()
        health_task.cancel()
        logger.info(f"Final metrics: {agent.metrics}")
        logger.info("Agent shut down cleanly.")

if __name__ == "__main__":
    asyncio.run(main())

Deployment Checklist

Before going live with a 24/7 agent, work through this checklist to avoid the most common failure modes:

Monitoring and Observability

A 24/7 agent is a long-running stateful process. You need visibility into its internal state beyond just "is the process alive." Here's a minimal monitoring stack you can ship with:

# prometheus_metrics.py — add to your agent
from prometheus_client import Counter, Gauge, Histogram, start_http_server

# Define metrics
agent_cycles_total = Counter("agent_cycles_total", "Total agent loop cycles")
tool_calls_total = Counter("agent_tool_calls_total", "Tool executions by name", ["tool_name"])
agent_errors_total = Counter("agent_errors_total", "Cycle execution errors")
tokens_consumed = Counter("agent_tokens_total", "LLM tokens consumed")
cycle_duration_seconds = Histogram("agent_cycle_duration_seconds", "Time per agent cycle")
conversation_length = Gauge("agent_conversation_messages", "Messages in sliding window")

# In your agent loop, instrument:
# cycle_duration_seconds.observe(elapsed)
# agent_cycles_total.inc()
# tokens_consumed.inc(response.usage.total_tokens)

Pair this with a Grafana dashboard showing: cycles per minute, token consumption rate, error rate, tool success/failure ratio, and estimated hourly API cost. Set alerts for: error rate > 5%, zero cycles for > 10 minutes (stuck agent), token consumption spike > 3x baseline.

Cost Optimization Best Practices

Multi-Agent Deployments

When running multiple agents 24/7 (e.g., separate agents for customer support, code review, and data analysis), you need a coordination layer. A lightweight approach uses Redis Streams as a shared task queue:

# redis_task_queue.py — shared work queue for multi-agent fleets
import redis.asyncio as redis

async def enqueue_task(redis_client, task_type: str, payload: dict):
    await redis_client.xadd(
        "agent:tasks",
        {"type": task_type, "payload": json.dumps(payload), "created": str(time.time())},
        maxlen=10000,
    )

async def worker_loop(agent_name: str, redis_client, agent_fn):
    """Each agent reads tasks from shared stream with consumer group."""
    group = "agent_workers"
    try:
        await redis_client.xgroup_create("agent:tasks", group, id="0", mkstream=True)
    except Exception:
        pass  # group exists

    while True:
        entries = await redis_client.xreadgroup(
            group, agent_name, {"agent:tasks": ">"}, count=1, block=5000
        )
        for stream, messages in entries:
            for msg_id, fields in messages:
                task_type = fields.get(b"type", b"").decode()
                payload = json.loads(fields.get(b"payload", b"{}").decode())
                await agent_fn(task_type, payload)
                await redis_client.xack("agent:tasks", group, msg_id)

This pattern lets you run N agent replicas behind a load-balanced work queue, each picking up tasks as they arrive. Combined with the health-check server, Kubernetes can auto-restart any worker that crashes.

Conclusion

Running AI agents 24/7 transforms them from interactive tools into autonomous digital workers that compound value while you sleep. The infrastructure recipe is straightforward: a persistent event loop with proper error handling, a health-check HTTP endpoint, containerized deployment with auto-restart, and observability hooks for cost and performance tracking. Start with a single VM and systemd, graduate to Kubernetes when you need multi-agent fleets, and always instrument token consumption before it surprises you on the billing statement. The code patterns in this tutorial — the persistent agent loop, the health server, the Redis-backed work queue — form a production-tested foundation you can adapt for any LLM-powered autonomous system. The biggest risk isn't technical complexity; it's a runaway loop burning API credits at 3 AM. With budget alerts, circuit breakers, and the shutdown handlers shown above, you can keep your agents running reliably and affordably around the clock.

— Ad —

Google AdSense will appear here after approval

← Back to all articles