blog/infrastructure-for-agent-swarms.md
Infrastructure for running agent swarms
What it actually takes for one engineer to keep a fleet of AI coding agents fed, contained, and productive on hard problems.
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:
- Fuel. Subscription quota is a perishable commodity, and a swarm burns it faster than one account can supply.
- Blast radius. An autonomous agent with real credentials on real infrastructure can do real damage.
- Stewardship. The artifacts a swarm produces need backup, cheap bulk storage, and a way to reach an agent from a phone without leaking secrets.
- 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.