Waiting for the owner

Part of team up — the pause a seat at a dialog meets, and the owner's answer to it.

The lobby is a folder no CLI has seen before, and up reads a trust question and never answers one: a seat that stops there waits for its owner — at a terminal up keeps the seat's workspace, records it waiting and asks with one prompt (below); a run without a terminal closes the workspace without an answer and records <seat>: left out: trust (no terminal for owner). Nothing is run until the owner trusts the lobby once in that CLI. Then it starts.

A seat that stops at anything that isn't idle and that team may not answer — trust under either policy, permission, question, vendor notice, login, unknown, unsent, or the idle wait's timeout — meets the pause when its owner runs up at a terminal. up keeps the seat's workspace, records waiting-owner (with the classification and the process identity read at that moment, in the same write), shows the provisional record

claude-beacon: waiting for owner (trust)

and asks, exactly:

claude-beacon is waiting at trust: [o] open pane, [s] skip seat, [q] stop cleanly

o, s and q are the whole answer. One chunk is one key only when it is exactly one byte and that byte is o, s, q or Ctrl-C; anything else — several bytes that arrived together (a paste, a held key, an escape sequence) or any other byte — reprints the exact line and changes nothing, and a chunk's first byte is never taken for its key. Every byte already pending on the terminal is read and discarded directly before each prompt, and once more on the way out, so a key typed while the previous poll ran, or a line typed before up was started, never answers a prompt it was not meant for: only a key that arrives after the prompt is drawn counts. A Ctrl-C is q; an end of input is not — a closed stdin leaves the seat exactly as it is, its workspace and its waiting record kept, with <seat>: left out: its input ended; left as it is and exit 1, since closing on a vanished terminal is the destructive reading. What remains possible on this runtime: a byte that has not reached node's stream when the drain runs — one the terminal itself still buffers, or one arriving between the drain and the read — is read by the next prompt; the one-chunk rule keeps a multi-byte paste from ever acting as a key there, and a single trailing byte of a paste split across two reads can only be o, s, q or Ctrl-C, the four the owner could have pressed. The record stays provisional while the prompt is open — the seat's one final record still comes later. The seat lock is taken before each of resume, open, skip and close, and never held while the owner's key or a poll is waited for, so team answer can take it in between.

o takes the lock, verifies the waiting pane is still the seat's recorded one and still holds the recorded process, records manual: true, releases the lock, and focuses that seat's herdr pane with herdr agent focus — it sends no key and no text, and a captured run on a pane holding typed, unsent text left the text exactly as it was and only moved the focus. up then polls the pane at the profile's idle poll interval for at most the profile's idle timeout (90 s today), re-taking the lock and reading the state and the pane fresh at each poll. A screen that reads idle means the owner finished the seat in the pane: up removes the waiting record and carries the seat through its ordinary steps — renaming it if it needs renaming, delivering its rules, and recording <seat>: ready. A screen mid-work keeps the poll running to the deadline, with manual kept. A screen that reads anything else keeps manual and asks again at once, under the new classification. The deadline also keeps manual and asks again as timeout. o stays available after every re-prompt. manual is cleared only when this same up makes the seat ready, or closes it with s or q.

s takes the lock and closes the seat's workspace without input — the workspace the multiplexer returns for the pane just verified, never the workspace id the state stores when the two differ (then nothing is closed, and the record is <seat>: left out: its pane is in workspace <live>, not its recorded <stored>; nothing renamed, nothing closed, the state as it was). The workspace's panes are read from herdr directly before the close, with nothing between that read and it, and it is closed only when it holds the verified pane and no other agent pane: a workspace holding any other seat's pane is never closed — <seat>: left out: its workspace holds another seat's pane; nothing closed (close its pane there, then run team up) — and a pane whose process is no longer the recorded one is left as it is, <seat>: left out: left as it is: its process changed. It clears the seat's launch state — the waiting field with it — and records <seat>: left out: skipped by owner.

q, and a Ctrl-C, closes without input every workspace this invocation created that is not already ready, and clears those seats' state. Before each of those closes the agent list, the workspace's panes and the pane's process are read again, directly before it, with nothing between the last read and the close, and the workspace closed is the one herdr returns for that pane, holding no other agent pane; a workspace whose pane's process is not the one this run started is not closed, and its record is <seat>: left out: left as it is: its process changed. The herdr session is stopped only when this invocation created it and it now holds no ready seat and no watchdog: a session that existed before, a ready seat and the watchdog are kept. The current seat and every later configured, non-stopped seat with no final record get, in file order, <seat>: left out: stopped cleanly; earlier final records stay. The run exits 1.

