Skip to content

feat(bin): add opt-in thin Pi crewmate launch via config/pi-crew-thin - #5

Merged
Vhailors merged 3 commits into
mainfrom
fm/bb-pi-thin-crew-launch-20260801
Aug 1, 2026
Merged

feat(bin): add opt-in thin Pi crewmate launch via config/pi-crew-thin#5
Vhailors merged 3 commits into
mainfrom
fm/bb-pi-thin-crew-launch-20260801

Conversation

@Vhailors

@Vhailors Vhailors commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Intent

Make BB secondmate-spawned normal Pi ship and scout crewmates start with minimal context only when their home-local config/pi-crew-thin contains 1 or on. Preserve the existing no-extensions, workflow, and turn-end behavior while adding --no-skills plus absolute global background-terminals and pi-render-cache extension paths, leave Pi secondmate coordinators and all other homes unchanged, and cover the launch template with colocated tests. Do not start bug-bounty hunts or change hunt rules.

What Changed

  • bin/fm-spawn.sh now reads a home-local config/pi-crew-thin (value 1 or on, surrounding whitespace tolerated) and, for normal Pi ship and scout crewmates only, adds --no-skills plus absolute -e paths for the globally installed background-terminals and pi-render-cache extensions to the Pi launch template; the --no-extensions boundary, workflow extension, and turn-end behavior are unchanged, and the Pi secondmate coordinator template never honors the opt-in.
  • Both thin extension paths resolve through a new shared pi_agent_dir helper (PI_CODING_AGENT_DIR then ~/.pi/agent), the same root the workflow extension already used, and a relative root or a missing extension refuses the spawn with an actionable message before endpoint and metadata creation instead of leaving an orphaned pane.
  • Documented the key in the AGENTS.md config inventory and docs/configuration.md (local, gitignored, not inherited into secondmate homes), and extended tests/fm-spawn-dispatch-profile.test.sh with six colocated cases covering the opt-in launch line, the on value, relocated-agent-dir resolution, the missing-extension refusal, a non-opt-in value, and the untouched Pi secondmate template.

Risk Assessment

✅ Low: The change is well-bounded and default-off: with no config/pi-crew-thin file every existing launch path is byte-identical (pinned by new assert_not_contains guards in test_pi_threads_model_and_max_effort), the opt-in fails closed before endpoint and metadata creation, it reuses one shared pi_agent_dir resolver, and six colocated tests cover the positive, whitespace, relocated-agent-dir, missing-extension-refusal, non-opt-in-value, and secondmate-unchanged cases; the only gap is documentation ownership.

Testing

I ran the change's colocated suite tests/fm-spawn-dispatch-profile.test.sh (all 34 cases pass, including the six new thin-Pi cases) and then demonstrated the intent the way an operator experiences it, by driving the real fm-spawn through a fake tmux pane that captures the literal command typed into a crewmate window: with no config/pi-crew-thin the Pi ship launch is identical to the previous one, with 1 or on it gains --no-skills and the two absolute global extension paths ahead of the preserved workflow and turn-end extensions for both ships and scouts, a Pi secondmate coordinator and a 0 value stay on their default templates, a missing pi-render-cache extension refuses with an actionable error and leaves no metadata or launched pane, and the flag is not inherited into a spawned secondmate home. No screenshot or rendered artifact applies — the change only affects the shell command firstmate types into a terminal pane, so the captured launch transcript is the actual end-user surface. The worktree was left clean.

Evidence: Thin Pi crewmate launch transcript (7 operator scenarios, real fm-spawn)

════════════════════════════════════════════════════════════════════
1. DEFAULT normal Pi ship crewmate (no config/pi-crew-thin)
════════════════════════════════════════════════════════════════════
config/pi-crew-thin: (absent)
--- operator command ---
fm spawn demo-default-1 <project> --model openai-codex/gpt-5.6-sol --effort max
--- fm-spawn output ---
warn: no registry at /tmp/thin-pi-demo.nTkfLg/demo-default/home/data/projects.md; defaulting project to no-mistakes off
spawned demo-default-1 harness=pi kind=ship mode=no-mistakes yolo=off window=firstmate:fm-demo-default-1 worktree=/tmp/thin-pi-demo.nTkfLg/demo-default/wt
--- command typed into the crewmate pane ---
FM_PI_HARNESS=pi FM_PI_EXTENSION_ISOLATION=1 pi --no-extensions --model 'openai-codex/gpt-5.6-sol' --thinking 'max'
      -e '/tmp/thin-pi-demo.nTkfLg/demo-default/pi-dynamic-workflows/extensions/workflow.ts'
      -e '/tmp/thin-pi-demo.nTkfLg/demo-default/home/state/demo-default-1.pi-ext.ts' "$('/home/dhajczuk/.no-mistakes/worktrees/31702870740f/01KYYA2XPM798C26HDHEASBCMX/bin/fm-operational-input.sh' encode launch-brief < '/tmp/thin-pi-demo.nTkfLg/demo-default/home/data/demo-default-1/brief.md')"

