# seat `mosaic launch ` starts a seat through its existing launch script, unchanged, and leaves one registration record that the control board reads instead of guessing. `mosaic seat task ` changes the task on that record. Nothing else. No provider or auth registry, no roster schema change, no stopping or killing of seats, one record per seat. Issue #1504. Plain ESM, no dependencies, Node 24 or newer. ## Commands The repository wrapper is `scripts/mosaic`. It is not on PATH and is not the npm-global `mosaic` CLI from the estate tooling, which has no `launch` or `seat` command; run it by path. ``` scripts/mosaic launch [--task TEXT] [--project NAME] [--workspace PATH] [--harness NAME] [--repo PATH] [--config PATH] [-- args...] scripts/mosaic seat task [--layout repo|fleet|unknown] [--config PATH] ``` - `` is a name under `/agents/` (`--repo` defaults to the current directory) or a path to a seat directory such as `~/.mosaic/fleet/agents/orch-01`. Either way the directory must hold an executable `launch.sh`. - Everything after `--` goes to the launch script untouched. - `launch` writes the registration, then replaces itself with the launch script (`process.execve`). The seat keeps the same pid, terminal and process group as if you had run `launch.sh` yourself, the launch script's exit code is yours, and the tmux pane's foreground command stays the harness, which the control board's liveness check depends on. Node marks `process.execve` experimental (present since 24); it is the only way to keep the seat's pid, so the package accepts that and pins Node 24 or newer. The seat tests exercise it directly (exit passthrough). - The launch script receives `MOSAIC_LAUNCH_REGISTERED=`. A launch script that sees this variable is already registered and must not call `mosaic launch` again; `mosaic launch` refuses to run when it is set. The four repository seats (`agents/darkwing`, `agents/dewey`, `agents/filbert`, `agents/rocko`) register themselves: their `launch.sh` re-enters through `scripts/mosaic launch` unless already registered or called with `--check`. So `agents/darkwing/launch.sh` and `scripts/mosaic launch darkwing --task "..."` are the same path; the second form is how you attach a task. A `--check` run never writes a record. ## The record `/seats///registration.json`, directory 0700, file 0600, written atomically. `layout` is `repo`, `fleet` or `unknown` (see below); it is part of the path because a seat name alone is not unique, and `seat task` refuses a bare name that is registered in more than one layout until `--layout` says which. `dataRoot` comes from `~/.config/mosaic-dev/config.json` (or `$MOSAIC_CONFIG`); a missing or unreadable config refuses the launch. The record is rewritten on every launch. It is written before the launch script runs, so a launch the script itself refuses (a failed `--check`-style precondition, a missing context file) still leaves a record with a pid that is no longer running; the next launch replaces it. It is a launch record, not a run record: it is not evidence, it holds one seat's latest launch only, and it lives outside `/board/` because the board never writes here and a scan never changes it. ```json { "version": 1, "seat": "darkwing", "project": "mosaic-stack", "task": "Control board: seat registration (#1504)", "workspace": "/mnt/storage/src/mosaic-stack", "tmux": { "socket": null, "session": "darkwing" }, "harness": "pi", "startedAt": "2026-09-12T16:20:11.000Z", "pid": 431734, "sessionsDir": "/mnt/storage/src/mosaic-stack/.pi/state/darkwing/sessions", "seatDir": "/mnt/storage/src/mosaic-stack/agents/darkwing", "launchScript": "/mnt/storage/src/mosaic-stack/agents/darkwing/launch.sh", "layout": "repo", "updatedAt": null } ``` Fields asked for in the brief: `seat`, `project`, `task` (empty unless `--task` or a later `seat task`), `workspace`, `tmux` (session name and socket, from the `TMUX` variable of the pane the launch ran in; null outside tmux), `harness` (only what `--harness` says; the repository launch scripts pass `pi` or `claude-code`), `startedAt`, `pid`. Fields added, and why: - `sessionsDir`: how the board matches a record to a row. Seat names are not unique across layouts (there is a `darkwing` in this repository and a `darkwing` in the fleet), so the record names the sessions directory the board already scans, and only an exact match counts. - `seatDir`, `launchScript`, `layout`: what was launched and how the paths were derived, so a wrong record can be traced without re-running anything. `layout` is `repo` (`/agents/` with `/.git`), `fleet` (`/.pi` exists) or `unknown` (nothing derived, `sessionsDir` null, the record is still written but the board cannot match it). - `updatedAt`: set only by `seat task`, so a task change is distinguishable from a relaunch. - `version`: so a later shape change can be refused rather than misread. `project` and `workspace` are derived only for the repo layout (the repository's basename and root). For the fleet layout they are null unless `--project` and `--workspace` are given, because the roster has no project field and all fleet seats run in `~/.mosaic`; the board then keeps its own derived values for those rows. ## How the board uses it `packages/control-board` reads every `/seats///registration.json` on each scan. A registered task, project or workspace wins over the derived value, and the row's `taskSource`, `activeProjectSource` or `workspaceSource` says `registration`. An empty task or a null project or workspace in the record leaves the derived value in place. Rows without a registration are unchanged. A malformed record is reported in `registrationErrors` on the index and skipped; it never takes the board down and it is never treated as absent silently. ## Exit codes `launch` exits with the launch script's own code once the script runs. Before that: 1 the launch script could not be started (the record is removed again), 2 invalid data or configuration (missing config, no such seat, no executable `launch.sh`, bad record on disk), 4 usage. `seat task`: 0 ok, 1 no registration for that seat, 2 as above, 4 usage. ## Tests ``` node --test packages/seat/tests/ ``` The repository launch scripts are covered end to end by `scripts/test-darkwing-launch.mjs` (darkwing, dewey, filbert) and `scripts/test-rocko-launch.mjs`, which run each `launch.sh` in a fixture root that is also its own data root, so no registration reaches a real one.