Marginalia, Side Effects & Loose Threads
Control Tower: one board for a fleet of coding agents, built mostly by the fleet
On a normal day I have six to eight coding-agent sessions open, Claude Code and Codex side by side, plus a few model windows and the BotSwarm consoles. Until September they lived in a pile of terminal windows across two screens. The failure mode wasn’t dramatic. A session would finish, or stop to ask a permission question, and sit there, waiting, because its window was behind three others.
So on 22 September I wrote down one success test for a new tool: on a normal day, across both screens, I never miss a session that is waiting on me. Two things were explicitly out of scope: it would not approve anything on its own, and it would not be a code editor. That tool is Control Tower.
What it is
Control Tower is a local board in the browser. Every agent terminal is a live tile. The sessions that need me move to centre stage; the rest sit around them, still visible, still live. A tab title and a chime say how many sessions are waiting, and when I come back from a break a “while you were away” view says what happened.
It doesn’t matter which agent is in a tile. Claude Code and Codex both report their state to the board, and when an agent can’t, the tile falls back to reading its terminal title and says so. The tiles also talk to each other through Conclave, the coordination board my sessions share: each tile shows its unread messages, and one gesture links two tiles for a handoff, a review, a pairing or a question. When a session runs low on context it can hand over to a fresh successor, which opens as a new tile with its lineage shown, while the parent retires.
And I can talk to it. A microphone button turns speech into text with my local Whisper setup (Conclave Voice). I can aim what I said at a tile, or drop it in a scratchpad first, edit it, and drag it onto the session it’s for.
Who built it
Control Tower is 28 days old and has 2,478 commits. 1,688 of those carry the handle of the agent session that wrote them, from 52 different handles. The busiest single session made 276 of them. On the busiest day the repository took 285 commits.
So the board was built largely by the sessions it supervises, with me deciding what gets built, reviewing, and walking through it. That has a pleasant consequence. Every problem an agent hits while working on the board is a problem the board should help with, and the entity reporting it is the one that just hit it. Since early October every session writes up the friction it ran into as a short lesson file. There are 303 of them now. Most of what follows comes from there.
What the agents taught the board
One dialog is one need. In my first long walkthrough the “needs you” count was wrong in a way that made it useless. A single permission dialog counted twice, once from the screen and once from the agent’s own report. Every finished turn asked for me, whether or not there was anything to decide. Agents’ mail to each other counted as mail for me. An attention signal that is wrong a third of the time trains you to ignore it, which is the failure the board was built to remove. Each of those got fixed or ruled on, one at a time.
A quiet process still exposes its command line. The helper that posts agent events to the board used to pass its credential as a command-line argument, and the browser opener got a reusable link. Anything that lists processes can read both. Now the browser opens with a link that works once and expires after five minutes, and terminals no longer inherit the board’s own credential from the shell that started it.
Agents run the full test suite because nothing stops them. The symptom in the lesson file was blunt: “full test suite kills hard drive”. Every session ran everything before every push, several at once. Now a bare npm test prints how to run the tests related to your change, and exits. The full suite runs at a named milestone, and Tower Kit refuses a broad run in the meantime. The post on hooks is about why the refusal works where the instruction didn’t.
A retry must not mean “launch another one”. If the answer to “start a successor” gets lost, the obvious retry starts a second successor. That happened. Now a retry identifies the request it is retrying, and an uncertain outcome means “find out”, never “try again”. A later review tried to break it: eight processes asking at once produced one successor.
Two boards on one profile is one board too many. A second Tower on the same profile now refuses to start, even on another port. If Tower crashes, the operating system releases the claim, so a crash never leaves the board locked.
Some breakage is the platform’s. Removing a git worktree with --force followed a Windows directory junction and deleted the main checkout’s dependencies. The first key typed into a terminal went missing about one time in three when the terminal resized at the same moment; that one traced back to the Git for Windows runtime, and an agent wrote up the upstream bug report.
And some of it is ours. Integration tests used to retry once on failure. That hid single-tick flakes, so retries are now off. A write to Conclave that times out is reported as unconfirmed, not failed, because “failed” invites a duplicate.
What it isn’t yet
It runs on one machine, for one person. There is a roadmap for boards on different machines working together, but none of it is shipped and I’m not claiming it. There’s no public demo, and the code is private for now. The success test from 22 September is still the one I hold it to: a session that needs me shouldn’t have to wait for me to notice.
For your own fleet
- Write down the one failure your tool exists to remove, and test against it, not against the feature list.
- Count your attention signal’s false positives. If it’s wrong a third of the time, you’ll stop looking, and then it’s worse than nothing.
- If agents build the tool, make every session write down the friction it hit. The lessons are the roadmap.
- Treat every retry as “the same operation, again”, never “another one”.
- Never put a credential on a command line, even for a process that only lives a second.