════════════════════════════════════════════════════════════════════
2. OPT-IN thin Pi ship crewmate (config/pi-crew-thin = 1)
════════════════════════════════════════════════════════════════════
config/pi-crew-thin: 1
--- operator command ---
fm spawn demo-thin-1 <project> --model openai-codex/gpt-5.6-sol --effort max
--- fm-spawn output ---
warn: no registry at /tmp/thin-pi-demo.nTkfLg/demo-thin/home/data/projects.md; defaulting project to no-mistakes off
spawned demo-thin-1 harness=pi kind=ship mode=no-mistakes yolo=off window=firstmate:fm-demo-thin-1 worktree=/tmp/thin-pi-demo.nTkfLg/demo-thin/wt
--- command typed into the crewmate pane ---
FM_PI_HARNESS=pi FM_PI_EXTENSION_ISOLATION=1 pi --no-extensions --model 'openai-codex/gpt-5.6-sol' --thinking 'max' --no-skills
      -e '/tmp/thin-pi-demo.nTkfLg/demo-thin/home/.pi/agent/extensions/background-terminals/index.ts'
      -e '/tmp/thin-pi-demo.nTkfLg/demo-thin/home/.pi/agent/npm/node_modules/pi-render-cache/extensions/index.ts'
      -e '/tmp/thin-pi-demo.nTkfLg/demo-thin/pi-dynamic-workflows/extensions/workflow.ts'
      -e '/tmp/thin-pi-demo.nTkfLg/demo-thin/home/state/demo-thin-1.pi-ext.ts' "$('/home/dhajczuk/.no-mistakes/worktrees/31702870740f/01KYYA2XPM798C26HDHEASBCMX/bin/fm-operational-input.sh' encode launch-brief < '/tmp/thin-pi-demo.nTkfLg/demo-thin/home/data/demo-thin-1/brief.md')"

════════════════════════════════════════════════════════════════════
3. OPT-IN thin Pi SCOUT (config/pi-crew-thin = "  on  ")
════════════════════════════════════════════════════════════════════
config/pi-crew-thin: [  on  ]
--- operator command ---
fm spawn demo-thin-scout-1 <project> --scout
--- fm-spawn output ---
warn: no registry at /tmp/thin-pi-demo.nTkfLg/demo-thin-scout/home/data/projects.md; defaulting project to no-mistakes off
spawned demo-thin-scout-1 harness=pi kind=scout mode=no-mistakes yolo=off window=firstmate:fm-demo-thin-scout-1 worktree=/tmp/thin-pi-demo.nTkfLg/demo-thin-scout/wt
--- command typed into the crewmate pane ---
FM_PI_HARNESS=pi FM_PI_EXTENSION_ISOLATION=1 pi --no-extensions --no-skills
      -e '/tmp/thin-pi-demo.nTkfLg/demo-thin-scout/home/.pi/agent/extensions/background-terminals/index.ts'
      -e '/tmp/thin-pi-demo.nTkfLg/demo-thin-scout/home/.pi/agent/npm/node_modules/pi-render-cache/extensions/index.ts'
      -e '/tmp/thin-pi-demo.nTkfLg/demo-thin-scout/pi-dynamic-workflows/extensions/workflow.ts'
      -e '/tmp/thin-pi-demo.nTkfLg/demo-thin-scout/home/state/demo-thin-scout-1.pi-ext.ts' "$('/home/dhajczuk/.no-mistakes/worktrees/31702870740f/01KYYA2XPM798C26HDHEASBCMX/bin/fm-operational-input.sh' encode launch-brief < '/tmp/thin-pi-demo.nTkfLg/demo-thin-scout/home/data/demo-thin-scout-1/brief.md')"

