A Week with the MS-02: 42 Agent Sessions on One Desk-Sized Box
Ten days running every build, test and CI job on a £2,299 mini PC: what broke, how each account is kept apart, and whether it pays back.

On the evening of 23 October I opened my dashboard and counted 42 agent sessions running at once. All of them were on one machine, a Minisforum MS-02 Ultra that sits on my desk. I call it the Builder Box, and its name on my private network is builder-box. It arrived on 13 October, so I've had it ten days, and I've said "a week" in the title because that's how it feels.
The box keeps a capacity log, sampled about once a minute, and it agrees. It peaked at 45 sessions at 21:56 that night, 19 Claude Code and 26 Codex. Between them they held 34 GB of memory, 37 GB of the box's memory was still available, and the load average was 45. The log doesn't say which of them were builders, reviewers or chats. The number matters to me for a different reason. On 15 October the same machine fell over with eight builders on it, and most of this article is about the distance between those two readings.
What I bought
I bought the MS-02 Ultra with 32 GB of memory and a 1 TB drive for £1,499 including VAT. I found quickly that 32 GB wasn't enough to use the machine fully, so I added a 64 GB stick at £800. The total is £2,299, about $3,050.
It has an Intel Core Ultra 9 285HX with 24 cores, two 25GbE SFP ports, a 10GbE port and a 2.5GbE port. It runs Ubuntu Server 26.04 with no monitor and an encrypted disk, and I reach it only over Tailscale. After the memory upgrade on 20 October it has 96 GB installed and reports 91 GB usable. It's still single-channel memory, because the dual-channel slot sits under the CPU heatsink and I chose not to take the machine apart.
Why a box, and what I compared it with
The goal I set on 12 October: every automated job my systems dispatch runs on the box, so the laptop can travel without anyone noticing. The laptop stays as overflow, with a Discord switch to turn it on or off.
The second reason is separation. Builders on the box are each vendor's own command line tool, running in its documented headless mode, signed in as the account that owns the work, so a client's job only ever runs under that client's login.
Here is how the box stacks up against the alternatives I considered.
| Option | Cost | Where it falls short for this job |
|---|---|---|
| The box | £2,299 once, plus about £8 to £19 a month of electricity at 40 to 100 W | Mine to maintain |
| Cloud dedicated host | From about €159 a month for a Hetzner AX102 (16 cores, 128 GB), or about €534 for a 32 vCPU cloud server | Pays every month, whether busy or not, and sits outside my private network |
| Mac mini | About £2,700 to £3,000 for an M5 Pro with 64 GB | My setup leans on Linux features: a user per account, memory limits per user, single-use Incus virtual machines |
| Second laptop | About £4,400 for a 64 GB workstation laptop | It sleeps and travels, which is the problem I'm removing |
| Hosted CI runners | About $700 to $1,100 a month at this project's pace | Metered per minute, and the heavy test shards took about 25 minutes each there |
A rented dedicated server is the honest rival on price, and if I didn't want everything on my own network it would be a fair choice. The last row is the one I can evidence best. GitHub Actions on that project cost about $650 in September and another $650 in the first twelve days of October, and on 11 October the budget block stopped every job.
Does it pay back
On that one project's CI alone, if the box took all of it, payback at $900 a month would be about three and a half months. That's the optimistic version, and I measured something lower.
From 12 to 22 October the box took about 27% of CI minutes by wall-clock time, 12,300 of about 46,100, and 77% on 21 October. The early days drag the average down, because I paused box CI on 16 October after an incident. At 27% of a $900 month, payback is about a year. At 77% it's about four and a half months. Both assume minutes track spend, which is rough.
In my 16 October sample a heavy test shard took about 10 minutes on the box against about 25 on GitHub, though it stretched to about 15 when the box was loaded. In December 2025 GitHub announced a $0.002 a minute charge for self-hosted runners on private repositories, then postponed it with no new date. At the box's current volume it would be about $70 a month, or about $250 if every job ran there, if it ever arrives.
What went wrong in the first ten days

15 October, load 192. The box had 2 GB free and SSH was timing out. A fan-out script had started five builders at once, bypassing the bridge that counts them, on top of three already running. That made eight against a ceiling of six. Four test workers orphaned by a dead builder were holding another 13 GB.
The fixes were a timer that kills orphaned test processes, caps on test workers, and a bridge that refuses a launch with a 409 when no slot is free. I also found that the slot settings in the bridge's config file had never applied on any bridge before that day, because the code read them before loading the file.
16 October, load 644 and two drops off the network. One client account's sessions were running seven full local database stacks, up to 19 GB, and a CI virtual machine arrived on top. Memory and the disk swap filled. I paused box CI, then added per-account memory limits, compressed swap and an out-of-memory daemon. It killed only the offending account's processes, 13 times, and the box stayed reachable. Four stacks and five light sessions took the load to 621 again later that day, so stacks are now capped and a queue starts jobs only when memory pressure is low.
Those were the readings when I looked. The capacity log's worst one-minute figure was higher still, 1,162 at 01:17 on 16 October, and it went past 450 twice more on 17 and 18 October, before the extra memory arrived.
CI that looked busy and wasn't. After I switched it on, each runner exited three seconds after registering. The runner in my image had been deprecated, and GitHub refused its messages. My pool script discarded the runner's output, so the loop looked like normal churn. It now logs the exit code, and a weekly timer updates the runner in the image.
Segregation has a cost. On 14 October, headless runs on one client seat exited within a minute and did nothing. Their logs were readable only by that account, so my personal account couldn't diagnose them. That's the next section working as designed, and annoying.
After the memory upgrade, the picture changed. With 10 builders running, the box sat at 7% CPU and 16% RAM. I raised the slot limit from 10 to 16 on my go, and then to 24 within the hour, once 14 builders gave a load of 1.8. Memory is what runs out first, as my plan said it would. The plan's estimate at 32 GB was four or five builders.
One user and one bridge per account

