Files
stack/docs/fleet/FLEET-LAUNCH.md
Jason Woltje 1288c4bc2c
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
feat(fleet): comms-block emitter + FLEET-LAUNCH runbook (#633)
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
2026-06-22 16:50:24 -05:00

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)

  1. roster.yaml carries each agent's runtime and optional model_hint.
  2. generateAgentEnv emits fleet/agents/<role>.env with MOSAIC_AGENT_NAME, MOSAIC_AGENT_RUNTIME, and MOSAIC_AGENT_MODEL.
  3. start-agent-session.sh has no MOSAIC_AGENT_COMMAND set, so it falls through to the default (line ~44):
    MOSAIC_AGENT_COMMAND="mosaic yolo $MOSAIC_AGENT_RUNTIME${MOSAIC_AGENT_MODEL:+ --model $MOSAIC_AGENT_MODEL}"
    
  4. The launcher bakes MOSAIC_AGENT_NAME into the pane command (line ~118), so composeContract can inject the Fleet-Comms cheat-sheet for that role.

That is the whole worker path: roster → .envmosaic 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

  1. Flag conflict. mosaic yolo claude already injects --dangerously-skip-permissions. Do not also pass --permission-mode bypassPermissions — the claude binary would receive both. Use mosaic yolo claude … alone (yolo covers the unattended posture), or non-yolo mosaic claude --permission-mode bypassPermissions …. Never mix the two.
  2. MOSAIC_AGENT_NAME must reach the pane. The launcher bakes it from the instance name, and composeContract gates the Fleet-Comms block on it (launch.ts, in composeContract) — and the role must be a member of roster.yaml, or the block resolves empty.
  3. launchRuntime guards. mosaic yolo claude runs checkSoul / checkRuntime / checkSequentialThinking. The host needs SOUL.md and the sequential-thinking MCP, or the launch aborts (a raw claude invocation 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 exact agent-send.sh command for each peer, and the FLIP / --verify conventions. --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 from MOSAIC_AGENT_NAME (a full-prompt smoke test). comms-block is the targeted, explicit-arg, comms-only view — e.g. mosaic fleet comms-block coder0-0 to 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.runtimegenerateAgentEnv emits MOSAIC_AGENT_RUNTIME → launcher line 44. UI just writes the field.
model (model_hint) end-to-end roster.model_hintMOSAIC_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.env MOSAIC_AGENT_COMMAND hatch: 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.