How I Built a Twenty-Agent Fleet on One Desktop

2026-09-09

I fix a lot of small issues. Not deep ones, mostly: a dialog that clips at 380px, a confirm that guards one call site and leaves its sibling unguarded, a label that says the wrong thing. Individually they take twenty minutes. There are a hundred of them.

That workload parallelises well. My brain can hold four or five shallow problems at once and switch between them cheaply. My computer could not, and closing that gap turned into a tool I've now rebuilt five times.

What it does now

Twenty Claude Code sessions run at once on one desktop. Each gets its own git worktree, picks up one ticket, and works it end to end: reproduce the bug, capture a screenshot of it happening, make the fix, capture the same screen again, open the PR with both images attached, and move the ticket to review. A launcher session watches the whole fleet, refills it as sessions finish, and unblocks the ones that get stuck.

The before-and-after screenshots are the part I'd keep if I had to throw everything else away. Reviewing a fix is fast when you can see in two seconds whether it did what it claims, and the capture doubles as proof the bug was ever real. A surprising number of reported issues evaporate the moment you try to photograph one.

None of that existed in the first version. Each capability arrived because something broke and forced it.

Rebuild one: getting rid of ports

The original setup ran one dev:portless server per branch. Every branch I had open meant another Next.js process and another Cloudflare Worker sitting in memory, burning RAM and CPU whether I was looking at it or not. Each one needed a port, which I assigned by hand and then had to remember.

Four terminals in, I was spending more attention on bookkeeping than on the bugs. Which port was session 7? Is this the branch I think it is?

So the first thing I built removed ports entirely, routing on hostnames instead: a branch gets https://testing.app.localhost rather than a number I have to track. That is where the name comes from. The tool is called portless-setup because killing ports was the whole of version one.

It reads like a small ergonomic change. It was the thing that made everything after it possible, because it turned a question I had to answer constantly into one nobody asks.

Rebuild two: one server, many workers

With ports gone I could run more sessions, so I did, and hit memory almost immediately. Screenshots need a running app, a running app needs a server, and servers are what I had just finished economising on.

I upgraded the machine from 32 GB to 64 GB, which is the boring half of the answer.

The useful half was noticing that a session writing code doesn't need a server for most of its life. It needs one at the moment it captures, and not before. So the fleet split into two roles: a small number of persistent testing sessions run the only live servers, and the working sessions are serverless, borrowing a testing slot when they're ready to shoot.

Concurrency went from ten or twelve to twenty. Two testing servers, twenty workers, and for a while that felt like the finished thing.

Rebuild three: the queue I couldn't see

Twenty workers against two capture slots is a ratio that looks fine written down.

A capture against a warm local slot takes two to three minutes. That is fast until eleven sessions want a slot at the same time, at which point most of the fleet is sitting on a lock doing nothing. My status display showed twenty healthy sessions, because they were healthy. They were also parked, and nothing on screen said so. I grew the pool from two slots to four, which helped and did not fix it, since the arithmetic was still against me.

What actually broke the queue open was giving sessions somewhere else to go. Claude can run sessions in cloud sandboxes, and a sandbox has no contention at all. It is much slower per capture, fifteen to twenty-five minutes against two to three, because it clones the repo, installs about 2,700 packages, migrates and boots from nothing. But I can have as many as I want at once.

Captures now try a local slot first and spill to the cloud when every slot is busy. One attempt at the lock, and a busy answer sends the session straight to a sandbox, because the patient retry loop was itself the queue I was trying to delete.

The part I didn't predict: the cloud path produces better evidence. A local testing slot accumulates merges from every session that has borrowed it, so its "before" state is a mashup rather than a clean baseline. A sandbox runs its before phase on untouched main. The slower path turned out to be the more trustworthy one, which changed how I think about when to reach for it.

Rebuild four: the machine switching itself off

Then the desktop started powering down mid-run. Not a crash, not a blue screen: the supply cutting out, everything dark, twenty sessions and their uncommitted work gone.

I assumed memory, because memory had been the answer twice already. It wasn't. The logs showed 35 to 51 GB free at the moment of every shutdown.

What twenty sessions do at launch is start twenty dependency installs simultaneously. A cold twenty-four-worktree build writes around 314,000 files, which is a sustained, parallel load across CPU and disk and everything drawing current with them. The power supply was hitting a limit the operating system never got to report.

I underclocked the CPU by 15%, which made it rarer without stopping it. The rest had to come from software. I couldn't fix the hardware fault, but I could stop producing the load profile that triggered it: installs now run in small waves with a settle gap between them, and free memory is re-checked before every single one instead of once at the start. In practice that meant two concurrent installs rather than twenty.

Stable at last. Also slow. Two at a time against a 26-minute install meant a ten-session top-up took most of an hour, and sessions were finishing faster than the fleet could replace them, so it drained instead of filling.

The fix that made the limit irrelevant

The last rebuild is my favourite, because it solved something I had stopped classifying as a problem.

