SokkoSokko
← Back to blog

Build Autonomous AI Agents That Run 24/7: Architecture

Sokko Team11 min read

What "runs 24/7" really demands

The moment you decide to build an autonomous AI agent that runs 24/7, the hard part stops being the model and becomes everything around it. A demo agent lives in a notebook, answers once, and exits. A production agent has to wake on a schedule or an event, keep working while your laptop sleeps, survive a crashed process, and remember what it did an hour ago. Those are operations problems, not prompt problems, and they are where most first attempts fall apart.

Before you write any code, be honest about what "always-on" means for your workload. The phrase hides at least four separate requirements:

  • Availability target. Do you need three nines (about 8 hours of downtime a year) or is "back within 10 minutes" fine? A personal automation and a customer-facing agent sit at very different points.

  • Recovery time. When the process dies at 3am, how fast does something restart it? Nobody should be paged to run a command by hand.

  • State durability. If the container is deleted, what must survive? Chat history, a task queue, files the agent wrote, a vector index?

  • Concurrency. One agent working one task at a time is easy. Ten agents sharing rate limits and a database is a scheduling problem.

Answering these up front stops you from over-building. A nightly refactor bot does not need a five-region failover. A support triage agent probably does. The architecture below scales down as easily as it scales up.

The architecture of an autonomous AI agent that runs 24/7

Every long-running agent I have shipped decomposes into the same six components. Name them explicitly, because when something breaks at 3am you want to know which box failed.

  • Scheduler / event loop. The heartbeat. It decides when the agent does work: a fixed interval, a cron expression, a webhook, or a message pulled off a queue. This is the outer while loop that never exits on its own.

  • Model client. The thin wrapper around your LLM API (Anthropic, OpenAI, a local server). It handles retries, timeouts, token budgets, and streaming. Keep it stateless so a restart costs nothing here.

  • Tool layer. The functions the agent can actually call: shell commands, HTTP requests, database queries, git operations. Each tool should be idempotent where possible and return structured results, not free text.

  • Memory store. Durable state that outlives the process. Short-term conversation buffer, long-term facts, a task ledger, and any files the agent produces. This is the piece people forget, and it is the one that hurts most when it is missing.

  • Health check. A cheap endpoint or function that answers one question: "is this agent actually making progress, or is it wedged?" A process can be alive and useless. Liveness and progress are different signals.

  • Supervisor. The thing that restarts the agent when it dies and backs off when it keeps dying. On a single box this is systemd. On a cluster it is the orchestrator's controller.

The data flows in a tight cycle: the scheduler fires, the loop loads relevant memory, the model client proposes an action, the tool layer executes it, the result is written back to the memory store, and the health check records that progress happened. If any step throws, the supervisor decides whether to retry, back off, or restart from durable state.

Keep the model client and tool layer stateless. Push all the state you care about into the memory store. That single discipline is what makes restarts boring instead of scary.

State, memory, and idempotent restarts

The difference between a toy and an always-on agent is almost entirely about state. Here is the contrast, concretely:

PropertySession-based agentAlways-on agent
LifetimeSeconds to minutes, one requestDays to months, continuous
StateIn-memory, gone on exitPersisted to disk or a database, survives restart
Restart behaviorRe-run from scratch by the callerAuto-restarted by a supervisor, resumes mid-task
Failure blast radiusOne request failsBacklog grows until recovery
Cost modelPay per invocationPay for a process running around the clock

An always-on agent will be killed. Count on it: a node reboots, a deploy rolls the pod, the OOM killer picks your process, a cloud provider does maintenance. The design goal is that a kill at the worst possible moment loses nothing important. Two rules get you most of the way there.

First, persist before you act on anything irreversible. Write "about to send email X" to the ledger, send it, then write "sent." On restart you can reconcile: if you see "about to" without "sent," you either retry or flag for review. This is the same at-least-once thinking behind any durable queue.

Second, make tools idempotent or guard them with keys. Sending the same email twice is bad. So attach an idempotency key derived from the task, and have the tool refuse duplicates. Creating the same GitHub PR twice is bad, so check for an open PR with the same branch first. Idempotent tools turn a scary restart into a no-op.

For memory itself, a pragmatic split works for most agents:

  1. Task ledger in Postgres or SQLite: one row per unit of work, with a status column (pending, running, done, failed). The scheduler reads this on boot to know what to resume.

  2. Conversation buffer capped at the last N turns, stored as JSON next to the task or in a messages table.

  3. Long-term facts in a vector store (pgvector is plenty for one agent) if the agent needs to recall things across weeks.

  4. Working files on a persistent volume. Coding agents in particular need a real filesystem that survives restarts, not a scratch tmpfs that vanishes with the container.

Get the ledger and the persistent volume right and the rest is tuning. For a deeper treatment of running many of these at once, see scaling AI agents.

Where it lives: hosting an always-on agent

An always-on agent needs a host that is also always on. Your laptop is not that host: it sleeps, it reboots for updates, its Wi-Fi drops. The realistic options, cheapest to most managed:

  • A single VPS with systemd. A $4 to $6 per month Hetzner CX22 or a $6 DigitalOcean droplet runs one agent fine. systemd handles restart-on-crash and boot-on-reboot. You own patching, backups, and the day the disk fills. Good for one agent, painful past three. There is a full walkthrough in host an AI agent on a server.

  • A container on a small orchestrator. Docker Compose with restart: unless-stopped, or a single-node k3s. You get declarative config and health checks, at the cost of learning the tooling.

  • Managed Kubernetes. A liveness probe restarts a wedged pod, a PersistentVolumeClaim keeps /workspace across restarts, and secrets are injected rather than baked into an image. You stop writing supervisor glue. You start needing to understand Kubernetes, unless something manages that layer for you.

