a chat, not a terminal
Talk to one agent.
It runs the fleet.
The agent-fleets pattern needs a coordinator, and until now the coordinator was a terminal: you opened one, typed instructions into a CLI agent, and that agent used kolu’s commands to drive the others. It works — kolu was built that way — but the thing you talk to all day was a PTY, with a PTY’s shape: scrollback you scroll, output you read, no idea which of its questions were waiting for you.
The Conductor is that coordinator as a chat. One of them, covering every machine you have. You talk to it; it creates, briefs, watches and answers the agents in your terminals, wherever those terminals are.
What it is
A single conversation with Claude, held by kolu-server — the process your browser is already talking to — and spoken over the Agent Client Protocol. Its working directory is the $HOME of the machine kolu-server runs on. Its hands are kolu’s own MCP face: the same tool table you can point any agent at, lifted over every host kolu knows about. So it opens terminals, speaks to the agents in them, reads their screens and kills them — on whichever machine the work belongs on — through exactly the tools documented on the MCP page: nothing special, nothing private. What its face does not carry is a way to block: the waits that could sit on an agent forever are not served to it (see it never waits).
A terminal’s name on that wire carries its host: local/3f29…, remote:nix@prod/9a1b…. A live hosts roster tells the agent which machines are there and which are reachable right now, and it changes while the conversation runs. Add a host mid-turn and its terminals start arriving; take one away and the terminals it was carrying report themselves gone, then the host leaves the roster. Nothing is reopened and nothing is re-taught — the conversation is opened against one tool endpoint and keeps exactly that one for its whole life.
It never waits
The first real waterfall ended in the obvious way: the Conductor called a wait on its planner, the wait had no end, and a sentence the human typed in the middle — we can skip review on this one — sat marked queued for as long as the planner worked. A coordinator that blocks is a coordinator you cannot reach.
So the Conductor does not block. It says what it is watching and ends its turn; kolu starts the next one for it.
What wakes it. Six things, and the wake says which. ready — a watched terminal’s agent has come up and has not been told anything yet; this is what covers the seconds (or the minutes, behind a nix develop) between creating a terminal and the agent in it being there to talk to, so the Conductor creates, ends its turn, and says the brief when the terminal rings ready. stopped — an agent it has already spoken to finished its turn. asking — the agent stopped on a question of its own. gone — the terminal is not there any more, closed or with its host. asleep — a watched terminal was put to sleep, so the agent in it is dead; it rings once and stays on the list, and waking the terminal returns it to the ordinary levels. ended — a terminal the Conductor created with a run command ran that command and came back to its shell prompt with no agent ever appearing. A plain shell nobody watched never wakes it, and there is no timer: nothing wakes the conversation because time passed.
What ended cannot see. kolu reads what a terminal is running and whether an agent is in it; padi exposes no exit status. So a command that fails before anything of its own starts — omp: command not found, answered by the shell itself — never shows a foreground to observe and does not ring. The limit is real and stated rather than papered over with a timer: a created terminal that has said nothing is one to read the screen of.
A wake that cannot be delivered says so. If kolu cannot wake the conversation — the agent’s bring-up is refused, a sign-in is missing — the news is put back, not dropped, and one notice row appears in the transcript naming the terminals still waiting and why (one per failure episode, not per retry). The retry is event-driven and has no timer: the next time the conversation falls idle (which includes a respawned agent finishing its bring-up, or a sign-in landing), the next watcher observation, or the end of the next turn you prompt.
One line per wake. A wake is a machine’s message in the transcript — full width on the left, never your bubble and never a “send again” — one line saying who it is about and when, which opens for the rest: each terminal as host/id, what it is called, which host it is on, and how it stands. Several terminals can ride one message.
News is held while it is busy. Events that happen during a turn are never injected into it; they are kept, one slot per terminal, newest state only, in the place the first one took — and they go out together, as one wake, the moment the turn ends, however it ended, cancelling included. A terminal that goes back to work withdraws its own held slot. What you type is never held behind them: your message goes out as it always did.
A restart loses nothing. The watched list is saved with the conversation; held events are not. When kolu-server comes back it looks at each watched terminal again, and a state the Conductor had already been told about does not ring a second time.
What it is watching
A terminal the Conductor creates is watched from the moment the call answers — kolu sees the create and adds it, the agent does not have to ask for it. It can add a terminal it did not start (watch_terminal, which it uses when you point it at one) and drop one whose work is done (unwatch_terminal). A terminal the Conductor closes itself (lifecycle_kill, say after a merge) leaves the list in silence — it is never woken about its own close. One that closes any other way drops off too, and that one rings gone.
And the Conductor can read it too. The list is a resource of its own — watched, beside terminals and hosts — naming every terminal on it by host/id, the host it is on and what that host is called, the terminal’s name and the agent kolu last saw in it, with no host beside a row whose machine has left the fleet. The watch and unwatch tools answer with the whole list as well, so the turn that changes it is told it. The script tells the Conductor to read that list before acting on what it thinks it is watching, and especially after a restart: a conversation’s memory of the list is a guess, the resource is the fleet’s answer, and the difference used to show as the Conductor hedging (“I’m not certain those two are still on my watch list”) and then trying to watch terminals that no longer exist.
Joining baselines. A terminal added with watch_terminal does not ring for the state it was found in — that state is the tool’s answer instead (the agent in it and how it stands, or that there is no agent, or that it is asleep), so the Conductor is told once, in the turn that asked. The same baselining happens when it speaks: saying something to a watched terminal withdraws that terminal’s held event and re-bases the watch at the state it stands in, so the Conductor is never woken about the agent it has just briefed. Going back to work resets it, so the next stop rings as it always did.
You can see that list, and on a wide window you are looking at it already. The watched terminals are one panel in two placements: on a window wide enough to hold the reading column and the panel side by side it is a side column beside the conversation, always on screen; below that width — and on a phone — it is what the status line’s N pieces of work · M terminals opens above the line. The same component, the same state: a Look you open in one is open in the other, and resizing across the width where the columns fold carries it with you.
Either way the terminals are grouped by the piece of work they belong to: one group per top-level terminal, headed by that terminal’s name and by how long that piece of work has been going — counted from when its first terminal joined the watch list, saved with the conversation, so a restart does not restart the clock — with its terminals under it, so a waterfall reads as one thing with its planner and its implementor inside it rather than as three unrelated rows. Inside a group the rows are drawn as the tree they are: a split sits under the terminal it is a split of, stepped in behind a quiet └, the group’s own top-level terminal first and deeper splits nested the same way. The nesting comes from each terminal’s live child-of edge rather than from the grouping, which is why a row whose parent is not watched here — or whose host is away, where kolu keeps the group but this browser has no record to read — sits at the top of its group instead of being nested on a guess. No row is named by a bare id. A terminal the @ menu names keeps that name; one it does not — which is every split, since that menu names tiles — is named by its intent, else by the title its agent gives itself, else by its agent and who it is a split of (Claude Code · split of paths-kept-by), else by its branch or its directory, with eight characters of its id only when there is nothing to read at all. A heading climbs the same ladder, with the name kolu last read for that piece of work ahead of the id, and it is drawn only when it says something the rows do not: a group of one terminal called the same thing is that name twice, so it is left off; its age is drawn either way. Each row is two lines — who it is (its name, its host, the agent in it), then how it stands, for how long, and Look to read the last lines of its screen right there, the same Look the transcript’s cards carry. Pressing the name takes you to the terminal, exactly as a create card’s open tile does — the host’s tab, then that tile — and it is not offered where there is nowhere to go, a terminal that is gone or one whose host is not connected. An agent that keeps a task list of its own says how far through it is beside its state — 7/12, the same fraction the tile’s title bar and the inspector print — on these rows and on the transcript’s agent lines alike, and an agent that keeps none says nothing about one. The duration beside the state is how long it has stood that way, for every state — working 4m, asking 40s, asleep 2m, stopped 10s. No host records when a state began, so the instant is kolu’s own watch’s: it is the thing that reads every watched terminal on every machine, so it is what sees each change, and the word beside the number is what kolu calls the level it read — the same words the kolu · wake line says, so a prompt with nothing in front of it reads no agent rather than a protocol token. It is as accurate as that first sight — after a kolu-server restart the clocks begin again at the first reading, and a terminal nothing has read yet shows no duration at all rather than a zero. A terminal with no agent detected in it yet but one of kolu’s agents in front says it is starting that agent, rather than claiming there is no agent while naming the program it is running.
The panel is a reading, not a control: nothing in it takes a terminal off the list. The ways off are telling the Conductor to stop watching one — it has unwatch_terminal for exactly that — or closing the terminal, which drops it anyway.
Nothing reads zero. A conversation watching nothing has no count on its status line and no control that would open an empty panel; the side column, which stays on screen so the conversation does not jump sideways when the first terminal appears, says Nothing in progress. in one quiet line.
The status line
Directly above the composer, because that is where you look when you are about to type, one line says everything about the conversation’s state:
- whether the Conductor is working (visibly moving, what it is doing, and for how long), asking you, or idle;
- N pieces of work · M terminals, when there are any — absent at zero, control and all. A piece of work is the watched terminals that share a top-level terminal (each one’s
child-ofplacement walked to its root), so a whole waterfall counts once and a terminal you pointed it at counts once; kolu derives it from the watch list’s own readings rather than from anything the agent declares, and a host that is away keeps its last-known grouping. Below the two-column width it is pressable and opens the panel above the line; on a wide window the panel is already beside you, so the same words are a readout; - how many events are waiting, when any are;
- ↯ Alt+⏎ steers, while a turn is running that it can be steered into. The hint had a row of its own under the composer; a chord that is true only during a running turn belongs on the line that reports the running turn.
That line is the only place the state is said. The header’s small state chip is gone: one fact, one place. Stop stays beside Send.
Talking while it works
⏎ sends as it always did — a plain prompt, which the agent picks up when its turn is over, marked queued until it does. Alt+⏎ steers: the words go into the turn that is already running, and the Conductor hears them now.
Steering is offered only where the agent’s own leg advertises it, and if the agent does not take the message it is sent as a plain prompt — the words are never lost. A message carrying attachments is always a plain prompt: the steer carries words only, and silently dropping the files you attached would be worse than arriving a turn later. One limit comes with it, brought over from olai rather than rediscovered: once a message has been queued in a session, that session queues everything afterwards, so Alt+⏎ is a plain send until a new session starts.
The other direction is a tool. The Conductor says something to an agent in a terminal with one call — the terminal and the words — and kolu does whatever that agent needs to receive them, idle or mid-turn. The per-agent knowledge (which keys, how many, in what order) lives with agent detection, so the Conductor never has to learn that one CLI needs a second Enter and another does not. An agent kolu cannot speak to is refused with the reason, never guessed at. And the call says whether it took: for an agent that was idle, it watches a few seconds for that agent to start working and reports which happened — began working, or did not, in which case the keys were sent and the screen is the thing to read rather than the same words sent again. For one that was already working it reports how the words were delivered, steered or queued, and that taking is not observable from outside. The tool exists because of an Enter that was silently dropped; a say that does not land must not be a silent hang.
The first tab in the chrome bar
The Conductor is a place you go, so the chrome bar draws it the way it draws the other places — as a tab. It is the first tab in the row, ahead of the host tabs, with the same shape: the rounded top on the same rail, the same hover, and the same lit belly when it is where you are. What it does not carry is everything that makes a host tab a machine — no connection dot, no host hue (the accent stands in for one), no + — and in their place the ◆ mark and the alpha tag. It is not a host and kolu does not model it as one; it is the one conversation, and the thing it is about is all of those machines at once.
Exactly one tab looks current. Selecting the Conductor lights its tab and no host tab is lit while you are in it — you are not on a machine. Which host you came from is still remembered: selecting any host tab leaves the conversation for that machine, and ⌘⇧O (Super+Shift+O off a Mac) jumps to the Conductor and, pressed again, back to the host tab you were on. Pressing the Conductor’s tab while you are already in it does nothing, like any other tab. Which of the two you had open persists the way the active host does, per device.
Leaving it does not close it. The conversation is built once, the first time you go there, and then kept: switching to a host tab hides it rather than throwing it away, so coming back finds exactly what you left — the watched list open if you opened it, each Look still open and still showing that terminal’s screen, the folds you opened, where you had scrolled to, and a half-typed message still in the box. What it does not do while you are elsewhere is work: a hidden conversation reads no screens, counts no clocks and takes no keystrokes, and an open ⋯ menu closes rather than following you onto the host tab. The same on a phone.
The controls that describe a host’s canvas — ▦ canvas · ▢ maximized, the dock toggle and the inspector toggle — are not drawn while you are in the Conductor, because there is no canvas on screen for them to describe. The recorder, settings and the command palette stay. Their keyboard shortcuts are unchanged.
Its badge is an unread count: how many pieces of work the conversation has in flight — the same number the status line says, so a waterfall running on three terminals is one. Absent at zero, and shown whether or not you are in it. One badge, and its colour says “look now”: while the Conductor is asking you something the same badge turns the needs-you violet, and if it is asking with nothing in flight it still appears — a violet dot with no number — so the question is never invisible. There is no second count on the tab. Those questions are a needs-you source like any other, so they chime, raise a system notification and count toward the app badge (which counts the questions themselves, beside every blocked terminal) through the same machinery. A turn merely ending is silent; being asked something is not.
The view is the chat — a header, then the conversation in one reading column: the transcript, the status line and the composer at the same measure (about 45rem, which is 70 to 90 characters a line at the chat’s text size), with your own messages at that column’s right edge rather than a screen away from the replies. On a window wide enough for it, the watched panel stands beside that column as a second track that is always drawn, so the conversation never slides sideways when the first terminal appears. No dock, no canvas, no second pane holding a terminal: the terminals are on the host tabs, one keystroke away, and a half-width copy of one here would be a second place to look at the same thing. What the chat does carry is a way to check on an agent without leaving it: the card for each terminal the Conductor opened says how that agent is doing, and Look shows the last lines of its screen right there (see the transcript). The way to the terminal itself is open tile on that same card, which takes you to the terminal’s host tab with the tile focused.
How to use it is the third column. On a window wide enough for all three — the help, the reading column and the watched panel — a short help column stands at the head of the frame: how to ask, how a waterfall is set up, what to do while it works, the chord that steers a running turn, that it wakes itself, and what it looks like when it asks you. Collapse it and it is a thin rail with one way back in, still collapsed on your next visit — and because its column keeps its width either way, collapsing it gives the space back without moving the conversation. Below that width, and on a phone, the same words are what How to use in the ⋯ menu opens.
On a phone the Conductor is the first entry in the pull-down chrome sheet, above the host chips. Tapping it gives the conversation the whole screen under the pull handle, which reads ◆ Conductor while you are in it — the one place the name is said, so the conversation’s header below it carries only the alpha tag, the model and the ⋯ menu. The sheet opens from there exactly as it does from a host — the Conductor lit, no host lit — and tapping a host chip takes you back to that machine. The composer is one line that grows as you type, with Send beside it and none of the desktop’s keyboard hints; a tool card for one of kolu’s own calls drops the raw tool name and wraps its title rather than cutting it off; and the inspector toggle is not offered while you are in the Conductor.
The conversation
The header
The agent’s name, and then the facts you steer by — but not the state, which lives in the status line above the composer and nowhere else:
- who it is signed in as, when the agent says — “Claude Max”, an API key, a gateway. Silence here is not the same as logged out: an agent that does not report its identity shows nothing, and one that reports it is logged out says so;
- the model, pressable for the session settings;
- a context meter,
148k/1M. A fraction rather than a percentage, because the window is not a constant — 200k and 1M are both ordinary, and “2%” would read identically in both and mean quite different amounts of work left; - a ⋯ menu, which performs nothing by itself.
Session settings
Pressing the model opens the adapter’s own settings: the models it offers (with their window sizes), reasoning effort, permission mode, and whatever else it advertises. The controls use the adapter’s names and values — kolu composes none of them. A change the agent refuses leaves the confirmed value where it was, and says why.
The model you choose survives a restart. That is a fix rather than a given: the agent re-asserts its own default over a resumed conversation, so kolu records the switch and re-applies it when the conversation comes back.
The ⋯ menu
Everything kolu can do to the conversation itself, as opposed to the options the agent advertises:
- New session — a fresh conversation, and an empty one. The messages go, the files attached to them are deleted from the machine running kolu, and the terminals the conversation remembered are forgotten; what is left is a blank chat, and the next thing you say teaches the agent its standing prompt again. There is no undo, so it asks once before it does any of it.
- Stop, while a turn is running.
- Sign in…, while the agent is not signed in.
- Copy transcript, always — the conversation as plain text, on the clipboard.
- the tool servers this conversation has, read rather than pressed:
kolu ✓when the agent has reported the server attached, the agent’s own sentence beside the name when it says it is not, and a note that the agent brought its own servers besides. There is no “kolu is missing” row: the fleet endpoint is proved to be answering before the conversation is opened, so an endpoint that is not gets a refusal naming it rather than a chat window that can see nothing. It used to be a strip above every conversation, saying the same thing all day; it is a question you ask now and then, so it is answered behind the one press that promises there is more here.
A conversation kolu could not resume after a restart is the one case where a — new session — rule is drawn across the transcript: the scroll above it is the conversation you were having, and the line says where the agent’s memory stops. A new session you ask for yourself leaves no such line, because there is nothing above it to divide from.
The strip
Above the scroll, and only while something is out — a conversation with nothing running has no strip at all, and starts at the top of the column:
- background tasks still running — a
run_in_backgroundshell, a monitor, an async subagent — with a live clock. These outlive the turn that started them, which is the whole point of them; - subagents out, pressable to open their shelf.
The transcript
What you typed sits quoted. What the agent answers is rendered as markdown. Between them:
- a plan card when the agent sends one, replaced whole on each update;
- tool cards. Anything kolu cannot name gets a generic card. Anything that is one of kolu’s own tools gets a card that knows what it means, and each one that is about a terminal names the host it acted on, since an id alone no longer says which machine:
lifecycle_createshows the terminal’s label (or its worktree branch), the directory and the command it started, and — once the call has answered with the terminal it made, not while it is still running — that terminal’s host and an open tile that takes you to that host’s tab with the tile focused;lifecycle_sendInputshows the target and what was sent, andagent_saythe terminal it spoke to and the words;wait_outputSettled— the one short wait left — shows how the settle ended and the last lines of the screen folded underneath;screen_textshows the snapshot. A call that failed says why in one red line under its title — the tool’s own sentence, without the code-fence marks or thesurface-mcp:tag kolu’s tool server puts in front of every error — and that line is the only place the error appears: a failed wait or read offers no screen, because there is none, and the folded call under a failed generic card shows what was asked, not the answer. A call that is still running carries a clock; - the agent line on a
lifecycle_createcard that names its terminal: one status line — the agent’s state and what it says it is doing, read from the terminal list kolu already keeps for every host, so it costs nothing — and a Look button. Look opens the last 40 lines of that terminal’s screen as read-only text, read from the terminal’s own host and refreshed about every two seconds — only while it is open, on screen, the browser tab is in front, and the Conductor is the view you are on; closed, or left behind on a host tab, nothing is fetched. A read that fails says so rather than showing an empty box, Look stays open across updates to the card and across leaving the conversation and coming back, a terminal that has ended says terminal gone, and a sleeping one says asleep — neither offers Look, since there is no live screen to read; - file diffs, trimmed, one box per hunk, click to expand;
- asks — a permission request the agent was not allowed to answer for itself, a question it needs you to settle, a page it needs you to visit — as a form in the flow. Nothing times out. Dismissing one is an answer too: the agent is told you would not say, never handed a choice you did not make. An answered question stays readable: the card keeps every question with what you said to it on one line (
Mode — trivial,Implementor — omp · anthropic/claude-opus-5-5, the typed “Other” beside the pick it belongs to), and says how it ended — answered, declined, or withdrawn by the agent. A form that carries its own labelled questions leads with them: the sentence some agents send with a form (Please answer the following questions.) is the adapter talking about the form, not a question, so it is not printed. A question with nothing to fill in — a permission request, a link — still shows its own words. Copy transcript carries the questions and the answers too.
The tool calls the Conductor makes between two things said — its own words, a message of yours — fold into one closed Details that says how many calls it holds, so the conversation reads as the sentences it is — open it to see the cards. The fold builds as the calls settle, so a turn that works for minutes before it says anything shows one Details with a growing count and the call in flight, never a wall of finished cards; a fold you open while it works stays open as more calls join it and when the Conductor speaks. Nothing is ever folded across a message of yours. Anything you would miss by having to open it stays out in the flow: a call still running (with its clock — it joins the fold once it settles), a call that failed or was left without an answer, a background task still out, the agent line above, and every question, plan, notice and message of yours. A failure stays news, but the same failure in a row is said once: six screen_text calls on six sleeping terminals draw as one red line — screen_text failed 6 times: Terminal is asleep — wake it before reading its screen, … — that opens to the six cards. Copy transcript still copies every call, and pressing a task in the strip opens the fold its row is in.
Long lists fold. A list in a reply longer than five items shows its first three and a line reading “and N more”; pressing it opens the rest in place, and that list stays open for the rest of the session (copying the transcript always copies every item).
The composer
It never locks, and it is one line at rest — it grows with what you type, in every layout, with Send (and Stop, while a turn runs) beside it. The row of keyboard hints that used to stand under it is gone: the two worth saying are in the placeholder, and the steer chord’s hint is on the status line directly above, where the running turn it belongs to is reported.
@ completes terminals from every host into a chip — the host is shown as you pick, and so is what tells same-named terminals apart (one repo, several branches): the intent you gave it, else the agent in it and its state, else its directory, cut from the left so the part that differs stays in view. The message that goes out carries a structured reference to each one, so the agent gets the terminal’s host/id and intent rather than a name it has to guess at. / completes the agent’s own commands. ⇧⏎ is a newline, ⏎ sends, Alt+⏎ steers into the running turn (above), Stop cancels the turn in flight.
Sending while it works is fine: a plain send goes out immediately and waits at the agent, marked queued until it is picked up. Nothing is held on kolu’s side, which is also why Stop has nothing to throw away — anything queued behind the turn you stopped survives and runs next. A steer is the other choice: it lands in the running turn instead of behind it.
A tile action, Ask the Conductor about this, opens the conversation with that terminal already mentioned.
Attachments
Paste an image into the composer, or drop a file onto the pane, and it goes up with your message — a chip appears above the box first, so you can see what is attached and take it off again. A screenshot is the obvious case: the agent reads the picture rather than your description of it.
What travels depends on what the agent said it could take. An image goes as an image. A text-like file goes as its text, embedded. Anything else goes as a link to the path kolu wrote it to, which the agent reads itself — so a 40 MB binary is a filename on the wire, not forty megabytes. An agent that never advertised the kind you attached gets told no, out loud, rather than quietly handed nothing.
There are two limits — 10 MiB per file, 20 MiB per message — and crossing either is a sentence in the composer before anything is sent.
A file goes up as it is attached, in pieces, and the message that follows carries only its name and where it landed — the bytes never ride the message itself, because one oversized message would take the whole tab’s connection down with it. Nothing is kept in the conversation’s record either: kolu writes the file into kolu-server’s own state directory and remembers where, so the thumbnail stays on screen in the tab that sent it and a conversation reopened tomorrow shows the chip with the file’s name and size. A transcript is a thing you scroll through for months, and a base64 screenshot in it is a conversation you cannot load.
Signing in
An agent with no credential cannot open a conversation or run a turn, and the protocol has one way of saying so. kolu reads it as its own refusal rather than a generic no, because it is the one refusal with something to do about it: the panel puts the agent’s own ways of signing in on screen.
Claude hands over a command for kolu to run. kolu spawns it in the session’s directory, streams its output with the URLs clickable, and gives you one line to type the code into; exit 0 is the whole of success. A message that was refused for want of a signature comes back to the composer and is sent again once you are in.
There is deliberately no API-key box. Where a credential typed into a form would live, how it would be stored and whether it would ever be shown again is a feature with its own argument to make; until it is made, the button is not drawn.
What it knows how to do
Every new conversation starts with one standing instruction — visible in the transcript, because the protocol has no system prompt and kolu would rather you could read what your agent was told than not. It says, in short: you are kolu’s conductor, and one conversation covers every host; a terminal is named host/id; the hosts resource is the live roster of machines and it changes under you; create a terminal on the host the work is for, and when the human has not said which, ask — the connected hosts as the options — rather than default to your own machine, because a path does not pick a host; you never wait — say what you are watching and end your turn, and kolu wakes you when a watched terminal’s agent comes up, stops, asks, is gone, is put to sleep, or ended without ever showing an agent, with a sentence on what each of those means; a terminal you create is watched for you, and you add or drop the others; speak to an agent with one call, let kolu do the keystrokes it needs and read the answer that says whether it took, keeping raw typing for a plain shell; the agents’ questions come up to you, and you bring the human only the decisions that are theirs; briefs go through files, written by the agents rather than by you, because none of your tools writes a file on another host; and a sleeping terminal has no screen to read, so check a terminal’s record before reading its screen, skip the sleeping ones and say how many are asleep instead of trying each — and if it reads one anyway, kolu answers that the terminal is asleep, never that it is missing, so it reports it as asleep rather than gone.
Choices are buttons. Whenever it puts a choice to you — inside a waterfall or not — the choice arrives as a question form, one option per choice and a box for an answer of your own; its prose explains the choice and the form asks it. It does not end a reply with “Your options: 1, 2, 3” and wait for you to type a number.
It also knows the waterfall — what it does when you ask for one, or ask it to implement something end to end. A waterfall runs in one of two modes, chosen for that waterfall rather than once for the conversation: say which you want in the request, or it asks, two options. Full is the list below. Trivial is one implementor — no planner, no plan, no approval, no review rounds, and no brief file: the Conductor has no tool that writes a file on the host the work is on, so it says the request and the standing rules to the implementor instead. Trivial keeps everything that makes the work land: the refactor pass, the PR, CI once, and the merge rules.
- Setup, every waterfall. Not once a conversation: every waterfall asks, because the second piece of work is rarely the first one’s host, harness, model and merge policy by default. It asks which host the work runs on — the hosts connected right now, last time’s first — unless your request names one; a path does not, because
~/code/oducan exist on several machines, and nothing is created until the host is known. It asks which harness runs the implementor, which model that harness runs on — a separate question, not a command line for you to type — what to do when CI goes green (the Conductor merges the PR itself, under the merge rules below, or hands it to you), and the mode, unless your request already said which. A full waterfall asks the same harness and model for the planner. The Conductor builds the command line out of the harness and the model you chose; you pick the two, it writes the flags. It all arrives as one Conductor asks you form when the agent behind the Conductor has a question tool — every question with options and a box beside them for an answer of your own, and last time’s answer offered first, so a waterfall that keeps everything is one press per question. An agent without a question tool asks in prose. Outside a waterfall the same holds whenever it is about to open a terminal you have not placed: it asks where, and never assumes the machine kolu-server runs on. - Plan (full only). A planner opens on the host you chose, in a fresh worktree. It plans and reviews and never implements; it writes the plan to
.waterfall/plan.mdin that worktree (kept out of git through.git/info/exclude) and stops. - Approve (full only). The Conductor shows you a short summary and where the full plan is, and asks you — in the same kind of form — to approve it or say what to change, typed in the box beside the two choices; changes go back to the planner until you approve.
- Implement. In a full waterfall the brief lands in
.waterfall/brief.md, written by the planner, and the implementor opens as a split of the planner in the same worktree; in a trivial one there is no brief file and the implementor opens in a fresh worktree of its own. Either way the terminal is created with the implementor’s command line and the Conductor ends its turn: the agent is not up when the create answers. When that terminal rings ready, the Conductor says the brief — its path in a full waterfall, the request and the standing rules themselves in a trivial one — and ends its turn again. It stops when its PR is open. A doubt it stops on is answered from the plan or the request, or by the planner; only a decision that is really yours comes to you. - Review (full only). The planner reviews the PR by the repo’s own guidelines and posts the review; the implementor addresses it; repeat until the planner is satisfied. Something you ask for mid-round waits for the next round.
- Finish. The implementor makes one final refactor pass (the Hickey/Löwy pass), then runs CI once, the way the repo says to run it, fixing it until green — then the PR is merged or handed over, as you chose. CI is one at a time: with several pieces of work going, only one is in CI at any moment, and the next run starts only after the first has finished. A red check is retried once, only when nothing else is running; red twice is a real failure whose cause the implementor finds.
- Merge, if you chose that. The Conductor merges the PR itself, and only when every required check is green on the PR’s current head commit (read from the PR by the Conductor, not taken from the implementor’s word), the head has not moved since the evidence it has — the planner’s last review in a full waterfall, its own reading of those green checks in a trivial one — and the branch contains the current base branch. A refused merge is a cause to find and report to you.
- Close up. Once the PR is merged or closed, the Conductor closes the terminals it opened for it — the planner and the implementor, or the one implementor — and nothing else.
It never implements anything itself, starts no new agent and opens no new PR without your approval, never closes an implementor before its PR is merged or closed, and never goes around the merge gate — no admin override, no GitHub auto-merge. It tells you each step in one plain sentence, not in tool names.
It is re-taught on every new session, and it is versioned with the binary — so the law never drifts from the tools it describes.
When it comes back
kolu-server restarts routinely — an upgrade does it — so a restart is the normal path rather than a crash. The conversation comes back: the session is resumed by its own id, the transcript is what it was, the model you put it on is re-applied, and the watched list comes back with it — kolu looks at each of those terminals again, so an agent that finished while kolu-server was down still wakes the Conductor, and one it had already been told about does not ring twice. Held events do not survive; they were never more than news waiting for a turn to end. What else does not come back is anything that was a fact about a subprocess that no longer exists — a turn in flight, a sign-in half done — because a restored one would be a lie with a timestamp on it. A host that was away while kolu-server was down needs nothing either: it rejoins the hosts roster when it reconnects, and its terminals flow back into the conversation that is already open.