New package packages/seat and wrapper scripts/mosaic. `launch <seat>` writes <dataRoot>/seats/<layout>/<seat>/registration.json and then execs the seat's launch.sh unchanged; `seat task <seat> <text>` edits the task only. The board reads registrations, matches by sessions directory, and lets a registered task, project or workspace override the derived value with a source tag. The four repository launch scripts register themselves unless already registered or run with --check. Fleet launchers untouched; one-liner on the plan page. Review found the record path keyed by seat name alone (repo and fleet "darkwing" would collide); fixed by keying on layout. Also: the Pi pin refusal now names installed and required versions. Tests: seat 15, control-board 89, launch scripts 5, registry 69, config 24. Co-Authored-By: Claude Fable 5.1 <[email protected]>
139 lines
6.5 KiB
Markdown
139 lines
6.5 KiB
Markdown
# seat
|
|
|
|
`mosaic launch <seat>` 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 <seat> <text>` 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 <seat|seat-dir> [--task TEXT] [--project NAME] [--workspace PATH]
|
|
[--harness NAME] [--repo PATH] [--config PATH] [-- args...]
|
|
scripts/mosaic seat task <seat> <text> [--layout repo|fleet|unknown] [--config PATH]
|
|
```
|
|
|
|
- `<seat>` is a name under `<repo>/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=<record path>`. 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
|
|
|
|
`<dataRoot>/seats/<layout>/<seat>/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 `<dataRoot>/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` (`<repo>/agents/<seat>` with `<repo>/.git`), `fleet`
|
|
(`<seatDir>/.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 `<dataRoot>/seats/<layout>/<seat>/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.
|