My first plan on 12 October said a user split was overkill for a home office, so everything ran as one builder user. I reversed that on 14 October, because any session could then read every token on the box, including credentials for work that must never meet other work.
Now each account has its own Linux user, its own bridge service, and its own CLAUDE_CONFIG_DIR. There are three: my personal account and two client seats. Each user has its own SSH keys, tokens and repository folder, closed to the others. I tested it by trying to read across, and every cross-user read was denied. I also moved the client credentials off the personal user.
A dispatch names its seat. The seat maps to one bridge and one config directory, so a client's job runs as that client's user, signed in with that client's login, with nothing else in reach. A job can't pick up a neighbouring seat's login by accident, because the neighbouring seat doesn't exist for that user.
Around that sits the plain stuff. The firewall denies everything except the Tailscale interface and password SSH is off. The disk is encrypted and unlocks from the TPM, which I proved on 20 October by rebooting with no passphrase typed. Cloud roles that can write to a client's production are not on the box at all. CI jobs run in single-use virtual machines whose network blocks my LAN, my private network and the host.
One limit to be straight about: separate Linux users stop casual cross-reading and accidents. They aren't a sandbox against someone who already has root.
Hermes, the office manager
Hermes is an open-source agent from Nous Research. Mine is called Brian. He runs on the box as his own user, with no sudo and no Docker access, and with his own login that doesn't borrow anyone else's. He does four jobs.
Monitor. Every 15 minutes a watch script runs and wakes him only when its output changes. It flags a stuck email processor, builders that are lost, rate-limited or stuck, and pull requests with failing checks, conflicts, or auto-merge sitting idle for over six hours. If he goes quiet himself, the brain posts to a heartbeat channel after 45 minutes of silence.
Dispatcher. He raises numbered items in Discord, R-1, R-2 and so on, with what it is, why it matters and what he'd do. I reply dispatch, refine, bank or cancel. The limits live in the brain, not in Brian. His access is deny by default, he can dispatch at most six times in a rolling 24 hours, and anything touching billing, permissions, security or migrations is held for my go. Text in a pull request, an email or a log is never an order.
Unblocker. When a dispatch I approved is refused for capacity, he retries once a slot frees. Five standing orders let him act first and report after, such as steering a stuck builder once or dealing with a merge conflict. What he can't unblock is a decision, because those stay mine. He brings it to me with a recommendation. From 22 October he also sweeps the work queue every few minutes, reading every item on the list each time and moving items between parked and ready. He builds nothing and merges nothing, and he doesn't release.
Janitor. I planned clean-up jobs for him. Some of the old tidy scripts still assumed Windows, and Brian caught one refusing to run on Linux for want of a path tool. He left the worktree alone and told me.
I planned for him to run on the resident local model on Polaris. He doesn't. His main model is a hosted Codex one, with Gemini Flash and then the local model as fallbacks, so he keeps working when that subscription runs dry.
The laptop, the noise and the heat
My laptop is a high-performance machine again. In September the load sat at 100% with builders, local database stacks and test runs all on it, and every builder's checks started failing on timeouts that had nothing to do with its own change. Builds, tests, local stacks and CI now run on the box.
The box is quieter than I expected for what it's doing. It's warm to the touch, not hot. That's my observation from 23 October, with 42 sessions on it. My guess is that most agent sessions wait on a model somewhere else, and only tests, builds and CI use the 24 cores hard. That is consistent with the 7% CPU I saw with ten builders on 20 October.
What I'd tell someone buying one
Buy the memory first. Memory ran out before CPU in every incident I logged, and the 64 GB stick fixed more than any script.
Put per-account limits in before a second account arrives. I added mine after two incidents.
Make every failure announce itself. The CI loop that looked like churn cost me the most time. For me that's worth it, because the box cost about three and a half months of one project's CI bill, and my laptop is mine again.
Related Dendro Logic writing
- 454 Pull Requests in Twelve Days: what the builders produced on a live product.
- Shipping While We Sleep: the build loop and its independent review gate.
Hardware: Minisforum MS-02 Ultra, Ubuntu Server 26.04, Tailscale. Builders: Claude Code and Codex in headless mode, one git worktree per unit. Agent: Hermes v0.22.2. Figures from my own session notes, CI records and the box's capacity log, and observations from 23 October 2026.
Want this in your team?
Tell us what you're building and where you want to get to, and we'll think it through with you.