════════════════════════════════════════════════════════════════════
4. Pi SECONDMATE coordinator is unchanged by the same opt-in
════════════════════════════════════════════════════════════════════
config/pi-crew-thin: 1
--- operator command ---
fm spawn demo-sm-1 <secondmate-home> --secondmate --harness pi
--- fm-spawn output ---
warning: secondmate demo-sm-1 sync skipped before launch: primary default-branch commit cannot be resolved
spawned demo-sm-1 harness=pi kind=secondmate mode=secondmate yolo=off window=firstmate:fm-demo-sm-1 worktree=/tmp/thin-pi-demo.nTkfLg/demo-secondmate/secondmate-home
--- command typed into the crewmate pane ---
FM_ROOT_OVERRIDE= FM_STATE_OVERRIDE= FM_DATA_OVERRIDE= FM_PROJECTS_OVERRIDE= FM_CONFIG_OVERRIDE= FM_HOME='/tmp/thin-pi-demo.nTkfLg/demo-secondmate/secondmate-home' FM_PI_HARNESS=pi FM_PI_EXTENSION_ISOLATION=1 pi --no-extensions
      -e '/tmp/thin-pi-demo.nTkfLg/demo-secondmate/secondmate-home/.pi/extensions/fm-primary-turnend-guard.ts'
      -e '/tmp/thin-pi-demo.nTkfLg/demo-secondmate/secondmate-home/.pi/extensions/fm-primary-pi-watch.ts' "$('/home/dhajczuk/.no-mistakes/worktrees/31702870740f/01KYYA2XPM798C26HDHEASBCMX/bin/fm-operational-input.sh' encode launch-brief < '/tmp/thin-pi-demo.nTkfLg/demo-secondmate/secondmate-home/data/charter.md')"

════════════════════════════════════════════════════════════════════
5. Non-opt-in value (config/pi-crew-thin = 0) keeps the default launch
════════════════════════════════════════════════════════════════════
config/pi-crew-thin: 0
--- fm-spawn output ---
warn: no registry at /tmp/thin-pi-demo.nTkfLg/demo-unknown/home/data/projects.md; defaulting project to no-mistakes off
spawned demo-unknown-1 harness=pi kind=ship mode=no-mistakes yolo=off window=firstmate:fm-demo-unknown-1 worktree=/tmp/thin-pi-demo.nTkfLg/demo-unknown/wt
--- command typed into the crewmate pane ---
FM_PI_HARNESS=pi FM_PI_EXTENSION_ISOLATION=1 pi --no-extensions
      -e '/tmp/thin-pi-demo.nTkfLg/demo-unknown/pi-dynamic-workflows/extensions/workflow.ts'
      -e '/tmp/thin-pi-demo.nTkfLg/demo-unknown/home/state/demo-unknown-1.pi-ext.ts' "$('/home/dhajczuk/.no-mistakes/worktrees/31702870740f/01KYYA2XPM798C26HDHEASBCMX/bin/fm-operational-input.sh' encode launch-brief < '/tmp/thin-pi-demo.nTkfLg/demo-unknown/home/data/demo-unknown-1/brief.md')"

════════════════════════════════════════════════════════════════════
6. Opt-in with a MISSING global extension refuses before the endpoint exists
════════════════════════════════════════════════════════════════════
config/pi-crew-thin: 1  (pi-render-cache NOT installed)
--- fm-spawn output ---
error: thin pi crewmate pi-render-cache extension is missing: /tmp/thin-pi-demo.nTkfLg/demo-missing/home/.pi/agent/npm/node_modules/pi-render-cache/extensions/index.ts (install it, or remove config/pi-crew-thin to launch a normal pi crewmate)
exit status: 1
--- crewmate endpoint state after refusal ---
state/demo-missing-1.meta exists: no
launch command typed into a pane: none (nothing was launched)


