You know that moment when you have three coding agents going at once and you lose track of which terminal is which? One is stuck on a permission prompt you haven't seen. Another just started a dev server on the port the first one is testing against. A third made a nice change on the branch the second one is still editing. Twenty minutes later you're untangling git instead of reading the work. I ran into all of that, and it's what pushed me to build the setup I use now.
I call it the pit wall. It's a small directory that sits next to my two repositories, this website and vd-macan, the public data and analysis repo for the Macan project. Inside are a handful of shell scripts, a rulebook, and a few short guides. The agents are Claude Code and Codex. They run inside cmux, a macOS terminal with a sidebar of named workspaces. Here's how it fits together, and where I stay in the loop on purpose.
One command per task
Every piece of work starts the same way. I describe the task in a
sentence and hand it to launch:
launch --background "tighten the /log index page spacing"
That one line does the provisioning I'd otherwise do by hand, and it does it under a lock, so two launches can't collide.
The task text becomes a slug: the launch date, then up to forty
characters of the words. Slugs are never reused, even after the work
lands. The launcher creates a git worktree at <repo>/work/<slug> on
a fresh branch, agent/<slug>, cut from main. The work/ folder is
gitignored, so an agent never touches the working tree of main.
Then it reserves a port pair: the first free even-odd pair from 4400
up, checked against every other reservation and every live listener,
and never 3000 or 4356, the two defaults my own checkout uses. The dev
server gets the even port and the test suite gets the odd one. Website
worktrees also get their own node_modules, an APFS copy-on-write
clone of the main install, so an agent can add a package without
touching mine. All of it is written into a TASK.md at the worktree
root, and that file goes on git's local exclude list so nobody commits
it.
I don't have to say which repo. The launcher lowercases the task and
looks for signals. Words like /log, home page, content/log,
layout, page, or post point at the website. Words like
raw data, session data, tools/, imu_sd, or day1_analysis.py
point at vd-macan. If the task hits one family, that repo gets the
worktree. If it hits both, says "both repos" or "cross-repo" outright,
or hits nothing the launcher recognizes, each repo gets a worktree on
the same branch name, and each TASK.md names the other. Paired is
the safe fallback. It costs a little disk and never loses work. When I
already know where the task belongs, I say so up front,
launch website "..." or launch vd-macan "...", and inference is
skipped.
What the agent reads first
TASK.md is short. It holds the task, and it points at the files
every agent reads before touching anything: the rulebook, an
orchestration guide, a working guide for the target repo, and two
engineering playbooks, one on small surgical diffs and one on
test-first work. It lists the worktree path, the ports, and the main
checkouts as read-only. It ends with the finishing rules: commit to
your branch, no AI co-author trailer, don't merge, don't push, report
what you did. The prompt the launcher hands the agent is one sentence:
read TASK.md, read every rulebook and guide it names in full, then
do the task.
The rulebook is the part that matters. It's one file, RULES.md, and
it holds everything durable: which numbers on this site are
owner-verified, the writing voice, the ban on em dashes, the
tests-are-the-contract rule, how worktrees and ports work, and what
agents may never do. Both repos carry a thin CLAUDE.md that points
at it, so even a session I open by hand inside a repo inherits the
same rules. When I change my mind, I change the file, and the next
agent reads the new version. Nothing durable lives in one agent's
memory where I'd have to remember to repeat it.
Where the agents live
Inside cmux, launch --background creates a workspace named after the
slug, with the worktree as its working directory and the agent
starting in it. It's created unfocused, so it shows up in the sidebar
without pulling me away from what I'm doing. Six tasks means six named
workspaces down the sidebar, each an agent in its own worktree on its
own ports. I click into one when I want to watch or answer a question,
and click out. Outside cmux, the same flag uses Claude Code's own
background mode instead. And plain launch, with no flag, runs the
agent right there in my terminal.
Because every agent is isolated, parallel is the default. Independent
tasks go out at the same time. The rulebook tells agents the same
thing about their own subagents: split independent work, give each
worker its own files, and coordinate through main, never through
each other's trees.
Fable first, then Sol
By default every launch starts on Claude Code running Fable at xhigh
effort. If Claude can't start (the binary is missing, or it exits with
a status the launcher treats as a failed start), the launcher runs the
same brief through Codex running GPT-5.6 Sol, also at xhigh. Same
TASK.md, same rulebook, same worktree, so a change of model doesn't
change the rules.
The cmux case is more careful, because a usage-limit screen inside a live session isn't an exit code. For a bounded window after launch, about thirty seconds by default, a small watcher reads the agent's own terminal surface, only that one. If it sees a recognized usage-limit line, it records the reason and, when the message shows one, the reset time. It sends an interrupt to the exact Claude process it captured at startup, a few times at most, confirms that process has stopped, and starts Codex in the same workspace. It never types into the terminal, so a later foreground process can't receive a stray keystroke. Later launches skip Fable until the reset, or for a six-hour cooldown if the message couldn't be parsed. After that, the next launch tries Fable again, and when it comes up clean the router marks it available; a generation counter keeps a stale success from clearing a newer cooldown. The launcher owns the fallback. The agents don't build retry loops of their own.
Watching from the wall
status is the glance. It prints the router's state first, so I
always know which model the next launch will get, then one line per
worktree per repo: slug, branch, ports, dirty file count, where the
agent is running (a cmux workspace, a Claude Code background session
and its short id, or a process id), and its route (provider, model,
effort, router health). Then it lists anything listening on an
assigned port, so a dev server an agent left running for me is right
there.
Agents verify their own work before they call it done. For the
website that means npm test on the assigned test port, which builds
the production bundle, starts the built server, and runs the
rendered-HTML checks against real responses; npm run lint; and, for
anything browser-visible, the dev server on the assigned dev port left
running so I can look. The agent reports the exact localhost URL.
That's where the human part starts. I open the page at desktop and
phone widths, and I read the diff. cmux can show me the branch diff or
just the agent's last turn, and land <slug> with no flags prints the
commits and diffstat across both repos.
The tests are the contract, and they are strict for a reason. They fetch the built pages and check the copy itself: the demo-data label appears exactly once, the banned words and phrases don't appear, every route declares its canonical URL, the sitemap lists every published post. If an agent changes canonical copy, it changes the matching assertion in the same commit, and I see both in the diff. The guards are not permission to invent content that passes them.
Landing
Merging is mine. land <slug> --merge merges the agent branch into
main wherever it has commits and sweeps an untouched paired
worktree. Before it touches anything, it checks that the worktree has
no uncommitted changes, that the agent is finished (its cmux workspace
closed, its process gone, or its native session in a terminal state),
and that no commit carries an AI co-author trailer. A human co-author
is fine, and it never rewrites a message. The merge subject is the
task I typed at launch, tidied up. Then it removes the cloned
node_modules, the worktree, and the branch. It never pushes. If the
work is wrong, land <slug> --abandon removes everything and keeps
nothing. Pushing main is a separate act, done by me, and Vercel
deploys from there.
What stays with me
The pattern underneath all of this is simple. The agents get isolation and a rulebook. I keep the decisions. I write the task. I decide what has to pass. I look at the page and read the diff. I merge. When an agent thinks a rule should change, it says so in its final report, and I make the change in the rulebook myself, so nothing changes quietly. When something ships, I've checked every number on it, and the source and the checks are committed beside the result so someone else could reproduce it.
If you run more than one agent, you don't need my scripts. You need three things: one worktree, branch, and port pair per agent; a rulebook the agents read before they start; and merging kept as a step a person takes. That's the whole trick. Everything else here is those three, made hard to get wrong.