fleet: start a roster pane through its seat when one is scaffolded

The roster lane and the harness-homes lane did not touch. start-agent-session.sh
ran `mosaic yolo "$RUNTIME"` with HOME set to the operator's home, so every fleet
seat on a host shared the operator's harness home and, for Claude, the operator's
own ~/.claude credentials. Nothing in framework/ called `mosaic fleet launch` at
all, which meant ~/.mosaic was a directory nothing read.

The pane now runs `mosaic fleet launch "$AGENT_NAME"` when a scaffolded seat
exists at $PANE_HOME/.mosaic/fleet/agents/<name>/profile.json, and the historical
command otherwise. Detection uses $PANE_HOME/.mosaic rather than MOSAIC_DATA_HOME
because the pane environment is cleared with env -i; the composition resolves the
same root from HOME, so the two cannot disagree.

Additive by construction: a host with no scaffolded seats launches exactly as
before, so this can land ahead of any seat being enrolled.

- fleet launch gains --dangerous, threaded to launchFleetRuntime. Without it a
  seat launched from the roster would drop the permissions footing `mosaic yolo`
  gave it and prompt at a pane with nobody at it. The roster launcher asks for it
  explicitly so it stays visible in the process table instead of becoming a
  profile default.
- A caller's --model replaces the profile's instead of being appended after it.
  The roster carries a model per seat and is the surface operators edit; emitting
  both flags would leave the choice to each harness's argument parser.
- Claude workdir trust is written into the seat's .claude.json when the pane will
  run in a seat home. It previously always went to the operator's ~/.claude.json,
  which would leave the seat prompting on its first turn.

Covers Jason's scope amendment for web1: without this seam, "multiple
authentication accounts and agent pegging to auth" cannot be demonstrated on a
roster-managed seat.
This commit is contained in:
terra
2026-08-14 18:35:11 -05:00
parent a12eeb4786
commit c1a42cdb81
5 changed files with 121 additions and 32 deletions
@@ -286,12 +286,24 @@ _build_runtime_bin_prefix() {
MOSAIC_RUNTIME_BIN_PREFIX=$(_build_runtime_bin_prefix)
PANE_PATH=${MOSAIC_RUNTIME_BIN_PREFIX:+${MOSAIC_RUNTIME_BIN_PREFIX}:}/usr/local/bin:/usr/bin:/bin
# A seat scaffolded by `mosaic fleet agent new` owns its harness home, settings
# overlay and auth bundle; launching it through `mosaic fleet launch` is what makes
# ~/.mosaic real for a roster-started pane instead of a directory nothing reads.
# Detection uses $PANE_HOME/.mosaic because the pane environment is cleared below,
# so `mosaic fleet launch` resolves the same root from HOME and the two agree.
FLEET_SEAT_DIR="$PANE_HOME/.mosaic/fleet/agents/$AGENT_NAME"
FLEET_SEAT=0
[ -f "$FLEET_SEAT_DIR/profile.json" ] && FLEET_SEAT=1
_ensure_claude_workdir_trusted() {
local workdir="$1"
local claude_json="$2"
local resolved
resolved=$(cd "$workdir" 2>/dev/null && pwd -P) || resolved="$workdir"
local claude_json="${MOSAIC_CLAUDE_JSON:-${CLAUDE_CONFIG_DIR:+$CLAUDE_CONFIG_DIR/.claude.json}}"
claude_json="${claude_json:-$HOME/.claude.json}"
if [ -z "$claude_json" ]; then
claude_json="${MOSAIC_CLAUDE_JSON:-${CLAUDE_CONFIG_DIR:+$CLAUDE_CONFIG_DIR/.claude.json}}"
claude_json="${claude_json:-$HOME/.claude.json}"
fi
command -v python3 >/dev/null 2>&1 || return 1
MOSAIC_CJ="$claude_json" MOSAIC_TRUST_DIR="$resolved" python3 - <<'PY'
import json, os, sys, tempfile
@@ -325,11 +337,23 @@ PY
}
if [ "$MOSAIC_AGENT_RUNTIME" = claude ]; then
_ensure_claude_workdir_trusted "$MOSAIC_AGENT_WORKDIR" || \
# Trust belongs to the home the seat will actually run in. Writing it to the
# operator's ~/.claude.json would leave the seat prompting on its first turn.
SEAT_CLAUDE_JSON=""
if [ "$FLEET_SEAT" = 1 ] && [ -d "$FLEET_SEAT_DIR/.claude" ]; then
SEAT_CLAUDE_JSON="$FLEET_SEAT_DIR/.claude/.claude.json"
fi
_ensure_claude_workdir_trusted "$MOSAIC_AGENT_WORKDIR" "$SEAT_CLAUDE_JSON" || \
echo "WARNING: could not pre-trust workdir for claude agent $AGENT_NAME" >&2
fi
LAUNCH_COMMAND=(mosaic yolo "$MOSAIC_AGENT_RUNTIME")
if [ "$FLEET_SEAT" = 1 ]; then
# --dangerous keeps the seat on the same permissions footing `mosaic yolo` gave it;
# the composition, not the roster, decides harness home, bundle and settings.
LAUNCH_COMMAND=(mosaic fleet launch "$AGENT_NAME" --dangerous)
else
LAUNCH_COMMAND=(mosaic yolo "$MOSAIC_AGENT_RUNTIME")
fi
if [ -n "$MOSAIC_AGENT_MODEL" ]; then LAUNCH_COMMAND+=(--model "$MOSAIC_AGENT_MODEL"); fi
if [ -n "$MOSAIC_AGENT_REASONING" ]; then LAUNCH_COMMAND+=(--thinking "$MOSAIC_AGENT_REASONING"); fi