════════════════════════════════════════════════════════════════════
7. The opt-in is home-local: a spawned secondmate home does not inherit it
════════════════════════════════════════════════════════════════════
primary home config/pi-crew-thin: 1
--- fm-spawn output ---
warning: secondmate demo-inherit-1 sync skipped before launch: primary default-branch commit cannot be resolved
spawned demo-inherit-1 harness=pi kind=secondmate mode=secondmate yolo=off window=firstmate:fm-demo-inherit-1 worktree=/tmp/thin-pi-demo.nTkfLg/demo-inherit/secondmate-home
--- secondmate home config/ after inheritance ---
crew-harness
secondmate config/pi-crew-thin exists: no
secondmate config/crew-harness inherited: pi
Evidence: Manual demonstration script used to produce the transcript
#!/usr/bin/env bash
# Manual end-to-end demonstration of the opt-in thin Pi crewmate launch.
#
# Drives the real bin/fm-spawn.sh through a fake tmux pane that captures the
# literal command firstmate types into a crewmate pane, so each scenario below
# shows exactly what an operator would see start in the new window.
set -u

REPO=${REPO:?set REPO to the firstmate worktree}
# shellcheck source=/dev/null
. "$REPO/tests/lib.sh"

SPAWN="$REPO/bin/fm-spawn.sh"
TMP_ROOT=$(fm_test_tmproot thin-pi-demo)

make_spawn_fakebin() {
  local dir=$1 fakebin
  fakebin=$(fm_fakebin "$dir")
  cat > "$fakebin/tmux" <<'SH'
#!/usr/bin/env bash
set -u
case "$*" in
  *"#{pane_current_path}"*) printf '%s\n' "${FM_FAKE_PANE_PATH:-}"; exit 0 ;;
esac
case "${1:-}" in
  display-message) printf 'firstmate\n'; exit 0 ;;
  list-windows) exit 0 ;;
  has-session|new-session|new-window|kill-window) exit 0 ;;
  send-keys)
    if [ -n "${FM_FAKE_LAUNCH_LOG:-}" ]; then
      prev=
      for a in "$@"; do
        if [ "$prev" = "-l" ]; then printf '%s\n' "$a" >> "$FM_FAKE_LAUNCH_LOG"; fi
        prev=$a
      done
    fi
    exit 0
    ;;
esac
exit 0
SH
  chmod +x "$fakebin/tmux"
  fm_fake_exit0 "$fakebin" treehouse pi-signed
  printf '%s\n' "$fakebin"
}

make_spawn_case() {
  local name=$1 harness=$2 case_dir home proj wt fakebin launchlog id
  shift 2
  case_dir="$TMP_ROOT/$name"
  home="$case_dir/home"; proj="$case_dir/project"; wt="$case_dir/wt"
  launchlog="$case_dir/launch.log"
  fakebin=$(make_spawn_fakebin "$case_dir/fake")
  mkdir -p "$home/data" "$home/projects" "$home/state" "$home/config"
  mkdir -p "$case_dir/pi-dynamic-workflows/extensions"
  : > "$case_dir/pi-dynamic-workflows/extensions/workflow.ts"
  printf '%s\n' "$harness" > "$home/config/crew-harness"
  fm_git_worktree "$proj" "$wt" "wt-$name"
  touch "$home/state/.last-watcher-beat"
  for id in "$@"; do
    mkdir -p "$home/data/$id"
    printf 'brief for %s\n' "$id" > "$home/data/$id/brief.md"
  done
  printf '%s\n' "$case_dir|$home|$proj|$wt|$fakebin|$launchlog"
}

seed_thin_pi_extensions() {
  local agent_dir=$1
  mkdir -p "$agent_dir/extensions/background-terminals" \
    "$agent_dir/npm/node_modules/pi-render-cache/extensions"
  : > "$agent_dir/extensions/background-terminals/index.ts"
  : > "$agent_dir/npm/node_modules/pi-render-cache/extensions/index.ts"
}

make_seeded_secondmate_home() {
  local home=$1 id=$2
  mkdir -p "$home/bin" "$home/data"
  printf '# Firstmate\n' > "$home/AGENTS.md"
  printf '%s\n' "$id" > "$home/.fm-secondmate-home"
  printf 'charter for %s\n' "$id" > "$home/data/charter.md"
}

run_spawn() {
  local home=$1 wt=$2 fakebin=$3 launchlog=$4
  shift 4
  : > "$launchlog"
  FM_ROOT_OVERRIDE='' FM_HOME="$home" \
    FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
    FM_PROJECTS_OVERRIDE="$home/projects" FM_CONFIG_OVERRIDE="$home/config" \
    FM_SPAWN_NO_GUARD=1 FM_FAKE_PANE_PATH="$wt" TMUX="fake,1,0" \
    CLAUDE_CONFIG_DIR='' \
    FM_PI_DYNAMIC_WORKFLOWS_EXTENSION="$CASE_DIR/pi-dynamic-workflows/extensions/workflow.ts" \
    PI_CODING_AGENT_DIR="${DEMO_PI_CODING_AGENT_DIR:-}" \
    FM_FAKE_LAUNCH_LOG="$launchlog" GROK_HOME="$home/grok-home" \
    HOME="$DEMO_HOME" PATH="$fakebin:$PATH" \
    "$SPAWN" "$@" 2>&1
}