Every one of those installs was downloading and extracting the same 2,700 packages already sitting on my disk, twenty times over. So sessions now clone a prepared dependency tree instead: a local file copy, gated on an exact lockfile match so a mismatch falls back to a real install rather than quietly producing a subtly wrong one.

Twenty-six minutes became about 99 seconds.

And then the consequence I like most. A clone peaks around 1 GB of memory where an npm install peaks around 4, so four concurrent clones cost less than the two concurrent installs I had throttled down to. The cap I'd added to keep the machine alive stopped being the binding constraint, and I could raise it and run faster than before the crashes ever started.

The other half: teaching it to watch itself

Everything above is about capacity. None of it matters if running twenty sessions means personally supervising twenty sessions, and for a while it did. I sat there watching a wall of terminals for the one that had gone quiet.

The first change was letting sessions announce themselves. A session that finishes writes a small flag file, and the launcher tears down its worktree seconds later instead of discovering the completion on a five-minute poll. Reclaiming a finished worktree frees about 3 GB, so noticing quickly is the difference between a fleet that refills and one that slowly starves.

Then the same idea for failure. A session can only see its own worktree, so it genuinely cannot tell a broken testing server from one another session is mid-rebuild on, or a wedged lock from a merely busy one. Before, it would either sit silent, escalate to me, or work around the blocker, and that third option is how sessions started quietly spinning up their own servers again. Now a blocked session writes what it observed and a category, then carries on with anything unaffected.

The launcher answers those, and the rule I settled on is that it diagnoses rather than relays. It can see the whole machine, so it checks whether the server is actually down or just compiling, fixes what is fixable, sends that session a specific answer, and clears the flag so a recurrence raises a fresh one. A session told “slot 1 is busy with session 18's capture, retry in a few minutes” can keep working. One told “unclear” is stuck.

The last piece lives outside Claude entirely. My machine hard-resets occasionally, and hitting the account's usage limit kills every session and the launcher at the same moment. Neither event leaves anything alive that could react to it. So the guard is a plain PowerShell script that snapshots every session's uncommitted work to patch files every few minutes and records what each session was working on. It spends no tokens, which is the entire point: it still works when the account that runs everything else is exhausted.

What I'd take from it

Four rebuilds for capacity, one for supervision, and every fix revealed the next wall. That kept surprising me, and I now think it is just what scaling something feels like from the inside.

Looking back through the commit history, though, the walls are not what I actually spent my time on. Nearly every bug I fixed along the way was the same bug wearing different clothes: something reporting success while failing.

git worktree remove deregisters a worktree and deletes its folder, and it routinely does the first and fails the second when a shell still holds a file open. It exits zero. I had eleven sessions marked reclaimed with eleven folders still on disk, about 33 GB of them, and a top-up gate counting those folders as a full fleet and never firing.

A capture slot with a live server and a dead worker answers every page with a 200 and every API call with a 500. The health check asked whether any node process was running, got yes, and moved on.

A parallel-install change took my last session window from 45 minutes to 20 seconds, which was real. The serial loop it replaced had also been guaranteeing that the testing server finished installing before any worker existed, which nobody had written down. Sessions came alive at 20 seconds against a slot that started serving at 35 to 45 minutes, and in that gap the fleet cheerfully captured screenshots of a 404 page.

The fix each time was the same shape: stop asking a question whose answer is a proxy, and ask the one you actually care about. Does the URL respond, rather than does node_modules exist. Is the folder gone, rather than did the command exit zero. Has this session been silent for eight minutes after a clean stop, rather than does its last message match some wording that changes between releases.

The power supply story is that same lesson in hardware. I assumed memory twice because memory had been the answer twice, and the free-RAM reading at the moment of failure is the only reason I looked at current draw instead.

If you want the longer version of how the design side of my job got here, I wrote about that in becoming a design engineer.

We're redefining zero-trust — so you can protect your accounts with confidence.

Identity is your first and last line of defense, and the root cause of most application security breaches. Multifactor's provably secure zero-trust solutions cryptographically guarantee that only authorized users can access sensitive data, turning identity into your greatest asset in the fight against cyber threats. Learn more about our research, or reach out to explore working together.

Related Posts

I'm a Designer. Last Week I Shipped Dark Mode to Production.

I'm a Designer. Last Week I Shipped Dark Mode to Production.

2026-07-27

How AI tooling closed the gap between design and code at Multifactor — and what it means to be a design engineer now.

We're Hiring

We're Hiring

2026-03-02

Multifactor is hiring. If you are a talented engineering leader or individual contributor who cares deeply about forging the future of authentication, authorization, and auditing for the agentic era, you should probably check out our open positions.

Multifactor Versus Password Managers: Securing Capabilities, Not Credentials

Multifactor Versus Password Managers: Securing Capabilities, Not Credentials

2026-01-31

Say goodbye to password managers and hello to Multifactor, the world’s first true Account Manager!