One cumulative control-board, webui and seat state. The four rows edit the
same files (scan.mjs, page.html, README.md, app.js), so they land together,
each on its own receipt:
- Row 18, Discord connector rows on the board (#1509): R3 approved by
Darkwing and Dewey, Gitea comment 26257, manifest 254403b8. Jason
accepted the visual test.
- Row 22, board attention status (#1503): Filbert approved R1, comment
26248, manifest e40b58ec; restart receipt 26249.
- #1511, task attribution (row 6 code phase): R2 approved by Filbert and
Dewey, manifest d4c96395. docs/TOOLS.md carries the approved --by usage
line (tools-usage.patch 86bcba3c).
- #1512, relaunch activity (row 6 pilot): R1 approved by Darkwing and
Dewey, candidate manifest 47769fad. All seven source files match it.
Row 16, internal development bootstrap (#1510): the seven files outside
shared records match Filbert's R1 pins, receipt 26204 (agents/researcher/*,
scripts/test-darkwing-launch.mjs, the bootstrap plan).
packages/webui/src/public/app.js is committed at its #1512 R1 pin ce7d79a4.
The working copy holds Dewey's unreviewed return-flow candidate on top of
that, and it stays uncommitted.
Also: the four row briefs and Darkwing's evidence records under
agents/darkwing/work, including the 2026-09-26 tree manifest and the #1512
re-run against 21e3e908. Serial acceptance command: 397/397, three runs.
The failures that only show when tests run concurrently are in #1509 engine
tests, and they reproduce on clean HEAD.
Suites on the exact staged tree: config 24, task 90, foundation 43,
conductor 17, release 14, auth 15, discord 63; package union 397/397
(serial); test-darkwing-launch 5/5.
Shared records (BUILD-LOG, QUEUE, CURRENT, DEFERRED, SESSIONS, AGENTS.md,
agents/README.md) follow in Sage's records commit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
8.6 KiB
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 and notes who set it. 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> [--by NAME] [--layout repo|fleet|unknown] [--config PATH]
<seat>is a name under<repo>/agents/(--repodefaults 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 executablelaunch.sh.- Everything after
--goes to the launch script untouched. launchwrites 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 runlaunch.shyourself, 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 marksprocess.execveexperimental (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 callmosaic launchagain;mosaic launchrefuses to run when it is set. seat taskrecords who set the task intaskSetBy(#1511):--by NAMEif given, else$MOSAIC_AGENT_NAMEif set and non-empty, else the wordunknown. An explicit value must be one token of 1 to 64 characters (letters, digits,._@:-, starting with a letter or digit); anything else refuses with exit 4 before the record is touched. A set but malformedMOSAIC_AGENT_NAMEalso refuses rather than being reported asunknown;--byis the explicit way past it. The value is never echoed back in a refusal. This is what the caller said, not a verified identity. Nothing checks it, nothing grants on it; the board displays it next to the task and does no more with it.launch --taskdoes not set the field.
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, and the board treats a
record whose pid is gone as stale (shown, but the derived values win). 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.
{
"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
}
After scripts/mosaic seat task darkwing "Row 6 (#1511)" --by jason the
same record also carries "taskSetBy": "jason" and a non-null
updatedAt; startedAt and every other field are unchanged.
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 adarkwingin this repository and adarkwingin 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.layoutisrepo(<repo>/agents/<seat>with<repo>/.git),fleet(<seatDir>/.piexists) orunknown(nothing derived,sessionsDirnull, the record is still written but the board cannot match it).updatedAt: set only byseat task, so a task change is distinguishable from a relaunch.taskSetBy: set only byseat task(see above). Optional in the shape: a record written before the field existed loads unchanged, the board showsunknownfor it, and nothing rewrites it until the nextseat taskor launch. The record version stays 1; a value that is not one bounded token makes the record unreadable (refused, not misread).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. A record whose pid is no
longer running is stale: still shown on the Registered line, but it does
not override anything. The row's taskSetBy is the record's setter only
while the task shown is the registered one (taskSource registration);
otherwise it is null, so a stale or empty registration never attributes a
transcript-derived task, and a Discord connector's fixed task never borrows
a native record's setter.
The repository launch scripts are the only launchers that register.
Fleet seats under ~/.mosaic stay on their own launchers (Jason's ruling,
2026-09-12); a fleet seat can still be registered by hand with
scripts/mosaic launch <seat-dir>.
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.