read_case_record() {
  IFS='|' read -r CASE_DIR HOME_DIR PROJ_DIR WT_DIR FAKEBIN_DIR LAUNCH_LOG <<EOF
$1
EOF
}

hdr() { printf '\n════════════════════════════════════════════════════════════════════\n%s\n════════════════════════════════════════════════════════════════════\n' "$1"; }
# Fold the captured launch on ' -e ' so long absolute extension paths stay readable.
show_launch() {
  printf -- '--- command typed into the crewmate pane ---\n'
  sed 's/ -e /\n      -e /g' "$LAUNCH_LOG"
}

# ---------------------------------------------------------------------------
hdr '1. DEFAULT normal Pi ship crewmate (no config/pi-crew-thin)'
read_case_record "$(make_spawn_case demo-default pi demo-default-1)"
DEMO_HOME=$HOME_DIR
printf 'config/pi-crew-thin: %s\n' "$( [ -f "$HOME_DIR/config/pi-crew-thin" ] && cat "$HOME_DIR/config/pi-crew-thin" || echo '(absent)')"
printf -- '--- operator command ---\nfm spawn demo-default-1 <project> --model openai-codex/gpt-5.6-sol --effort max\n'
printf -- '--- fm-spawn output ---\n'
run_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" demo-default-1 "$PROJ_DIR" \
  --model openai-codex/gpt-5.6-sol --effort max
show_launch

# ---------------------------------------------------------------------------
hdr '2. OPT-IN thin Pi ship crewmate (config/pi-crew-thin = 1)'
read_case_record "$(make_spawn_case demo-thin pi demo-thin-1)"
DEMO_HOME=$HOME_DIR
seed_thin_pi_extensions "$HOME_DIR/.pi/agent"
printf '%s\n' 1 > "$HOME_DIR/config/pi-crew-thin"
printf 'config/pi-crew-thin: %s\n' "$(cat "$HOME_DIR/config/pi-crew-thin")"
printf -- '--- operator command ---\nfm spawn demo-thin-1 <project> --model openai-codex/gpt-5.6-sol --effort max\n'
printf -- '--- fm-spawn output ---\n'
run_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" demo-thin-1 "$PROJ_DIR" \
  --model openai-codex/gpt-5.6-sol --effort max
show_launch

# ---------------------------------------------------------------------------
hdr '3. OPT-IN thin Pi SCOUT (config/pi-crew-thin = "  on  ")'
read_case_record "$(make_spawn_case demo-thin-scout pi demo-thin-scout-1)"
DEMO_HOME=$HOME_DIR
seed_thin_pi_extensions "$HOME_DIR/.pi/agent"
printf '%s\n' '  on  ' > "$HOME_DIR/config/pi-crew-thin"
printf 'config/pi-crew-thin: [%s]\n' "$(cat "$HOME_DIR/config/pi-crew-thin")"
printf -- '--- operator command ---\nfm spawn demo-thin-scout-1 <project> --scout\n'
printf -- '--- fm-spawn output ---\n'
run_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" demo-thin-scout-1 "$PROJ_DIR" --scout
show_launch

# ---------------------------------------------------------------------------
hdr '4. Pi SECONDMATE coordinator is unchanged by the same opt-in'
read_case_record "$(make_spawn_case demo-secondmate codex demo-sm-1)"
DEMO_HOME=$HOME_DIR
seed_thin_pi_extensions "$HOME_DIR/.pi/agent"
printf '%s\n' 1 > "$HOME_DIR/config/pi-crew-thin"
SM="$CASE_DIR/secondmate-home"
make_seeded_secondmate_home "$SM" demo-sm-1
SM=$(cd "$SM" && pwd -P)
printf 'config/pi-crew-thin: %s\n' "$(cat "$HOME_DIR/config/pi-crew-thin")"
printf -- '--- operator command ---\nfm spawn demo-sm-1 <secondmate-home> --secondmate --harness pi\n'
printf -- '--- fm-spawn output ---\n'
run_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" demo-sm-1 "$SM" --secondmate --harness pi
show_launch

