Part two of The Studio in My Pocket. I run four software projects with AI agents, and they share one Windows PC and one Mac. This is how they take turns, and what happened on the Sunday one test needed the PC to itself.

Run from one chat, part two of a case study by Dawson McThay, beside an iPad mini showing a terminal session on my Mac, with the Mac and PC that run the tests behind it
AI illustration with a real screenshot: my iPad mini open to a terminal session on the Mac, with the Mac and PC that run the tests behind it.

At 11:51 on a Sunday morning, an AI agent sent me a question. It reached my iPad as a card:

Cancel two redundant Ruken CI runs so PR 108 gets PC runners sooner?

All four PC Linux runners are busy, and 22 Ruken jobs are queued. PR 108 (which stops the heavy stack job re-running on every merge to main) has waited since 16:12Z. Ahead of it are the push-to-main runs for the PR 106 merge. PR 106 already passed full CI as a PR, so those runs only repeat the same checks. GitHub has no queue priority, so cancelling is the only way to move 108 up.

  1. Cancel them: I cancel those two runs on RukenApp, which frees up to three PC slots for PR 108; they can be re-run later if wanted.
  2. Leave queue: Nothing is cancelled; PR 108 waits behind them, likely another hour or more.

Recommended: Cancel them. Those runs re-check code that already passed, and 108 cuts PC load for everything after it.

The card as sent, with two run numbers cut. Times in quoted messages are UTC; I'm in Chicago.

I tapped the first option. The thing that sent the card is a Claude project I call the hub. It writes no code and runs no tests. It exists so that four projects, each full of busy agents, can share two computers without anyone noticing the computers are the limit. This is the story of the afternoon it earned its keep, and of one test that needed the PC to itself and spent three hours failing to get it.

The setup, in one paragraph

