Add `mosaic fleet comms-block <role> [--host]` — an explicit-arg, comms-only emitter wrapping a new resolveCommsBlock() over readFleetCommsBlock. Unlike the launch-time reader (which returns '' on any miss so composeContract can no-op silently), the emitter fails loud: unknown role / missing roster → stderr + exit 1, so an operator can safely preview any peer's view and a typo is never a silent no-op. Add docs/fleet/FLEET-LAUNCH.md: the canonical roster-driven launch path (worker + orchestrator .env fold via MOSAIC_AGENT_COMMAND, which short-circuits the line-44 yolo hardcode), 3 launch gotchas (flag conflict, MOSAIC_AGENT_NAME baking, launchRuntime guards), the #632 preserve-list note, and the North-Star A→B→webUI launch-config arc. PATH A of the orchestrator-launch fix; PATH B (roster-native launch-config: yolo toggle + command/channels emission) tracked as #636. TDD: 6 new resolveCommsBlock cases; 177 fleet+comms tests green; typecheck, eslint, prettier clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EsgTQzV5YUGk1JtCLP4B83
8.0 KiB
Fleet Launch Runbook
How every Mosaic fleet agent — workers and the orchestrator — is launched, and how to
configure each one. The guiding principle: one roster-driven launcher. There is no bespoke
per-agent launch script; the roster plus per-agent .env files are the single source of launch
config.
The launch chain
| Layer | File | Responsibility |
|---|---|---|
| systemd unit | mosaic-agent@<role>.service |
One templated unit per role; ExecStart runs the session launcher with the instance name %i. Defaults MOSAIC_AGENT_RUNTIME=pi, MOSAIC_AGENT_NAME=%i. |
| session launcher | tools/fleet/start-agent-session.sh <role> |
Builds the launch command, opens the tmux pane, wires the heartbeat. |
| launch command | mosaic yolo <runtime> (or a per-agent override) |
Replaces the pane's foreground process with the runtime, fully seeded. |
| seeding | mosaic's composeContract() |
Injects the Constitution/USER/TOOLS/runtime contract, *.local overlays, and the Fleet-Comms cheat-sheet — all via --append-system-prompt. |
Per-agent overrides live in fleet/agents/<role>.env, generated from roster.yaml by
generateAgentEnv (packages/mosaic/src/commands/fleet.ts) and consumed by the launcher.
Worker launch path (default)
roster.yamlcarries each agent'sruntimeand optionalmodel_hint.generateAgentEnvemitsfleet/agents/<role>.envwithMOSAIC_AGENT_NAME,MOSAIC_AGENT_RUNTIME, andMOSAIC_AGENT_MODEL.start-agent-session.shhas noMOSAIC_AGENT_COMMANDset, so it falls through to the default (line ~44):MOSAIC_AGENT_COMMAND="mosaic yolo $MOSAIC_AGENT_RUNTIME${MOSAIC_AGENT_MODEL:+ --model $MOSAIC_AGENT_MODEL}"- The launcher bakes
MOSAIC_AGENT_NAMEinto the pane command (line ~118), socomposeContractcan inject the Fleet-Comms cheat-sheet for that role.
That is the whole worker path: roster → .env → mosaic yolo <runtime> → seeded pane.
Orchestrator fold (PATH A — ships today)
The orchestrator is just another roster agent launched through the canonical path — not a snowflake script.
| Piece | Value |
|---|---|
| host-side launcher | orchestrator-launch.sh |
| systemd unit | mosaic-fleet-orchestrator.service |
| tmux session | orchestrator (role-named) |
Set its launch command via fleet/agents/orchestrator.env:
MOSAIC_AGENT_COMMAND='mosaic yolo claude --channels plugin:discord@<channel>'
When MOSAIC_AGENT_COMMAND is set, start-agent-session.sh's if [ -z "$MOSAIC_AGENT_COMMAND" ]
guard (line ~41) is false, so the line-44 default — including its hardcoded yolo — is skipped
entirely. The override fully controls the runtime and flags. Routing through mosaic yolo claude
(rather than a raw claude invocation) is what gives the orchestrator the same full
composeContract seeding + Fleet-Comms cheat-sheet as every worker, with --channels and any
other flags passed straight through to the claude binary.
Launch gotchas
- Flag conflict.
mosaic yolo claudealready injects--dangerously-skip-permissions. Do not also pass--permission-mode bypassPermissions— theclaudebinary would receive both. Usemosaic yolo claude …alone (yolo covers the unattended posture), or non-yolomosaic claude --permission-mode bypassPermissions …. Never mix the two. MOSAIC_AGENT_NAMEmust reach the pane. The launcher bakes it from the instance name, andcomposeContractgates the Fleet-Comms block on it (launch.ts, incomposeContract) — and the role must be a member ofroster.yaml, or the block resolves empty.launchRuntimeguards.mosaic yolo clauderunscheckSoul/checkRuntime/checkSequentialThinking. The host needsSOUL.mdand the sequential-thinking MCP, or the launch aborts (a rawclaudeinvocation skipped these checks). Dry-run the composed command in a throwaway tmux session before swapping a live launcher.
Why per-agent .env survives upgrades (#632)
install.sh PRESERVE_PATHS includes fleet/*.yaml, fleet/agents, and fleet/run, so
mosaic update's framework re-seed preserves your roster and per-agent .env overrides
(glob-aware cp fallback; matching TS parity in file-adapter.ts). Before #632, an auto re-seed
could wipe them — which is exactly why PATH A's .env override is safe to rely on now.
Inspecting the comms wiring
mosaic fleet comms-block <role>prints the Fleet-Comms cheat-sheet a given role receives at launch — its[host:session]identity, the exactagent-send.shcommand for each peer, and the FLIP /--verifyconventions.--host <h>previews a cross-host view. An unknown role or missing roster fails loud (stderr + non-zero exit), so a typo is never a silent no-op.- Versus
mosaic compose-contract <runtime>: that emits the whole system prompt and reads the role fromMOSAIC_AGENT_NAME(a full-prompt smoke test).comms-blockis the targeted, explicit-arg, comms-only view — e.g.mosaic fleet comms-block coder0-0to preview a peer.
North Star / future direction
Vision: a webUI lets the user edit each agent's launch config — switch harness (claude / pi / codex / opencode), toggle yolo, pick a model, set a command/channels override — with no terminal.
Continuity — this is not a new launch path. It is a data-model + UI-binding layer over the existing roster-driven launcher. Field-by-field status today:
| Launch-config field | Roster-native today? | Mechanism / gap |
|---|---|---|
harness (runtime) |
✅ end-to-end | roster.runtime → generateAgentEnv emits MOSAIC_AGENT_RUNTIME → launcher line 44. UI just writes the field. |
model (model_hint) |
✅ end-to-end | roster.model_hint → MOSAIC_AGENT_MODEL → launcher line 44 --model. UI just writes the field. |
| yolo | ❌ new | Launcher line 44 hardcodes mosaic yolo. A non-yolo toggle needs a roster yolo field → emit MOSAIC_AGENT_YOLO → make line 44 conditional. |
| command / channels | ❌ new | MOSAIC_AGENT_COMMAND is consumed (launcher line ~12) but generateAgentEnv does not emit it. Needs a roster command/channels field → emitted. |
The arc:
- A —
.envMOSAIC_AGENT_COMMANDhatch: manual, ships now, kept safe across upgrades by #632. - B — roster-native launch-config: harness + model are already there; add the yolo toggle (line-44 conditional) and command/channels emission to complete the data model.
- webUI — binds dropdowns/toggles directly to those four roster fields.
PATH A's .env override is the manual form of exactly what PATH B makes roster-native and the
webUI edits — one continuous arc, not three separate features. PATH B is tracked as #636.