# ---------------------------------------------------------------------------
hdr '5. Non-opt-in value (config/pi-crew-thin = 0) keeps the default launch'
read_case_record "$(make_spawn_case demo-unknown pi demo-unknown-1)"
DEMO_HOME=$HOME_DIR
printf '%s\n' 0 > "$HOME_DIR/config/pi-crew-thin"
printf 'config/pi-crew-thin: %s\n' "$(cat "$HOME_DIR/config/pi-crew-thin")"
printf -- '--- fm-spawn output ---\n'
run_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" demo-unknown-1 "$PROJ_DIR"
show_launch

# ---------------------------------------------------------------------------
hdr '6. Opt-in with a MISSING global extension refuses before the endpoint exists'
read_case_record "$(make_spawn_case demo-missing pi demo-missing-1)"
DEMO_HOME=$HOME_DIR
mkdir -p "$HOME_DIR/.pi/agent/extensions/background-terminals"
: > "$HOME_DIR/.pi/agent/extensions/background-terminals/index.ts"
printf '%s\n' 1 > "$HOME_DIR/config/pi-crew-thin"
printf 'config/pi-crew-thin: 1  (pi-render-cache NOT installed)\n'
printf -- '--- fm-spawn output ---\n'
run_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" demo-missing-1 "$PROJ_DIR"
printf 'exit status: %s\n' "$?"
printf -- '--- crewmate endpoint state after refusal ---\n'
printf 'state/demo-missing-1.meta exists: %s\n' "$( [ -e "$HOME_DIR/state/demo-missing-1.meta" ] && echo yes || echo no)"
printf 'launch command typed into a pane: %s\n' "$( [ -s "$LAUNCH_LOG" ] && echo yes || echo 'none (nothing was launched)')"

printf '\n'

# ---------------------------------------------------------------------------
hdr '7. The opt-in is home-local: a spawned secondmate home does not inherit it'
read_case_record "$(make_spawn_case demo-inherit codex demo-inherit-1)"
DEMO_HOME=$HOME_DIR
printf '%s\n' 1 > "$HOME_DIR/config/pi-crew-thin"
printf '%s\n' pi > "$HOME_DIR/config/crew-harness"
SM2="$CASE_DIR/secondmate-home"
make_seeded_secondmate_home "$SM2" demo-inherit-1
SM2=$(cd "$SM2" && pwd -P)
printf 'primary home config/pi-crew-thin: %s\n' "$(cat "$HOME_DIR/config/pi-crew-thin")"
printf -- '--- fm-spawn output ---\n'
run_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" demo-inherit-1 "$SM2" --secondmate --harness pi
printf -- '--- secondmate home config/ after inheritance ---\n'
ls -1 "$SM2/config" 2>/dev/null || printf '(no config dir created)\n'
printf 'secondmate config/pi-crew-thin exists: %s\n' \
  "$( [ -e "$SM2/config/pi-crew-thin" ] && echo yes || echo no)"
printf 'secondmate config/crew-harness inherited: %s\n' \
  "$( [ -e "$SM2/config/crew-harness" ] && cat "$SM2/config/crew-harness" || echo no)"
printf '\n'
Evidence: Default vs. thin Pi crewmate launch command (key excerpt)
1. DEFAULT normal Pi ship crewmate (no config/pi-crew-thin)
spawned demo-default-1 harness=pi kind=ship ...
--- command typed into the crewmate pane ---
FM_PI_HARNESS=pi FM_PI_EXTENSION_ISOLATION=1 pi --no-extensions --model 'openai-codex/gpt-5.6-sol' --thinking 'max'
-e '<case>/pi-dynamic-workflows/extensions/workflow.ts'
-e '<home>/state/demo-default-1.pi-ext.ts' "$('.../fm-operational-input.sh' encode launch-brief < '<home>/data/demo-default-1/brief.md')"