A later up on a seat the state records waiting reuses its recorded pane and workspace — nothing is created for it — verifies the current screen with a fresh read, and enters the same pause, before the ordinary unnamed or wrong-name handling. A recorded pane that no longer exists is not a reason to launch again: nothing is created, the record kept, and the final record is <seat>: left out: its waiting pane is gone, with the team file's own repair under it on stderr — team down then team up (to restart the whole team) for a seat the file names as its orchestrator or operator, team remove <seat> --keep then team add <seat> (or team down then team up for the whole team) for any other. A pane that still exists but no longer holds the recorded process fails the same way, with its waiting pane holds another process. Neither team down nor team remove answers a prompt, and neither touches an agent it cannot name: a seat at a dialog is left running — skip <seat>: is blocked at a prompt, which \team` never answers; left runningon stdout fordown, team remove: is blocked at a prompt, which team never answerson stderr and exit 1 forremove— and a pane holding an agent the state doesn't record is left in place. The owner answers that dialog, or closes that pane, by hand first — then the sequence the record names runs, and the nextup` handles the seat.

A waiting record is a hint of where to look, never an authority — the state file is in the project and any seat can write it. Before a later up resumes, opens, skips or closes a recorded waiting seat it proves, from fresh reads, that the pane is still this seat's: no other seat's record of this session names that pane or that workspace (then it is refused, <seat>: left out: the state names one pane for two seats (<seat> and <other>); nothing renamed, nothing closed, the state as it was, and nothing is acted on); the multiplexer lists that pane with its agent unnamed or already carrying this seat's name; the workspace's live label is the one this seat's launch gives it (what up set when it created the workspace); and the pane's process is still the recorded one. A waiting record with no process identity proves nothing: it is never resumed, opened, skipped or closed from the record, and the run fails closed with <seat>: left out: its waiting record has no process identity, the team file's own repair under the record on stderr, beginning with the by-hand step first — answer or close its dialog in its pane for a pane the multiplexer lists under the seat's name, close that pane for any other — then run team down then team up (to restart the whole team) for a seat the file names as its orchestrator or operator, or team remove <seat> --keep then team add <seat> (or team down then team up for the whole team) for any other, to establish one by a run. A stored classification is validated on the state's own read against the closed list above: anything else the file holds reads unknown in the record, the log line, the prompt, team status and team doctor.

Under dialogs.trust: orchestrator, a seat waiting at trust is also re-read on a tick: every prompt timeout and every return from o takes the lock and reads the state and the pane fresh, and follows what they show — a seat team answer made ready in the meantime, the trust-sent-recovery state it may have left behind (shown as <seat>: waiting for owner (trust sent; recovery required), with the same keys), or the screen's own reading. up never invokes team answer and never sends a trust key.

An owner whose stdin is not a terminal never prompts: for each seat that meets a dialog it closes the workspace without input, prints <seat>: left out: <classification> (no terminal for owner) and exits 1. That close makes the stop pass's own reads — the agent list, the workspace's panes and the pane's process, directly before it, with nothing between the last read and it — and closes only the workspace the multiplexer returns for the verified pane, holding no other agent pane; a pane whose process is not the one the dialog was found with is left as it is (left as it is: its process changed), its record kept — and a close that did not happen writes the seat's state back so the next up reads it: the waiting record with the classification that stopped it, and the process identity the seat's own records already carried (its launched), never the changed read just made, which would bless the replacement. The next up then compares the pane against the launch's identity and resumes or refuses by the rules above; a seat whose records carried no identity gets the waiting record alone, and the next up refuses it for that missing identity rather than adopting its named, idle pane or refusing the whole session over an agent the state does not record. A seat stopped at the idle wait's timeout is not a dialog: that owner keeps the ordinary timeout record and the detail under it.

A delegated run takes the no-terminal path above whatever its stdin is, terminal or not: it never prompts, never focuses a pane, never sends a key or a text. Every dialog it meets is closed without input and recorded <seat>: left out: <classification> (no terminal for owner) exactly as above, and its idle wait's timeout is a dialog like any other stop for it — closed without input under the same proof as every close here, never left at launched. The owner's runs are unchanged: at a terminal the owner is asked, and without one the owner keeps the ordinary records above.