Skip to content
All posts

blog/infrastructure-for-agent-swarms.md

Infrastructure for running agent swarms

8 min read

What it actually takes for one engineer to keep a fleet of AI coding agents fed, contained, and productive on hard problems.

ai-agentsinfrastructuresystems

One of my tool servers once accumulated 125 TCP connections stuck in CLOSE-WAIT, with a deadlocked JVM underneath them. No human team does that to its own tooling. Humans get bored, take lunch, give up. Agents retry — in parallel, at machine speed, until something gives.

That incident is the whole post in miniature. Over the first half of 2026, most of my serious engineering output — byte-exact game decompilations, a WebGPU engine port, firmware analysis tooling — was not typed by me. It came out of fleets of Claude Code sessions I directed like a small remote team. A swarm, it turns out, is a workload, with a cost structure and failure modes all its own, and almost nothing off the shelf addresses them. This post is about the infrastructure I ended up building — some of it, recursively, built by the agents it exists to serve.

Four problems recur when you point a swarm at a genuinely hard problem, and each one forced a project into existence:

  1. Fuel. Subscription quota is a perishable commodity, and a swarm burns it faster than one account can supply.
  2. Blast radius. An autonomous agent with real credentials on real infrastructure can do real damage.
  3. Stewardship. The artifacts a swarm produces need backup, cheap bulk storage, and a way to reach an agent from a phone without leaking secrets.
  4. Hands. Agents cannot decompile or diff anything unless a tool server safely exposes those capabilities — and survives them.

Fuel: quota as a control-systems problem

Quota windows reset on a schedule and never roll over — fuel that evaporates whether or not you burn it — and Claude Code authenticates as one account at a time. claude-swap, a pure-Go daemon and reverse proxy of about 12,400 lines across 30 commits, turns “which account should be burning right now” into a control loop. Unusually, it was designed before it was coded: the design doc was locked a full day before the first commit, with a “verified facts, tested live” section de-risking the hardest unknowns.

The keystone discovery was an undocumented usage endpoint that reports the five-hour, seven-day, and per-model quota windows, with reset timestamps, without consuming any message quota itself — though it is aggressively rate-limited. The design also mapped where Claude Code stores credentials, and surfaced the sharp edge that shaped everything downstream: refresh tokens are one-time-use and rotate on every refresh, so two processes touching the same credential must obey strict ownership rules or they brick each other.

Two cooperating modes fell out of that:

