I've been experimenting with using AI to bring classic games to old Macs. There are six games, each running on an open-source engine: Xash3D (Half-Life), QuakeSpasm (Quake), Yamagi Quake II, ioquake3 (Quake III), Aleph One (Marathon) and KeeperFX (Dungeon Keeper). Each one ships as a single universal binary that runs on everything from a PowerPC G3 to Apple Silicon. KeeperFX is the exception: it only runs on Apple Silicon.
It started as one Claude Code session working on one game. It's now a team of ten AI agents, each with its own job. This post is about how that team works and what it has got done so far.
# The short version
- One agent per job. Each game has its own agent. A few helper agents look after the shared things.
- One manager. A manager agent decides what gets worked on and in what order. It doesn't write game code.
- One rulebook. Every agent reads the same short set of rules.
- A checking script. Every five minutes a plain script called telemetry checks on every agent, the job board and the Macs, and nudges any agent that's idle with work waiting.
- Guardrails as well as trust. Small automatic checks stop common risky mistakes before they happen. The rest relies on the model following the rules, which is risky.
- Simple quality gates. A short list says what "good" means, and scripts check it.
- Proof with evidence. A job isn't done until there is evidence, such as a measured frame rate on a real Mac.
# The long version
The post has four parts:
- The team: what an agent is, who does what, and what each one is given.
- Ways of working: how work moves, what keeps it moving, and what "done" means.
- Keeping costs down: where the savings come from, and how agents restart.
- Results: the work so far, and some tips if you want to try this.
# Part 1: The team
Each agent is one copy of Claude Code running in a terminal window. If you don't know what an agent is or how one works, read this first.
# The team of agents (and me)
There are ten agents. Each one is a separate Claude Code session running inside tmux, a program that keeps many terminal windows open in one place, with one tab per agent.
- Six game agents. Each owns one game. It builds the game, tests it on real old Macs, makes it faster, fixes bugs and publishes new versions.
- A machines agent. I have a room of old Macs. This agent looks after them: who is using which Mac, the tools installed on them, and the shared tooling and test scripts every game uses to deploy and test a build.
- A game servers agent. The games can be played online. This agent looks after deployment of their server builds and the website to administer them. It only starts when there is server work to do.
- An SDL agent. SDL is a library that games use to draw on screen and read the keyboard. Old Macs need specially patched versions. This agent keeps one tested copy for each old version of macOS. It also only starts when needed.
- The manager. It reads every new ticket and decides whether it's worth doing. It sets the order, keeps the rules and scripts up to date, and helps with the hardest bugs, because it runs on a bigger model than the others (see below).
And then there's me. The rulebook reserves three decisions for me:
- anything that costs money;
- anything permanent outside its own project, such as deleting a repository, wiping one of the old Macs, or changing the cloud account the servers run on;
- changing what another agent's project is for, for example asking the Quake agent to stop supporting the oldest Macs.
Inside its own project, an agent can act freely, even publish releases, because git keeps every change and any mistake can be fixed with another commit. For any other choice, the agent picks a sensible default, writes down what it chose and why, and carries on. If it truly can't move, it says so in one line starting PARKED: and waits.
In practice, a few more jobs are mine because no agent can do them:
- Starting new things. Creating a new repository or fork, and setting up accounts such as Oracle, Cloudflare and GitHub.
- Giving permission. The rulebook says agents can't widen what they, or other agents, are allowed to do. Only I can, for example "you may publish releases without asking". The manager directs the team, but it can't hand out permission. I keep a log of every permission I've given. This is a rule the agents follow, not a lock. Yes, this does rely on trust in the model. How much of this is trust? below explains what that means.
- Setting targets. How fast is fast enough, and which Macs get the best graphics.
- Choosing what to change after a look back. I ask for a review of what went wrong, the agents dig through the evidence and suggest changes, and I decide which ones happen.
- Anything that needs hands. Switching on an old Mac, or turning one off and on again when it's stuck.
- Playing the games. I still play each game before a big release.
Agents don't share a conversation. They send each other short messages through the launcher, which saves them as files. What keeps the agents running is telemetry: a plain script, not an AI, that checks on every agent every five minutes and nudges any that are sitting idle with messages or work waiting. Part 2 explains it in more detail.
# The right model for the job
Claude comes in different sizes. A bigger model thinks more carefully, but it is slower and costs more for every word it reads. So each job gets the size it needs. One small settings file sets it for the whole team:
- the manager: Claude Opus, the biggest model, with high effort;
- the six game agents, the machines agent and the SDL agent: Claude Sonnet, with high effort;
- the game servers agent: Claude Sonnet, with medium effort;
- telemetry's wake-up message: Claude Haiku, the smallest.
"Effort" is how long the model is allowed to think before it answers. At the time of writing, these are Opus 5.5, Sonnet 5 and Haiku 4.5.
The reasons are simple:
- The manager does the least typing and the hardest thinking. It reads evidence from other agents, decides whether a ticket is worth doing, and helps with bugs the owner couldn't crack. A mistake there spreads to the whole team, so it gets the biggest model.
- The game agents do lots of steps. Build, copy to a Mac, run, measure, read the log, repeat. Sonnet is quick and strong at this, and its lower cost matters because they work all day.
- The game servers agent's work is mostly routine, so it thinks for less time.
- The wake-up only sends one line, so the smallest model is plenty.
Here's how a hard bug reaches the manager. If an agent tries twice and still can't fix a bug, it sends the manager a short packet: the failing build and command, which Mac, what it expected and what it saw, the logs that matter, and one question. The manager reads that and the code, then suggests one test whose result will tell the possible causes apart. The owner runs the test and fixes its own code. The manager never edits a game.
# Repository setup
Each agent owns exactly one code repository (a project folder with its full history, kept on GitHub). It can read any other repository, but it can only change its own. If the Quake agent finds a problem in the shared test scripts, it doesn't fix them itself. It files a ticket for the machines agent, who owns them.
This means two agents never edit the same file at the same time. And when something breaks, it's always clear whose job it is to fix it.
In the middle sits the team repository. It holds no game code, and the manager is the only agent that changes it. It holds:
- the rulebook (
POLICY.md) - a short brief for each agent, like a job description
- the guardrails: small scripts that run automatically
- the tools, which are small scripts:
- the launcher starts each agent in its own tmux window, with its rulebook, brief, model and hooks already set. It carries messages between agents, and it does the safe restarts described in Part 3. One command starts or stops the whole team;
- the job board helpers read the shared GitHub board once and save a copy for everyone, put a new ticket on the board in the Triage column, and move a ticket between columns with a signed comment saying who moved it and why. They also refuse to reopen a finished ticket by accident
- telemetry is the checking script that looks in on every agent every five minutes
- a few more helpers check every project's automatic builds, list each game's latest release, and switch off the old Macs at the end of the day
# Briefs, rules, guardrails and tools
There are five layers. The first three tell the agents what to do. The last two check what they actually do.
1. The rulebook. About 120 lines that every agent shares: who decides what, how to prove a result, how to publish a release, and how to share the old Macs. When two instructions clash, the rulebook wins. Read it here: POLICY.md.
2. The brief. Each agent gets a short job description, usually under 15 lines. It only holds facts that are special to that job. Nothing in a brief repeats the rulebook, because two copies of a rule drift apart. Here are all ten:
- games: Half-Life, Quake, Quake II, Quake III, Marathon, Dungeon Keeper;
- helpers: machines, game servers, SDL;
- the manager.
3. The startup note. Every time an agent starts, a script prints a fresh note: its approved tickets, any unread messages, any broken builds, and which Macs are switched on. So the agent starts from the real situation, not from memory. Here is a real one from the Quake II agent.
4. The guardrails. These are hooks: small scripts that Claude Code runs automatically before or after the agent does something.
- The guard runs before every command. It blocks risky ones, like force-pushing, deleting a repository, adding every file at once, or shutting down a Mac.
- The stop gate runs whenever the agent tries to stop. It sends the agent back to work if it has unread messages, a broken build, unpushed commits or approved work left, or if its last reply was a question to nobody. For approved work, a
PARKED:line saying why it can't carry on lets it stop. If the gate has already sent the agent back once, the next stop is allowed, so it can't loop forever.
5. Telemetry. A plain script that checks on everyone every five minutes. Part 2 explains it.
The team repository's own notes are here too. README.md explains how it's laid out, and CLAUDE.md is the quick reference the manager reads, since it's the agent that works on this repository.
# How much of this is trust?
A lot of it is trust.
The agents run on my Mac under a separate non-admin user account, so they can't read my personal files. They share one GitHub login, the keys to the old Macs, and the cloud keys the game server agent needs. Claude Code's permission prompts are off. Within those limits, an agent can do anything its user can do from a terminal. The GitHub login has restrictions on things that I do not want to allow, like deleting a repository. The agents do have full sudo (admin) access to each of my old Macs since those machines are just a plain install of an operating system with nothing personal on them. If an agent one day nukes my iMac G5 I'll just re-install Mac OS X 10.5 on it.
Most of the rules in this post are rules the model chooses to follow. "Only change your own repository" and "only I give permission" are written in the rulebook, and nothing technical enforces them. The guard hook strictly enforces a few, such as no force-pushing, no deleting a tag and no adding every file at once.
Here's what catches mistakes and limits the damage beyond blind trust in model behaviour:
- Tripwires. The guard checks every command before it runs. Blocks deleting, archiving or renaming a repository, making one public or private, deleting the main branch or a release tag, and wiping a home folder or a whole project folder. But the guard only reads the command as it was typed. The same command inside a script would get past it.
- The GitHub login can't delete a repository. I took that permission away, so a delete fails at GitHub, however the command is typed.
- The agents have their own Mac user. They can't read or change my own files.
- Everything is in git. Every change is a commit pushed to GitHub, and every repository also has a full copy on my laptop.
- Money has a hard cap. The Oracle account has never been upgraded from Always Free. An account like that refuses to create anything that would cost money, instead of charging for it.
- Changes get noticed. Telemetry, the signed board moves and the evidence rules mean an odd change doesn't go unseen for long.
What I could still do, from easiest to hardest. Each one turns "please don't" into "can't":
- Give the agents a GitHub login that only works on named repositories, with even more strict permissions for only what they need.
- Give the cloud keys to the game servers agent only, not to everyone.
- Switch on Claude Code's sandbox, which limits which folders and websites a command can reach.
- Run the whole team inside a virtual machine, so the worst case is "throw it away and start again".
# Part 2: Ways of working
# How work moves
All work is written down as GitHub issues (tickets). Each ticket lives in the repository of the agent who will do the work. Every ticket from every repository also sits on one shared GitHub project board, with a column for each stage:
- Triage. Any agent can file a ticket and put it here. Only the manager moves it on.
- Measuring. Approved. The first step is to measure, not to change things.
- Ready and In progress. Queued, then being worked on.
- Blocked. Waiting on something outside the agent's control, usually a Mac that is switched off.
- Review. An optional pause if the agent wants a second look.
- Done. The agent that did the work closes it, with a comment showing the evidence.
A few habits keep the board useful:
- Work for someone else becomes a ticket in their repository. Agents don't swap jobs by message alone. A message points to a ticket; the ticket holds the detail.
- Every move is signed. When an agent moves a ticket, a small script adds a one-line comment saying who moved it and why. So I can always see who did what.
- Commits point to tickets. Each commit says which ticket it belongs to, so the history links the code to the reason for it.
- Reading the board is cheap. GitHub limits how many requests an account can make each hour, and ten agents share one account. So telemetry reads the whole board once every five minutes and saves a copy. The agents and their startup notes read that copy.
# Telemetry keeps work moving
"Telemetry" is the script regularly taking readings from the agents since they don't share context and I'm not always watching. Its job is to go round regularly, check on everyone, and give a nudge where one is needed. It is an ordinary script, not an AI. It can't write code, choose what to work on or answer a question. It only detects certain things and passes them on.
It runs in its own tmux window on two timers:
- every ten seconds it redraws a screen for me to watch;
- every five minutes it does a full round: look, write it down, nudge.
1. It looks. Every time an agent does something, a hook updates a small status card for that agent. The card is a tiny file that says whether the agent is busy or idle, what it did last, when it last moved, and how full its memory is. Telemetry reads every card. Then it gathers the outside facts:
- the job board, to see which agent has approved tickets;
- which Macs are switched on, and which agent is using each one;
- GitHub's automatic build checks, to see if any game's build is broken;
- the latest release of each game;
- each agent's unread messages.
2. It writes it down. It turns what it found into a short list, one line for each thing that needs attention. These lines are from real rounds on the day I wrote this:
WAKE quakespasm: idle 3m with newly approved old-mac-quakespasm#69 CHECKPOINT-ASKED halflife: context 312K RESTART quakespasm: natural break, context 262K START infra: retro-server-infra#29 PARKED keeperfx (38m): wrapped up
The manager agent reads this list every ten minutes. It doesn't have to check on ten agents one by one. It only acts on the lines that need a decision, such as a new ticket to approve.
3. It nudges. For a few simple cases it acts on its own, following fixed rules:
- Idle, but has work or messages? If an agent has sat idle for three minutes with unread messages or approved tickets, it gets a one-line wake-up. It won't be nudged again for 20 minutes, so a busy agent isn't pestered. An agent that has parked (said "I can't move until a Mac is switched on") is left alone, unless a message or a newly approved ticket arrives.
- Memory getting full? It asks the agent to save its notes at its next good stopping point, and then restarts it fresh. Part 3 explains this.
- Build broken? It sends the owning agent one message about it.
- A helper agent not running, but work waiting for it? It starts that agent.
- Usage limit reached? If my Claude account has hit its limit, it wakes nobody until the time the limit resets.
The wake-up is the only step that uses AI: one small call to Claude Haiku that sends each idle agent the same fixed sentence. Everything else is counting and comparing, which costs nothing. Then it waits five minutes and does it all again in a loop.
The screen I watch redraws every ten seconds from the files telemetry writes, so updating it costs nothing either:
From top to bottom, that screen shows:
- the summary: how many agents are working or idle, unread messages, whether every build is passing, how many Macs are on, and how many tickets are in each column;
- Sessions: one row per agent, with a bar showing how full its memory is (it turns red near the limit), its current ticket and what it's doing right now;
- Games: each game's latest release, how old it is, and whether its build checks pass;
- Macs: each Mac's macOS version, and which agent is using it (here, the Quake III agent is installing a build on the G4 Mac mini);
- Telemetry actions: what it did on its last round, such as waking two idle agents that had messages.
# Definition of done
A ticket is only done when the work is good enough, so everyone needs to know what "good" looks like. For this project the answer is short. A new version of a game is ready to release when:
- it's fast enough. It runs at 25 frames per second or more on every kind of Mac (20 on the G3, the oldest). Above that, the aim is as many visual effects and as high a frame rate as each Mac can manage, and the best graphics on the G5s. If a Mac is too slow, the first answer is to make the code faster, not to switch effects off.
- it starts on every Mac. A smoke test installs the exact download a player would get, opens it the way a player would, and checks that the game really runs. Every release must pass on a set test Mac, and on as many of the others as are switched on.
- it looks and plays right. Numbers can't tell you everything, so a person looks at it. The agents take screenshots, and I play each game before a big release.
- it works online. The oldest Mac and the newest must be able to play in the same game on the server.
- one download works everywhere, from a G3 to Apple Silicon (apart from Dungeon Keeper which is Apple silicon only).
- the automatic checks pass. GitHub builds the code after every change, and the build must stay green.
These gates are simple on purpose. Each one is either a number, a pass or fail, or my own eyes. Most of them are checked by a script, and the agents write down which ones they checked when they close a ticket.
A different project would need different gates. For an online shop, or any other ordinary piece of software, you might say a change is only done when:
- the tests pass: small tests for each part, tests of the parts working together, and end-to-end tests that click through a whole journey like a customer would;
- the code stays simple: for example, no function with a cyclomatic complexity over 10. That is a count of the different paths through a piece of code, and a high number means it's hard to follow and easy to break;
- there are no known security holes in the code or in the libraries it uses;
- pages load in under two seconds;
- accessibility checks pass, so people using screen readers or keyboards can use it.
The details change, but the idea stays the same. Write the gates down in one place. Make a script check each one. Count a job as done only when the gates pass and the evidence is saved. The clearer the gates, the less the agents need to ask me.
# Proof, not promises
The gates only work if the results are real. So the rule I care about most is simple: a claim needs evidence.
When an agent says "Quake is now faster on the G4", I want to know exactly which build it tested, on which Mac, with which settings, and what the numbers were. Frame-rate tests go through a shared script that bundles all of this together. It marks a run as invalid if something doesn't add up.
For example, a run is invalid if:
- the game installed on the Mac isn't the build being tested;
- the game's clock didn't move, so it wasn't really running;
- the settings the game actually used aren't the ones that were asked for.
A check that couldn't run counts as "not tested", never as a pass. This found a real problem: screenshots meant to prove a game was running were showing the Mac's desktop, because an old iMac was putting its screen to sleep after one minute. The runs had passed because the two screenshots were different. The only difference was the clock in the corner. The check was fixed for every game at once.
# Part 3: Keeping costs down
# Where the savings come from
AI agents are charged by the amount of text they read and write. Ten agents working all day read a lot. These made the biggest difference:
- Restarts. An agent reached over 700,000 tokens per step before the restart rules existed. Now an agent is asked to restart once it passes 300,000, and it usually restarts well below that.
- The right size of model. Only the manager runs on the biggest model. The agents that work all day run on a smaller, cheaper one.
- Plain scripts for checking. Watching the board, the Macs and the builds doesn't need AI. Only waking an agent does.
- Short briefs, no repeats. Every line in a brief is re-read by that agent at every step, all day.
- Quiet logs. Build output goes to a file, and the agent reads only the lines that matter.
- Rule changes in batches. A rule change means every agent has to restart to read it, so I save changes up and make them together.
# Saving notes and restarting
An agent re-reads its whole conversation at every step. The longer it runs, the more each step costs, and the more old, out-of-date detail it carries around. So agents are restarted regularly with a fresh memory. The trick is not losing what they were doing.
The agent doesn't decide when to restart, and it never restarts itself. Scripts decide when to ask, check that it's safe, and do the restart. The agent's part is to write good notes.
Here are the four steps in detail:
Step 1: A restart becomes due. Three things can make a restart due:
- its memory has passed 300,000 tokens (tokens are the chunks of text an AI reads, roughly three-quarters of a word each);
- the rulebook or its brief has changed, because an agent only reads those when it starts;
- it has gone idle with more than 150,000 tokens. This is a natural break: it has just finished a job or is waiting for a Mac, so starting fresh loses nothing.
Telemetry spots the first two and sends the agent a message. The stop gate spots the third as the agent stops. Either way, the agent is told to finish its current step first. Nothing restarts mid-task.
Step 2: The agent writes a handover note. The aim is that a brand-new session can carry on without the old conversation. So the agent pushes its code, lets go of any Mac it was using, and writes down what it did, what's next, and anything it only half knows.
Step 3: The launcher checks it's safe. The agent runs one command, called a checkpoint. The launcher checks that the code is pushed, nothing is still running, no Mac is held and the note is saved. If anything fails, the agent is told what and tries again later. If everything passes, the agent stops.
Step 4: A fresh session takes over. Within five minutes, telemetry sees the agent has stopped with a checkpoint. It closes the old session and opens a new one in the same tmux window. The new session reads the handover note and a fresh startup note, and carries on where the old one left off.
The handover notes are kept, so if the whole team is stopped at night, each agent picks up from its own notes the next morning.
# Part 4: Results
# Work so far, in numbers
These numbers come from GitHub on 25 September 2026, across all ten repositories.
- 497 tickets have been filed. 474 are done (95%), 5 were dropped as not worth doing, and 18 are still open.
- 42 of them came before the team existed. The other 455 arrived in the 35 days since 22 August: about 13 new tickets a day, even counting a week off.
- The typical ticket (the median) was closed 17 hours after it was filed. 269 of the 474 (57%) were closed within a day. The average is much longer, at 78 hours, because a few waited weeks for a Mac to be switched on.
- Tickets have 2,660 comments between them, about 5 each. Most are evidence: numbers, logs and which build was tested.
- 203 tickets (41%) are labelled "needs measurement", meaning the first job was to measure, not to change code.
- 112 tickets (23%) were filed by one agent for another, usually a game agent asking the machines agent or the game servers agent for something.
- Since 22 August there have been about 1,700 commits (about 49 a day) and over 80 release tags, 12 of them for servers. Every commit the agents made shows my name, because they all use my git account.
The machines agent has the most tickets (119, or 24%). Test rigs and the Mac lab are the biggest kind of work: getting a game onto a 20-year-old Mac, running it and proving what happened takes more work than changing the game.
Some of the work I'm most pleased with:
- Half-Life. One of the first big wins. The agent changed the engine to draw the world in one pass instead of two, which took the G3 from 25.4 to 33.1 frames per second, 30% faster, but it caused some lighting glitches. The agent then fixed them, so the flashlight now works properly on every Mac. That meant fixing a dark rectangle round the flashlight on PowerPC, its beam ending in a hard line at wall corners, and the welder in the game going dark. On newer graphics cards the flashlight drew as a grid of grey squares, so one-pass drawing is switched off on Intel and Apple Silicon Macs, which are fast enough without it.
- Quake III. Mirrors and reflections now work on the G3 at 25 frames per second or more. It was fun to watch: the agent A/B testing code and settings changes one at a time, alternating runs so the Mac's own ups and downs couldn't fool it, and kept only what actually helped.
- Marathon. The game runs on Aleph One, a modern engine, and getting hardware graphics working on every Mac was a big job. At first every Mac was stuck with slow software drawing, because the build script switched OpenGL off, and the iMac G5 managed less than one frame per second because its graphics card claimed to support shaders it couldn't really run. The agent fixed how the shader renderer draws on older cards. It made Macs whose chips have no shaders, or are too slow with them, use the classic hardware renderer, and it made the game pick the right renderer for each Mac when it first starts. Now every supported Mac gets hardware OpenGL by default. The game also stopped crashing on Mac OS X 10.3, and all three Marathon games come in one app. Next, I'd like to try bringing features from Sprintathon, a community fork of Aleph One with parkour movement and bullet time, to the old Macs.
- Dungeon Keeper. OpenGL rendering now works on Apple Silicon. The agent also fixed a frame rate drop to 4 per second when a tooltip was scrolling.
- Quake II. Bloom (the soft glow round bright lights) was reworked after the first version was far too slow on older Macs. It's still too slow for the G3 and G4s, so it's only switched on for the Macs that can afford it. The game picks its graphics level from the Mac's model when it starts, or by checking the graphics card on a Mac it doesn't know.
- Quake. Players can download a missing map from the server, and a graphics path that crashed Macs with one kind of NVIDIA chip was found and avoided. The agent also added damage marks on walls for each weapon, such as bullet holes, axe slashes and scorch marks.
- Machines. A modern compiler (GCC 14) was built from source to make PowerPC code, so a new Mac can build games for 20-year-old ones.
- Servers. Five game servers run on Oracle's free tier. They send an alert if one falls over, keep a record of who played, and are rate-limited so they can't be used to flood someone else with traffic. I pen tested the setup using HexStrike.
# Tips if you want to try this
Some things I've learned that might help you:
- Your chat history is a gold mine. Every conversation you have with the agents, and every message they send each other, is a record of what's working and what isn't. Ask a strong thinking model to read it and look for waste: the same thing asked for again and again, work repeated, questions that didn't need asking. My first review found over 100 copies of messages sent word for word to several agents.
- Watch the team, then dive in. The telemetry screen gives me the whole team at a glance. Because everything runs in tmux, I can jump into any agent's window and talk to it directly when something needs a human.
- Use the manager for team-wide ideas. When I have an idea that affects everyone, I give it to the manager. It can start a research agent, talk the plan through with me, and then write the tickets for the rest of the team.
- One agent per repository, one job per agent. It keeps each agent's memory small and its focus clear. That's why SDL, the library most of the games depend on, got its own repository and its own agent instead of being patched inside each game.
- If a rule matters, make a script check it. "Keep the builds green" was in the rulebook from the start. The builds only stayed green once telemetry checked them every five minutes.
- Check claims against the source. "Done" should mean you can see it: the commit pushed, the build green on that commit, the numbers from a real run. More than once an agent reported work as finished that was still only on my laptop.
- Write down exactly which decisions are yours. Mine are money, anything permanent outside an agent's own project, and changing what a project is for. Everything else the agents decide and write down, so work doesn't stop while I'm away.
Why I think it works well here: this is brownfield work. The games already exist, there are already ways to measure them, and a lot of the hard thinking was done long ago by the people who made them. My quality gates are simple, too: a high frame rate, and a game that looks great and plays well to a human. A brand-new app or service would need many more gates to define "done", and depending on the data it handles, much more sandboxing than I use.