Four projects: Floor Fifty and The Last Light (games), Ruken (a platform for browser games) and Automata Framework (the framework they're converging on). Each is its own Claude project, with a coordinator that knows its repository and a thread for every task. Part one is about how a single project keeps its agents honest. The shared machines are a Windows PC, with the graphics card and four Linux test runners in WSL, and an M1 Mac for WebKit. GitHub's hosted minutes ran out, so nearly every heavy job lands on those two boxes.

Questions go up through the hub; work goes down coordinator to coordinator
Questions go up through the hub; work goes down coordinator to coordinator

Until that Sunday, every coordinator scheduled only its own jobs. That works until four of them decide the same quiet hour is theirs.

The test that wanted a quiet room

Ruken's pull request #58 adds a playthrough of Floor Fifty inside Ruken, run on the PC's real graphics card, with a demo video as the receipt. It refuses to pass on a software renderer, and it needs one 45-minute job on a PC that is doing nothing else. That turned out to be a lot to ask of a Sunday.

11:38 a.m. The PC vanishes. The first Windows run died with "self-hosted runner lost communication" for ten minutes, and every other job on the machine sat untouched. Ruken's coordinator wrote to the hub, and its message contains a sentence I'd frame:

Nothing ran. At the same time every other queued job sat untouched for about 11 minutes until 16:49:45Z, so the whole PC (or its network) appears to have dropped off GitHub, maybe through sleep.

I found out later the PC had gone to sleep. From GitHub's side, a sleeping PC looks exactly like a very slow queue. It stays awake while plugged in now.

11:51 a.m. The clock runs out. The hub said go, and the run started. The backlog from the PC's nap was still draining, so the job spent its first 21 minutes just setting up (installing alone took 9 minutes, normally about 3). The playthrough then ran out of time with other jobs working beside it:

cancelled at the 45-min job limit, with no test result. Setup took 21 min because the PC was flooded by the backlog after it came back. [...] #58 needs one more GPU run, but only in a quiet window: no stack or e2e jobs on the PC, and #58's own verify and e2e-dom finished first.

1:15 p.m. The polite failure. Two stack jobs had been "running" for over an hour and forty minutes, against a normal ten to twenty-five. The hub asked me, in one word, whether to cancel them. One was cancelled and the other had finished on its own, and #58 got its go. Then it lost anyway, and its coordinator's report is the best bug report I read this year:

Time went to: 6 min waiting for pickup, 7.7 min checkout, 11 min install, 2.5 min FloorFifty checkout, then 7.7 min in "Wait until the fixed ports are free". Something held 5174/8787/8790/8443/9229. That was almost certainly the WSL M3.4 playthrough, since WSL shares localhost ports with Windows.

That wait is a function from an earlier problem on the same pull request. An earlier Windows run had failed with "Something else is already using port 5174", because a Linux job in WSL, whose ports Windows forwards to its own localhost, had grabbed it during the four minutes the Windows job spent building. So an agent taught the job to wait its turn:

bash
# Succeeds once nothing listens on the port, checking every 2 seconds for up to the given seconds (0: check once).
# The Windows GPU job sets RUKEN_PORT_WAIT_SECONDS (M1.7d): on the Owner's PC, a Linux playthrough in WSL can hold
# the same ports for a few minutes (Windows forwards WSL's ports to its own 127.0.0.1), and the job should wait
# its turn at the moment it needs the ports rather than fail.
wait_until_port_is_free() {
  local port="$1" seconds="$2" waited=0
  while port_is_busy "$port"; do
    if ((waited >= seconds)); then
      return 1
    fi
    sleep 2
    waited=$((waited + 2))
  done
  return 0
}

It worked perfectly. It waited 7.7 minutes, politely, and the 45-minute clock ran out while it did. A check is only true at the moment it runs, and good manners cost wall time.

The coordinator wrote the rule for the next attempt in one line, and the hub agreed: "no WSL playthrough or stack job may overlap the Windows GPU job." Pull request #108 merged, to stop the biggest source of load. Its merge to main immediately started another playthrough that held the same ports, which is the kind of joke a CI system makes at your expense. The hub kept Automata Framework's merges on hold, and everyone waited for the room to empty.

2:42 p.m. The quiet window. Everything drained. The exchange is short enough to quote whole, with the times added (AF is Automata Framework, the other project with jobs on that PC):

2:42 p.m. Ruken to hub: claim <gpu-runner> RukenApp#58 GPU ~45min (attempt 4 on da865d1; WSL idle: main Playthrough <run-id> and CI <run-id> both green, nothing queued). Other RukenApp pushes stay frozen until "done #58".

2:42 p.m. Hub to Ruken: go: <gpu-runner> for RukenApp#58 attempt 4 on da865d1. AF stays on hold, and no WSL jobs should run until "done #58". Send "done #58 <result>" when it finishes.

2:42 p.m. Hub to Automata Framework: Ruken #58 GPU attempt 4 just started on the PC (about 45 min). Keep the HOLD: no <linux-runner> runs until I send "clear". Your cloud work, like the timelapse, is fine.

2:57 p.m. Ruken to hub: done RukenApp#58 FAIL (attempt 4, real GPU, no timeout) [...] 3 of 4 playthrough tests pass on the AMD GPU. The new slow-link test (from #65, never run on this runner before) fails its "watched long enough" guard: it saw 100 and then 82 game frames and needs more than 100, because the real GPU boots too fast.

2:57 p.m. Hub to Automata Framework: Hub here: clear. Ruken #58 is done and the shared PC/WSL is free again.

Shortened where marked. Runner names and run IDs are placeholders.

Read that failure twice. For three hours the test had failed because the PC was too slow. When the PC was finally quiet, it failed because the PC was too fast: a test written for a slow link wants more than 100 frames of loading screen, and a real graphics card boots the game before it can produce them. It took four attempts and a little over three hours to get a clean room, and the first thing the clean room showed was a flaw in the test's guard. The failed window cost 15 minutes, not 45. The thread is fixing the guard and will ask for one more window, and #58 is, as I write, still a draft.

What the hub actually does

It does four things, all of them dull, and the dullness is the feature.

It shares the machines. A coordinator sends claim <runner> <repo#PR job> ~Nmin, the hub answers go or a place in line, and the coordinator sends done when it's finished. When a job needs the machine to itself, the hub sends every other project a hold, and a clear afterward. It's the etiquette of a shared office kitchen, kept by the one agent whose only job is remembering it. Its priority order fits in one sentence: "re-runs that block a merge go first, then the oldest claim." One more line I like, from the hub to a coordinator: "I can't see the runners myself, so your side is the source of truth." It schedules machines it cannot look at, entirely from what other agents tell it.

It turns every question into a tap. At 1:28 that same Sunday I told the load balancer to become the hub for everything. These are the rules I gave it, in part:

This project is now the hub for all my projects. Every question that needs me comes through here the moment it's asked, and you check status every 15 minutes. [...] 3. Questions, immediately: every other project sends its questions for me to you the moment they come up, instead of asking me in its own chat or waiting for a check. Post each one here as soon as it arrives, answerable in one word, with your recommendation, then send my answer straight back to that project.

The hub passed the rule to every coordinator in its own words:

Make each one answerable in one word, give a line of context and your recommendation. I'll post it to the owner and send the answer straight back to you.

An agent can spend ten minutes working out the options. I should spend ten seconds choosing. If a question can't be put that way, the agent hasn't finished thinking.

The work doesn't stop for the question, either. The coordinator carries on with the option it recommended, unless the step can't be undone. That's how I once changed three codebases with one tap. At 2:00 that afternoon the hub asked whether to enforce my whole workflow in every project from the next task on, and put the cost on the card:

All three projects follow the workflow only partly. [...] Review is CI plus audits plus your merge taps, not a Reviewer pass and sign-off per phase. Full compliance costs more tokens.

  1. Enforce: New tasks use Superpowers specs, the four roles with your models, and an Opus Reviewer plus your sign-off per phase.
  2. Keep as is: Projects carry on as they work today, which is cheaper and faster.

Recommended: Enforce. It's your stated standard against "vibe coded" work, and it starts with new tasks so nothing in flight is disrupted.

I tapped Enforce 25 seconds after it arrived. Seventeen minutes later it asked whether every shipped item should become content, or only the big milestones. I tapped "Per item", 19 seconds in.

It reports once an hour, and only on change. I asked for every 15 minutes, and the shortest interval the platform allows is 60. So the hub's hourly prompt is one paragraph, and its last instruction re-arms the next check. Each coordinator answers with one line, or "no change". This is the 2:31 p.m. check, with replies that came back within seconds:

Hub to Automata Framework: Hourly hub check: what has changed since your last update to me (about 19:25Z)? Reply with one line each for what shipped, what it waits on, and any blocker, with links. Reply "no change" if nothing did.

The Last Light: no change since 19:30. Still waiting on the owner's taps on the Mac runner for LOOK-G1 on #84 [...] No blockers otherwise.

Content Creation: No change since 19:30Z. [...] Waiting on: the content-inbox repo URL, the owner's review of articles 1 and 2, and the Oct 11 batch review.

Links cut. Yes, "articles 1 and 2" are the ones you're reading.

A status channel that talks every hour trains you to stop reading it, so when nothing changes the hub says nothing. What does change goes to one status page, which opens with the questions waiting on me.

The top of the hub's status page: two open questions, one from The Last Light with a recommended Yes, one from Floor Fifty that needs a typed reply
The top of the status page at 2:43 pm on October 11. The full page runs about ten screens.

It asks before it cancels. A job that has stopped making progress still holds its machine. The hub spots it, but it's someone's work, so cancelling is my call. That's the card at the top of this article.

What broke on the first day

Two coordinators refused an instruction. The hub told two of them to reroute their work, and both said no. The instruction hadn't come from me, and they wouldn't change course until I confirmed it myself. So I did, in each project, and then they rerouted. That's the behaviour I want: a hub that could steer my projects on its own say-so would be a hub anyone could impersonate. Now when a coordinator should change course, the hub passes along my own message, and it's my words they act on.

Replies bounced. The hub first tried to answer the task threads under each coordinator, and the replies never landed. So the rule became coordinator to coordinator, which costs one extra hop and has lost nothing since.

What's next

Floor Fifty launches on Ruken on Monday, November 30, so the two projects now share one release, and the queue matters more: the same PC has to carry the game's tests and the platform's. #58 is waiting for its fifth attempt. When the guard is fixed, the thread will claim the GPU, the hub will send a hold and then a go, the PC will run one job in a silent room, and I'll read the result on the iPad in one glance.

That's the whole idea. Not a smarter agent. A machine room with manners.


If you run agents across several repositories, your own or a client's, and want a hub like this built for them, I build it: shared hardware that takes turns, decisions that arrive as a tap, and status that only speaks when something changed. Email me or find me on LinkedIn.

Part one covers how each project keeps its agents honest. I'm also on GitHub and Instagram, and Floor Fifty has its own page.