If you run on Kubernetes yourself, the single most useful primitive is the probe. A liveness probe restarts a container that has stopped making progress; a readiness probe keeps traffic away until the agent is warm. The official reference on liveness, readiness, and startup probes is worth reading once end to end, because the failure thresholds and initial delays are exactly the knobs that decide whether your agent flaps or recovers cleanly.

For the agent itself, OpenClaw is a solid, real, open-source gateway assistant you can host directly: one persistent agent reachable across chat channels, with tool-use and a large community skill library. Its image is ghcr.io/openclaw/openclaw and it keeps a persistent /workspace, which is precisely the durable-filesystem property an always-on agent needs.

This is the layer where Sokko does the boring part for you. Sokko runs agents like OpenClaw on always-on infrastructure we manage, with automatic restarts and durable per-agent storage already wired up, so the restart-and-state plumbing described above is handled. You create an instance from the dashboard in a couple of clicks, pick the runtime, and bring your own model key, and you get an always-on runtime instead of a supervisor script you maintain forever. There's a sokko CLI too if you'd rather script it.

Your agent's files survive restarts, a stuck agent is restarted automatically, and command-approval mode lets you pick autonomous execution or ask-before-acting per instance. You do not have to become a Kubernetes operator to get there. For the credentials that agent needs, keep them out of the image and read managing secrets for AI agents.

A supervisor and health loop you can actually run

If you are on a plain VPS, systemd is the least-effort supervisor that does the right thing. This unit restarts the agent on crash, waits 5 seconds between attempts, and starts it on boot:

# /etc/systemd/system/agent.service
[Unit]
Description=Always-on AI agent
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=agent
WorkingDirectory=/opt/agent
EnvironmentFile=/opt/agent/.env
ExecStart=/opt/agent/.venv/bin/python -m agent.main
Restart=always
RestartSec=5
# stop a crash loop from hammering the CPU
StartLimitIntervalSec=60
StartLimitBurst=5

[Install]
WantedBy=multi-user.target

Enable it with systemctl enable --now agent. That covers "restart when it dies." It does not cover "restart when it is alive but wedged," which is the sneakier failure. For that you need a health signal the supervisor can act on. The agent writes a heartbeat every loop; a watchdog checks that the heartbeat is fresh and kills the process if it is stale, letting systemd restart it:

import os
import time
import json

HEARTBEAT = "/opt/agent/heartbeat.json"
STALE_AFTER = 300  # seconds without progress = wedged

def write_heartbeat():
    tmp = HEARTBEAT + ".tmp"
    with open(tmp, "w") as f:
        json.dump({"ts": time.time()}, f)
    os.replace(tmp, HEARTBEAT)  # atomic

def is_stale():
    try:
        with open(HEARTBEAT) as f:
            last = json.load(f)["ts"]
    except (FileNotFoundError, ValueError):
        return True
    return (time.time() - last) > STALE_AFTER

# --- main agent loop ---
def run_forever(step):
    while True:
        try:
            step()               # do one unit of real work
            write_heartbeat()    # record progress, not just liveness
        except Exception as err:
            log(err)
            time.sleep(5)        # back off, let the supervisor decide
        time.sleep(2)

The atomic os.replace matters: a reader must never see a half-written heartbeat. On Kubernetes you would express the same idea as a liveness probe that reads the heartbeat file and fails once it goes stale, and the orchestrator restarts the pod for you. Same logic, different owner of the restart.

Failure modes to plan for

The agents that stay up are the ones whose authors listed the failures in advance. The usual suspects:

  • Crash loops. A bad input kills the agent, the supervisor restarts it, it hits the same input, and repeats forever while burning tokens. Cap restarts per minute (the systemd StartLimit* fields above) and quarantine the poison task by marking it failed after N attempts.

  • Rate limits and 429s. The model API throttles you. Exponential backoff with jitter, plus a per-minute token budget, keeps one busy hour from starving every other task.

  • Runaway cost. An agent in a tight loop can spend real money fast. Set a hard daily token or dollar ceiling and stop the loop when you hit it. Alert, do not silently continue.

  • Silent wedging. The process is alive, the CPU is idle, nothing is happening, often a hung network call. This is why you monitor progress via the heartbeat, not just process liveness.

  • State corruption. A crash mid-write leaves a truncated file or a half-finished transaction. Atomic writes, database transactions, and the write-before-act ledger pattern prevent it.

  • Secret expiry. A rotated API key or an expired token takes the agent down at the worst time. Fail loudly with a clear message, and rotate through a secret store rather than an image rebuild.

Notice that none of these are model quality problems. A perfectly reasoning agent with no crash-loop guard and no cost ceiling is still a liability. The reliability work is separate from the intelligence work, and it is what "24/7" actually costs you in engineering time.

The bottom line

To build an autonomous AI agent that runs 24/7, treat it as a small distributed system, not a script. Six components: a scheduler, a model client, a tool layer, a durable memory store, a health check, and a supervisor. Keep the model and tools stateless, push everything durable into the store, make tools idempotent, and let a supervisor own restarts. Plan for crash loops, rate limits, runaway cost, and silent wedging before they happen, not after.

You can assemble all of this yourself on a $5 VPS with systemd, and for a single agent that is a fine choice. Once you want several agents, real probes, and persistent storage without becoming an infrastructure expert, a managed runtime like Sokko removes most of the restart-and-state plumbing so you can spend your time on what the agent should actually do. Start with the smallest version that survives a reboot, then add durability as the stakes go up.