sessions            claude-swap                account pool
 s1 ──┐   ┌────────────────────────────┐    A [########..]
 s2 ──┼──▶│ proxy: pin session→account │──▶ B [###.......]
 s3 ──┘   │ shed saturated accounts    │    C [..........]
          │ one session at a time      │         │
          ├────────────────────────────┤         ▼
          │ switcher: watermarks, burn │   usage endpoint
          │ estimator, min dwell time  │◀──(rate-limited,
          └────────────────────────────┘    spends nothing)

The switching daemon hot-swaps the active credential before a window exhausts, ranking accounts by soonest-expiring window on a use-it-or-lose-it argument. The subtlest problem it solves is deciding under a vanishing signal: the usage endpoint rate-limits hardest exactly when the fleet is busiest, so the daemon has to estimate burn and bail out conservatively when it goes blind. The proxy mode injects a per-request bearer token with session pinning, so each session’s prompt cache survives an account rotation and a saturated account sheds one session at a time instead of collapsing into a thundering herd of 429s.

By mid-July it was shared fleet infrastructure on my tailnet, mining its own traffic — a built-in priced cost report and full transcript capture of every proxied exchange. Two honest caveats: the whole thing rests on undocumented endpoints that can change without notice, and multi-account rotation carries a terms-of-service posture to adopt deliberately, not by accident.

Blast radius: a sandbox platform that built itself

If claude-swap answers “how do you feed the swarm,” dream-serpent answers “how do you contain it.” It is a ground-up platform — Heroku, but for coding-agent sessions — where every session gets its own ephemeral KVM virtual machine wrapped in layers the agent inside cannot route around:

              internet
                 ▲
                 │ real credentials injected here,
                 │ never below this line
┌────────────────┴─────────────────┐
│ TLS-terminating egress gateway   │  swaps creds at boundary
├──────────────────────────────────┤
│ DNS-gated egress allowlist       │  unlisted names: refused
├──────────────────────────────────┤
│ default-deny firewall            │  everything else: dropped
├──────────────────────────────────┤
│ ephemeral KVM VM                 │
│   └─ agent session (untrusted)   │  holds nothing worth
└──────────────────────────────────┘  stealing

Each workload carries its own cryptographic identity, and long-lived secrets never enter the guest at all. If a VM is compromised, you rotate nothing, because there was never anything inside to steal.

The method is the real headline. Nearly the entire five-and-a-half-week build — roughly 4,600 commits on main, about 372,000 first-party lines of Go, Rust, and TypeScript, with a peak day of 936 commits — was produced by a self-directed fleet of agent sessions coordinating through infrastructure the project built for itself as it went. The hardest problems were exactly the ones a swarm creates:

  • Serialized landing. Twenty agents all try to push main at once. The answer was a single elected, systemd-supervised leader that fast-forward-lands exactly one gate-green integration branch at a time — over 6,600 landings by project end.
  • Merging state, not text. Concurrent agents constantly edit the same task-tracking JSON, so a custom git merge driver reconciles by task ID and status semantics (“done” beats “open”) instead of diffing lines. It was proven on a final-day reconcile spanning 443 commits.
  • Governance for an AI workforce. A 147-entry ratified decision log, cited by number throughout code and commits; an explicit anti-scaffold list of things deliberately not built; and an “epic sweep” pattern where agents audit other agents’ completed work. One July sweep surfaced three live security gaps — including a fail-open certificate check — all closed the same day.

The safety spine was demonstrated live, end to end, in a nested-VM testbed. The plan for its first daily-driver workload is hosting my own decompilation workspaces — the factory and the sandbox designed to meet.

Stewardship: the domestic corner

The smallest system, manclaw, applies the same discipline at household scale: a stdlib-only Go service (about 5,400 lines, built in seven days) forced into existence by a 3.7 TB storage pool sitting at 95–99% full. It provides an encrypted, byte-verified backup path to cloud object storage with customer-held keys, and it reclaimed roughly 435 GB of local disk by moving rebuildable bulk data to the cloud at about three dollars a month — without a single sudo along the way.

It also fronts a credential-isolated household agent: a pinned, security-reviewed agent runtime inside egress-locked rootless containers, behind a minimal gateway daemon, reachable from my phone over a dedicated messaging bot. The supply-chain review behind it avoided five separate curl-pipe-to-shell installs, and its best “verify, don’t assume” catch was a placeholder API key silently falling back to a different model than anyone believed was running — discovered only by asking the bot what model it was. Manclaw’s task queue closes the loop with dream-serpent: a Postgres-backed task DAG whose runner executes each task inside a fresh sandbox VM. Honest status: that live end-to-end path is currently blocked at VM boot, pending a kernel module and a reboot.

Hands: tool servers that survive a swarm

Fuel, containment, and stewardship are worth nothing if the agents cannot touch the problem. The rule across all of it is typed tools, not prompts: agents never eyeball raw assembly and guess. The tool layer is a set of Model Context Protocol servers, chiefly a fork of pyghidra-mcp — a programmatic front door to Ghidra, sitting atop my console-focused Ghidra fork — that lets an agent say “decompile the function at this address” without a GUI. The fork exists for two reasons: teaching the server my target formats (Xbox 360 executables, plus a from-scratch GameCube/Wii binary transcoder), and making it survive being pounded by concurrent agent traffic.

Which brings the opening scene back around. The hardening arc is a run of war stories — a Python GIL wedge on a 79 MB firmware image from a responsible-disclosure security project, a racing multi-binary project save — and the capstone was those 125 CLOSE-WAIT connections, traced to mixed sync and async handlers racing a single-threaded JVM into deadlock, fixed with one process-wide reentrant lock serializing all 34 tool methods. The recursive punchline is in the commit trailers: agents co-authored the fixes to the very server that serves agents.

A companion fork of objdiff changes what the tool emits rather than how it is invoked: a machine-readable mismatch-analysis engine with 21 pattern detectors that hands agents a root-cause diagnosis and a fixability verdict as structured JSON. Its nastiest bug was squarely an agent-infrastructure bug — a nondeterminism that made two byte-identical builds report different match percentages, corrupting the exact metric the agents optimize against.

What I actually learned

The four systems stack into one substrate:

┌───────────────────────────────────────────────┐
│ hands        MCP servers — ghidra, objdiff    │
├───────────────────────────────────────────────┤
│ stewardship  manclaw — backup, phone access   │
├───────────────────────────────────────────────┤
│ containment  dream-serpent — per-session VMs  │
├───────────────────────────────────────────────┤
│ fuel         claude-swap — quota control loop │
└───────────────────────────────────────────────┘

Behind a headline like “a swarm matched 40,000+ functions of a stripped retail binary in ten weeks” sits a quota daemon draining accounts to precise watermarks, a proxy preserving prompt caches, and a lock stopping agents from deadlocking the JVM they depend on.

None of it is finished, and some of it is fragile by construction — undocumented endpoints, open questions about shipping checked-in task state, a task queue stalled one reboot from working. But the lesson generalizes: agents are not a feature you sprinkle on. They are a workload, with the same demands as any other — capacity planning, isolation, observability, and tools designed for how they actually fail. Building that substrate turned out to be as interesting as anything running on top of it.

Infrastructure for running agent swarms | Free Wortley