2. OPT-IN thin Pi ship crewmate (config/pi-crew-thin = 1)
spawned demo-thin-1 harness=pi kind=ship ...
--- command typed into the crewmate pane ---
FM_PI_HARNESS=pi FM_PI_EXTENSION_ISOLATION=1 pi --no-extensions --model 'openai-codex/gpt-5.6-sol' --thinking 'max' --no-skills
-e '<home>/.pi/agent/extensions/background-terminals/index.ts'
-e '<home>/.pi/agent/npm/node_modules/pi-render-cache/extensions/index.ts'
-e '<case>/pi-dynamic-workflows/extensions/workflow.ts'
-e '<home>/state/demo-thin-1.pi-ext.ts' "$('.../fm-operational-input.sh' encode launch-brief < '<home>/data/demo-thin-1/brief.md')"

4. Pi SECONDMATE coordinator with the same config/pi-crew-thin = 1
--- command typed into the crewmate pane ---
... pi --no-extensions
-e '<sm-home>/.pi/extensions/fm-primary-turnend-guard.ts'
-e '<sm-home>/.pi/extensions/fm-primary-pi-watch.ts' ... (no --no-skills, no thin extensions)

6. Opt-in with a MISSING global extension
error: thin pi crewmate pi-render-cache extension is missing: <home>/.pi/agent/npm/node_modules/pi-render-cache/extensions/index.ts (install it, or remove config/pi-crew-thin to launch a normal pi crewmate)
exit status: 1
state/demo-missing-1.meta exists: no
launch command typed into a pane: none (nothing was launched)

7. Secondmate home inheritance
secondmate config/pi-crew-thin exists: no
secondmate config/crew-harness inherited: pi

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 warning
  • ⚠️ bin/fm-spawn.sh:1724 - bin/fm-spawn.sh:1724-1725 build the two thin -e paths with no existence check, unlike the sibling Pi preflight pi_dynamic_workflows_extension_path (bin/fm-spawn.sh:610-613) which refuses the spawn with an actionable message when the extension file is missing. On a home that sets config/pi-crew-thin=1 but does not have background-terminals or pi-render-cache installed at the assumed global paths, fm-spawn still reports spawned &lt;id&gt; harness=pi and sends a launch line referencing nonexistent files; pi either aborts (dead pane behind a live meta record) or silently starts without the extensions the opt-in promised, with no diagnostic either way. Whether both extensions should be hard requirements is a product decision (a home may deliberately have only one), so this needs the author's call rather than an automatic gate.
  • ⚠️ bin/fm-spawn.sh:1722 - The HOME must be absolute refusal at bin/fm-spawn.sh:1720-1723 runs after the pane is created and after state/<id>.meta is published (bin/fm-spawn.sh:1704) and after ORCA_ABORT_CLEANUP=0 (bin/fm-spawn.sh:1709), so spawn_abort_cleanup cleans up nothing. With PI_CODING_AGENT_DIR or FM_PI_DYNAMIC_WORKFLOWS_EXTENSION pointing at a valid absolute workflow extension while HOME is relative or unset, the workflow preflight at :714 passes, the endpoint and meta are created, and the spawn then exits 1 at :1722 leaving an orphaned pane plus a task record that claims a launched task. The existing Pi preflight contract (pinned by test_pi_crewmate_missing_workflow_extension_refuses_before_endpoint) is to refuse before the endpoint; move this HOME validation and the path computation next to PI_THIN_FLAGS=$(pi_crew_thin_flags) at bin/fm-spawn.sh:715.
  • ⚠️ bin/fm-spawn.sh:1725 - The thin extension paths hardcode $HOME/.pi/agent/... (bin/fm-spawn.sh:1724-1725) while the sibling resolver for the workflow extension honors PI_CODING_AGENT_DIR (bin/fm-spawn.sh:601, agent_dir=${PI_CODING_AGENT_DIR:-${HOME:-}/.pi/agent}). On a home with a relocated Pi agent dir - a configuration this script already supports - the workflow extension resolves correctly under PI_CODING_AGENT_DIR while both thin -e paths point into an unused $HOME/.pi/agent tree, so the opt-in silently loads neither extension. Resolving both thin paths from the same agent_dir the workflow resolver uses keeps them absolute and global as the intent requires while staying consistent with the one existing owner of Pi's agent-dir location.
  • ⚠️ bin/fm-spawn.sh:621 - bin/fm-spawn.sh:621 reads the opt-in with value=$(&lt;&#34;$CONFIG/pi-crew-thin&#34;), which strips only trailing newlines, then matches strictly against 1|on. Every other value-style config reader in this repo normalizes whitespace first (fm_backlog_backend_value at bin/fm-tasks-axi-lib.sh:58 and resolve_crew at bin/fm-harness.sh:82 both use tr -d &#39;[:space:]&#39;). A pi-crew-thin file written as 1 (trailing space), with a CRLF line ending, or with a leading blank/indent silently falls through to the default fat launch with no error, so an operator sees a normal Pi crewmate and no indication the opt-in was rejected. Use the same tr -d &#39;[:space:]&#39; read as the sibling config readers.
  • ℹ️ AGENTS.md:72 - AGENTS.md carries the canonical inventory of every config/* key, including local, gitignored, non-inherited ones (see config/calm at AGENTS.md:72 and config/herdr-presentation-spaces at AGENTS.md:73), and docs/configuration.md documents the value-bearing ones. The new config/pi-crew-thin key is documented only inside the fm-spawn.sh header comment (bin/fm-spawn.sh:141-144), so an operator reading the config inventory has no way to discover the opt-in or that it is deliberately not inherited into secondmate homes. Add one inventory line noting: LOCAL, gitignored, values 1|on, crewmate/scout only, not inherited.

🔧 Fix: validate thin Pi opt-in before endpoint creation
1 warning still open:

  • ⚠️ docs/configuration.md:216 - AGENTS.md:49 states the repo invariant that "docs/configuration.md is the single owner of the top-level operational-home layout and configuration schemas; each producing script's header and help own exact child fields and mutation mechanics", and AGENTS.md:166 splits ownership so bin/fm-spawn.sh owns launch flags and fail-closed validation while docs/configuration.md owns the schema. Every other config/ key in the inventory follows this and has both an AGENTS.md line and a docs/configuration.md section: config/calm (docs/configuration.md:26), config/backlog-backend (:34), config/herdr-presentation-spaces (:86), config/wedge-alarm (:108), config/cmux-socket-password (:177). The new config/pi-crew-thin key has only the AGENTS.md:73 inventory line - grep -rn pi-crew-thin returns zero hits under docs/. This matters concretely because docs/configuration.md:216 is the paragraph that describes the Pi-family crewmate/scout launch composition ("apply the same discovery boundary, explicitly load the installed pi-dynamic-workflows extensions/workflow.ts by absolute path, and fail before endpoint creation when that extension is unavailable"), and that description is now incomplete: it omits the opt-in that adds --no-skills and two more absolute -e extensions to that same launch, and omits the second fail-before-endpoint refusal the opt-in introduces (bin/fm-spawn.sh:650-657). Add a short section (or extend the paragraph at docs/configuration.md:216) covering: values 1 or on with surrounding whitespace tolerated, any other value or an absent file keeps the default launch, resolution under PI_CODING_AGENT_DIR then ~/.pi/agent, both extensions required so a missing one refuses before endpoint creation, crewmate/scout only and never the secondmate coordinator, and LOCAL/gitignored/not inherited.
✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-spawn-dispatch-profile.test.sh — full colocated suite (34 cases) including the six new thin-Pi cases: opt-in flags/extensions on a ship, on value with whitespace on a scout, resolution under a relocated PI_CODING_AGENT_DIR, missing-extension refusal before endpoint creation, non-opt-in value keeping the default launch, and the untouched Pi secondmate template
  • Manual end-to-end launch-command capture: ran the real bin/fm-spawn.sh behind a fake tmux that records the literal command sent with send-keys -l, across 7 operator scenarios (default ship, thin ship, thin scout, Pi secondmate, pi-crew-thin=0, missing pi-render-cache, secondmate config inheritance) — script /tmp/no-mistakes-evidence/01KYYA2XPM798C26HDHEASBCMX/thin-pi-demo.sh
  • Refusal-safety check in the same manual run: asserted exit status 1, the actionable thin pi crewmate pi-render-cache extension is missing: … message, absence of state/&lt;id&gt;.meta, and an empty launch log
  • Inheritance check: spawned a Pi secondmate from a home with config/pi-crew-thin=1 and inspected the secondmate home's config/crew-harness inherited, pi-crew-thin absent
  • git diff --name-only 1300bde c37db2f — confirmed only AGENTS.md, bin/fm-spawn.sh, and the colocated test changed (no hunt rules touched)
  • git status --porcelain — worktree left clean, no transient test artifacts
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@Vhailors
Vhailors merged commit cd9f768 into main Aug 1, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant