Skip to content

feat(bin): add a single owner for verified harness launch commands - #1080

Open
sbracewell64 wants to merge 10 commits into
kunchenguid:mainfrom
sbracewell64:fm/fm-launch-lib-extract
Open

feat(bin): add a single owner for verified harness launch commands#1080
sbracewell64 wants to merge 10 commits into
kunchenguid:mainfrom
sbracewell64:fm/fm-launch-lib-extract

Conversation

@sbracewell64

Copy link
Copy Markdown

Intent

Give firstmate's harness launch knowledge exactly one owner, as a pure refactor with zero behavior change.

Background: firstmate is gaining a second caller of its launch knowledge - a fleet launcher that opens a PRIMARY firstmate session, alongside bin/fm-spawn.sh which opens crewmate sessions. Today that knowledge lives only inside bin/fm-spawn.sh. The drift this prevents already happened once: an external platform hand-copied Claude's launch command and dropped the CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false ghost-text suppression variable, which firstmate would otherwise read as real typed input when it captures a pane. One owner, or it happens again.

What was done: extracted launch_template, model_flag_for_harness, and effort_flag_for_harness out of bin/fm-spawn.sh into a new sourced bin/fm-launch-lib.sh, and had fm-spawn.sh source it. No copy of those three functions remains in fm-spawn.sh; the library is their single definition.

Deliberate decision the captain specified up front, not a free choice made during implementation: launch_template additionally gains a 'kind' axis distinguishing 'primary' from the existing crewmate shape, because a primary session has no task, no worktree, no brief, and no status file. The primary branch was added while keeping every existing crewmate call path byte-identical in behavior - a crewmate spawn on every verified harness must compose exactly the command it composes today, ghost-text variable included. The second consumer (sibling task fm-launch-menu, blocked on this one) is already designed against this exact shape, so the axis shape is intentional and any alternative shaping would have required escalation rather than a local judgment call.

Also deliberate: the launcher itself is explicitly out of scope and is not built here. bin/fm-brief.sh, bin/backends/herdr.sh, bin/fm-backend.sh, bin/fm-merge-local.sh, and bin/fm-ff-lib.sh were left untouched because other workers were concurrently live in those files.

Testing: a colocated regression test covers the extracted library directly, including the new primary kind, and proves the crewmate templates are unchanged. The existing spawn test suite passes untouched.

Repo conventions applied per firstmate's coding guidelines: shellcheck-clean bin scripts, one sentence per line in prose, plain dashes, colocated tests, no agent co-author on commits.

This is a relaunch for publication only. The implementation already passed review, tests, documentation, and lint in a prior pipeline run; that run failed solely at the push step because the GitHub account had pull-only access to the upstream repository. That is now cleared: pushes are routed to the sbracewell64/firstmate fork while fetches still track upstream, so the resulting pull request into kunchenguid/firstmate is expected to be a cross-fork PR.

Two review findings from the prior run were escalated and decided by the project owner; both decisions are already committed on this branch, so they are deliberate and should not be re-flagged:

  1. The opencode primary template uses the full verified briefless shape - the OPENCODE_CONFIG_CONTENT allow-permission JSON plus opencode --auto, not --prompt. The crewmate template keeps --prompt because it has a brief to pass; a primary has none. The empirical evidence CONTRIBUTING.md:50 requires is tests/fm-opencode-primary-live-e2e.test.sh:256 and :310, which both launch a primary opencode TUI exactly that way, and it is cited in the code comment.

  2. Kimi binary resolution (resolve_kimi_binary) deliberately stays private to bin/fm-spawn.sh and was NOT extracted. The planned second consumer, fm-launch-menu, reaches Kimi through the pi harness rather than a native kimi binary, so it will never call it and there is no hand-copy drift to prevent; extracting it would have expanded a pure refactor to serve a consumer that will not exist. That rationale is recorded as a comment at the KIMIBIN placeholder so it is not re-raised, to be revisited only if a native kimi launch ever becomes a launcher entry.

Also already applied: the placeholder contract now lives in bin/fm-launch-lib.sh's own header (fm-spawn.sh points at it), AGENTS.md section 4 attributes the launch flags and unverified-adapter guard to bin/fm-launch-lib.sh, and the one-owner test guard in tests/fm-launch-lib.test.sh covers every per-harness autonomy marker with its fail-open || true removed.

Publication routing note: the gate is configured with fork = sbracewell64/firstmate, so branches push to that fork while the pull request opens against kunchenguid/firstmate. A cross-fork PR is the expected and correct outcome here.

What Changed

  • Extracted launch_template, model_flag_for_harness, and effort_flag_for_harness (plus the shell_quote helper they depend on) out of bin/fm-spawn.sh into a new sourced bin/fm-launch-lib.sh, which is now their single definition; fm-spawn.sh sources it and keeps resolve_kimi_binary private, and the placeholder contract moved into the library's own header. Crewmate, scout, and secondmate launch commands are byte-identical to before.
  • launch_template gained a kind axis with a new primary arm for briefless primary sessions on claude, codex, opencode, pi, and grok, while kimi returns non-zero for primary so the guard refuses instead of emitting an unsubstitutable command. Each arm cites its in-repo evidence, the header records a consumer obligation to disclose the permission-bypass posture of every primary template that carries one, and the library distinguishes "unverified adapter" from "verified adapter, unsupported kind" as separate refusal causes.
  • Added tests/fm-launch-lib.test.sh pinning every crewmate and primary template plus a one-owner guard that fails if a launch command is hand-written elsewhere in bin/, mapped the new file into bin/fm-test-run.sh change-based selection, and repointed launch-command ownership in AGENTS.md, CONTRIBUTING.md, docs/configuration.md, docs/scripts.md, and the harness-adapters skill at bin/fm-launch-lib.sh.

Risk Assessment

✅ Low: This round changes only comments, it correctly resolves the prior finding by grounding pi's posture in SKILL.md:272 and widening the consumer obligation to all five primaries rather than exempting one, every citation verifies, and the crewmate call paths remain byte-identical to the base commit.

Testing

I exercised the extraction end to end rather than only through unit assertions: the new tests/fm-launch-lib.test.sh, the touched kimi/pi/captain-translation contract tests, and the three spawn suites all pass, and on top of that I built a differential harness that compares base commit a5fe1bc against HEAD across 438 fully substituted launch commands (zero differences) and a real-script end-to-end run where bin/fm-spawn.sh at each commit types its launch line into a recording tmux (byte-identical for all five task harnesses). The new primary kind was demonstrated as the fleet launcher will consume it, including kimi's refusal and the fail-closed unverified-adapter guard still firing from the real script, and five planted mutations confirmed the one-owner guard and the template pins actually catch drift including the exact historical ghost-text omission. No visual artifact applies - this change is shell library code whose entire end-user surface is the composed launch command string, which the CLI transcripts capture directly. The only failure seen was one node-driven pi watch-extension assertion that reproduces unchanged at the base commit.

Evidence: base vs HEAD launch command differential (438 combinations, 0 differences) + the new primary kind

combinations compared (harness x kind x model x effort, fully substituted): 438 RESULT: 0 differences. Every composed launch command is byte-identical. --- ghost-text suppression survived the extraction (the drift this refactor exists to prevent) --- claude ship carries CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude scout carries CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude secondmate carries CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude primary carries CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false NEW 'primary' KIND: what the fleet launcher will get from the one owner claude CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude --dangerously-skip-permissions --model 'opus' --effort 'high' codex codex --model 'opus' -c 'model_reasoning_effort="high"' --dangerously-bypass-approvals-and-sandbox opencode OPENCODE_CONFIG_CONTENT='{"permission":{"*":"allow"}}' opencode --model 'opus' --auto pi pi --model 'opus' --thinking 'high' grok grok --trust --always-approve --model 'opus' --reasoning-effort 'high' kimi REFUSED (exit 1, no output) - not a verified primary harness --- unverified adapter still refused for every kind (the fail-closed guard) --- kind=ship refused (exit 1) kind=scout refused (exit 1) kind=secondmate refused (exit 1) kind=primary refused (exit 1)

==============================================================================
 CREWMATE / SCOUT / SECONDMATE LAUNCH COMMANDS: base a5fe1bc  vs  HEAD b08b230
==============================================================================
combinations compared (harness x kind x model x effort, fully substituted): 438

RESULT: 0 differences. Every composed launch command is byte-identical.

--- the exact commands a crewmate spawn types into the pane (kind=ship, model=opus, effort=high) ---
  claude    CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude --dangerously-skip-permissions --model 'opus' --effort 'high' "$('/home/cap/firstmate/bin/fm-operational-input.sh' encode launch-brief < '/home/cap/.firstmate/data/t-42/brief.md')"
  codex     codex --model 'opus' -c 'model_reasoning_effort="high"' --dangerously-bypass-approvals-and-sandbox -c "notify=[\"bash\",\"-c\",\"touch '/home/cap/.firstmate/state/t-42.turn-ended'\"]" "$('/home/cap/firstmate/bin/fm-operational-input.sh' encode launch-brief < '/home/cap/.firstmate/data/t-42/brief.md')"
  opencode  OPENCODE_CONFIG_CONTENT='{"permission":{"*":"allow"}}' opencode --model 'opus' --prompt "$('/home/cap/firstmate/bin/fm-operational-input.sh' encode launch-brief < '/home/cap/.firstmate/data/t-42/brief.md')"
  pi        pi --model 'opus' --thinking 'high' -e '/home/cap/.firstmate/state/t-42.pi-ext.ts' "$('/home/cap/firstmate/bin/fm-operational-input.sh' encode launch-brief < '/home/cap/.firstmate/data/t-42/brief.md')"
  grok      grok --always-approve --model 'opus' --reasoning-effort 'high' "$('/home/cap/firstmate/bin/fm-operational-input.sh' encode launch-brief < '/home/cap/.firstmate/data/t-42/brief.md')"
  kimi      '/home/cap/.local/bin/kimi' --model 'opus' --auto

--- ghost-text suppression survived the extraction (the drift this refactor exists to prevent) ---
  claude ship        carries CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false
  claude scout       carries CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false
  claude secondmate  carries CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false
  claude primary     carries CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false

==============================================================================
 NEW 'primary' KIND: what the fleet launcher will get from the one owner
==============================================================================
  claude    CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude --dangerously-skip-permissions --model 'opus' --effort 'high' 
  codex     codex --model 'opus' -c 'model_reasoning_effort="high"' --dangerously-bypass-approvals-and-sandbox
  opencode  OPENCODE_CONFIG_CONTENT='{"permission":{"*":"allow"}}' opencode --model 'opus' --auto
  pi        pi --model 'opus' --thinking 'high' 
  grok      grok --trust --always-approve --model 'opus' --reasoning-effort 'high' 
  kimi      REFUSED (exit 1, no output) - not a verified primary harness

  (base commit had no primary kind at all:)
  base launch_template claude primary -> CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude --dangerously-skip-permissions __MODELFLAG____EFFORTFLAG__"$(__OPINPUT__ encode launch-brief < __BRIEF__)"

--- a primary carries no task-scoped placeholder (no task, no worktree, no brief, no status file) ---
  claude    clean (only flag placeholders): CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude --dangerously-skip-permissions __MODELFLAG____EFFORTFLAG__
  codex     clean (only flag placeholders): codex __MODELFLAG____EFFORTFLAG__--dangerously-bypass-approvals-and-sandbox
  opencode  clean (only flag placeholders): OPENCODE_CONFIG_CONTENT='{"permission":{"*":"allow"}}' opencode __MODELFLAG__--auto
  pi        clean (only flag placeholders): pi __MODELFLAG____EFFORTFLAG__
  grok      clean (only flag placeholders): grok --trust --always-approve __MODELFLAG____EFFORTFLAG__

--- unverified adapter still refused for every kind (the fail-closed guard) ---
  kind=ship        refused (exit 1)
  kind=scout       refused (exit 1)
  kind=secondmate  refused (exit 1)
  kind=primary     refused (exit 1)
Evidence: end-to-end: the literal command the real fm-spawn.sh types into the crewmate pane, base vs HEAD

--- claude ------------------------------------------------------------- HEAD b08b230 (bin/fm-launch-lib.sh): CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude --dangerously-skip-permissions --model 'opus' --effort 'high' "$('<FM_ROOT>/bin/fm-operational-input.sh' encode launch-brief < '<RUN>/home/data/<TASK>/brief.md')" base a5fe1bc (pre-extraction fm-spawn.sh): IDENTICAL --- codex ------------------------------------------------------------- HEAD b08b230 (bin/fm-launch-lib.sh): codex --model 'opus' -c 'model_reasoning_effort="high"' --dangerously-bypass-approvals-and-sandbox -c "notify=[&#34;bash&#34;,&#34;-c&#34;,&#34;touch '<RUN>/home/state/<TASK>.turn-ended'&#34;]" "$('<FM_ROOT>/bin/fm-operational-input.sh' encode launch-brief < '<RUN>/home/data/<TASK>/brief.md')" base a5fe1bc (pre-extraction fm-spawn.sh): IDENTICAL --- opencode ---------------------------------------------------------- base a5fe1bc: IDENTICAL --- pi ---------------------------------------------------------------- base a5fe1bc: IDENTICAL --- grok -------------------------------------------------------------- base a5fe1bc: IDENTICAL RESULT: real spawned launch lines are byte-identical at base and HEAD --- fail-closed unverified-adapter guard, still enforced through the sourced library --- $ fm-spawn.sh <task> <project> not-a-harness error: unknown harness 'not-a-harness'; pass a raw launch command to use an unverified adapter

==============================================================================
 END TO END: the literal command bin/fm-spawn.sh types into the crewmate pane
 (real fm-spawn.sh, real git worktree, fake tmux recording send-keys -l)
==============================================================================

--- claude -------------------------------------------------------------
  HEAD b08b230 (bin/fm-launch-lib.sh):
    CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude --dangerously-skip-permissions --model 'opus' --effort 'high' "$('<FM_ROOT>/bin/fm-operational-input.sh' encode launch-brief < '<RUN>/home/data/<TASK>/brief.md')"
  base a5fe1bc (pre-extraction fm-spawn.sh): IDENTICAL

--- codex -------------------------------------------------------------
  HEAD b08b230 (bin/fm-launch-lib.sh):
    codex --model 'opus' -c 'model_reasoning_effort="high"' --dangerously-bypass-approvals-and-sandbox -c "notify=[\"bash\",\"-c\",\"touch '<RUN>/home/state/<TASK>.turn-ended'\"]" "$('<FM_ROOT>/bin/fm-operational-input.sh' encode launch-brief < '<RUN>/home/data/<TASK>/brief.md')"
  base a5fe1bc (pre-extraction fm-spawn.sh): IDENTICAL

--- opencode -------------------------------------------------------------
  HEAD b08b230 (bin/fm-launch-lib.sh):
    OPENCODE_CONFIG_CONTENT='{"permission":{"*":"allow"}}' opencode --model 'opus' --prompt "$('<FM_ROOT>/bin/fm-operational-input.sh' encode launch-brief < '<RUN>/home/data/<TASK>/brief.md')"
  base a5fe1bc (pre-extraction fm-spawn.sh): IDENTICAL

--- pi -------------------------------------------------------------
  HEAD b08b230 (bin/fm-launch-lib.sh):
    pi --model 'opus' --thinking 'high' -e '<RUN>/home/state/<TASK>.pi-ext.ts' "$('<FM_ROOT>/bin/fm-operational-input.sh' encode launch-brief < '<RUN>/home/data/<TASK>/brief.md')"
  base a5fe1bc (pre-extraction fm-spawn.sh): IDENTICAL

--- grok -------------------------------------------------------------
  HEAD b08b230 (bin/fm-launch-lib.sh):
    grok --always-approve --model 'opus' --reasoning-effort 'high' "$('<FM_ROOT>/bin/fm-operational-input.sh' encode launch-brief < '<RUN>/home/data/<TASK>/brief.md')"
  base a5fe1bc (pre-extraction fm-spawn.sh): IDENTICAL

RESULT: real spawned launch lines are byte-identical at base and HEAD

--- fail-closed unverified-adapter guard, still enforced through the sourced library ---
  $ fm-spawn.sh <task> <project> not-a-harness
  error: unknown harness 'not-a-harness'; pass a raw launch command to use an unverified adapter
Evidence: mutation check: the one-owner guard and template pins catch all five planted drifts

--- mutation 1: a second bin/ consumer hand-copies claude's command --- not ok - a launch command is hand-written outside bin/fm-launch-lib.sh ('--dangerously-skip-permissions'): bin/fm-launch-menu.sh --- mutation 2: fm-spawn.sh keeps its own copy of launch_template --- not ok - bin/fm-spawn.sh defines launch_template again; bin/fm-launch-lib.sh is the single owner --- mutation 3: the grok primary template loses --trust --- not ok - launch_template grok primary drifted: expected: grok --trust --always-approve MODELFLAG____EFFORTFLAG got: grok --always-approve MODELFLAG____EFFORTFLAG --- mutation 4: the opencode primary reverts to the crewmate --prompt shape --- not ok - launch_template opencode primary drifted: expected: ... opencode MODELFLAG--auto got: ... opencode MODELFLAG--prompt --- mutation 5: a shipped crewmate template drifts (claude loses the prefix) --- not ok - launch_template claude ship drifted not ok - existing launch template changed: (tests/fm-kimi-harness.test.sh byte pin) RESULT: all five mutations are caught; the guard and the pins are load-bearing.

==============================================================================
 MUTATION CHECK: are the one-owner guard and the template pins real?
 Each mutation is planted in a throwaway copy of HEAD, the targeted test is
 run, then the mutation is reverted. The real worktree is never touched.
==============================================================================

--- control: unmutated HEAD ---
ok - one owner: bin/fm-spawn.sh sources the library and redefines nothing
ok - one owner: no other script under bin/ hand-writes any verified harness launch command

--- mutation 1: a second bin/ consumer hand-copies claude's command and drops
    CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION (the drift that already happened once) ---
not ok - a launch command is hand-written outside bin/fm-launch-lib.sh ('--dangerously-skip-permissions'): bin/fm-launch-menu.sh

--- mutation 2: fm-spawn.sh keeps its own copy of launch_template ---
not ok - bin/fm-spawn.sh defines launch_template again; bin/fm-launch-lib.sh is the single owner

--- mutation 3: the grok primary template loses --trust (turn-end guard fails open) ---
not ok - launch_template grok primary drifted:
  expected: grok --trust --always-approve __MODELFLAG____EFFORTFLAG__
  got:      grok --always-approve __MODELFLAG____EFFORTFLAG__

--- mutation 4: the opencode primary reverts to the crewmate --prompt shape ---
not ok - launch_template opencode primary drifted:
  expected: OPENCODE_CONFIG_CONTENT='{"permission":{"*":"allow"}}' opencode __MODELFLAG__--auto
  got:      OPENCODE_CONFIG_CONTENT='{"permission":{"*":"allow"}}' opencode __MODELFLAG__--prompt

--- mutation 5: a shipped crewmate template drifts (claude loses the prefix) ---
not ok - launch_template claude ship drifted:
  expected: CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude --dangerously-skip-permissions __MODELFLAG____EFFORTFLAG__"$(__OPINPUT__ encode launch-brief < __BRIEF__)"
  got:      claude --dangerously-skip-permissions __MODELFLAG____EFFORTFLAG__"$(__OPINPUT__ encode launch-brief < __BRIEF__)"
not ok - existing launch template changed:     claude) printf '%s' 'CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude --dangerously-skip-permissions __MODELFLAG____EFFORTFLAG__"$(__OPINPUT__ encode launch-brief < __BRIEF__)"' ;;

--- copy restored: clean again ---
ok - one owner: no other script under bin/ hand-writes any verified harness launch command

RESULT: all five mutations are caught; the guard and the pins are load-bearing.
Evidence: reproducible differential harness (base vs HEAD launch commands)
#!/usr/bin/env bash
# Differential proof: every crewmate/scout/secondmate launch command firstmate
# composes at HEAD (bin/fm-launch-lib.sh) is byte-identical to the command the
# pre-extraction bin/fm-spawn.sh composed at base commit a5fe1bc.
#
# It does NOT compare the template functions in the abstract: it replays
# fm-spawn.sh's own placeholder substitution (fm-spawn.sh:1355-1370) so what is
# compared is the literal string typed into the agent's pane.
set -u

HEAD_ROOT=$1
BASE_ROOT=$2

# --- pull the three pre-extraction functions out of base fm-spawn.sh ---------
extract_fn() {
  awk -v fn="$2" '
    $0 == fn "() {" { inside = 1 }
    inside { print }
    inside && $0 == "}" { inside = 0 }
  ' "$1"
}

BASE_SPAWN="$BASE_ROOT/bin/fm-spawn.sh"
{
  extract_fn "$BASE_SPAWN" shell_quote
  extract_fn "$BASE_SPAWN" launch_template
  extract_fn "$BASE_SPAWN" model_flag_for_harness
  extract_fn "$BASE_SPAWN" effort_flag_for_harness
} > "$TMPD/base-fns.sh"

for fn in shell_quote launch_template model_flag_for_harness effort_flag_for_harness; do
  grep -q "^$fn() {" "$TMPD/base-fns.sh" || { echo "FATAL: could not extract $fn from base fm-spawn.sh"; exit 2; }
done

# --- fm-spawn.sh's own substitution step, replayed verbatim -----------------
# Representative values in the shape fm-spawn.sh passes them.
BRIEF=/home/cap/.firstmate/data/t-42/brief.md
TURNEND=/home/cap/.firstmate/state/t-42.turn-ended
PIEXT=/home/cap/.firstmate/state/t-42.pi-ext.ts
PITURNEND=/home/cap/.firstmate/projects/sm-1/.pi/extensions/fm-primary-turnend-guard.ts
PIWATCH=/home/cap/.firstmate/projects/sm-1/.pi/extensions/fm-primary-pi-watch.ts
OPINPUT=/home/cap/firstmate/bin/fm-operational-input.sh
KIMIBIN=/home/cap/.local/bin/kimi

compose() { # <harness> <kind> <model> <effort>  -- uses whatever fns are sourced
  local harness=$1 kind=$2 model=$3 effort=$4 launch modelflag effortflag
  launch=$(launch_template "$harness" "$kind") || { printf '<REFUSED exit=%s>' "$?"; return 0; }
  modelflag=$(model_flag_for_harness "$harness" "$model")
  effortflag=$(effort_flag_for_harness "$harness" "$effort")
  launch=${launch//__KIMIBIN__/$(shell_quote "$KIMIBIN")}
  launch=${launch//__MODELFLAG__/$modelflag}
  launch=${launch//__EFFORTFLAG__/$effortflag}
  launch=${launch//__BRIEF__/$(shell_quote "$BRIEF")}
  launch=${launch//__TURNEND__/$(shell_quote "$TURNEND")}
  launch=${launch//__PIEXT__/$(shell_quote "$PIEXT")}
  launch=${launch//__PITURNEND__/$(shell_quote "$PITURNEND")}
  launch=${launch//__PIWATCH__/$(shell_quote "$PIWATCH")}
  launch=${launch//__OPINPUT__/$(shell_quote "$OPINPUT")}
  printf '%s' "$launch"
}

HARNESSES=(claude codex opencode pi grok kimi)
CREW_KINDS=(ship scout secondmate)
MODELS=(default opus "sonnet-4-5" "o'pus")
EFFORTS=(default low medium high xhigh max)

dump_matrix() { # writes one "harness|kind|model|effort<TAB>command" line per combo
  local h k m e
  for h in "${HARNESSES[@]}"; do
    for k in "${CREW_KINDS[@]}"; do
      for m in "${MODELS[@]}"; do
        for e in "${EFFORTS[@]}"; do
          printf '%s|%s|%s|%s\t%s\n' "$h" "$k" "$m" "$e" "$(compose "$h" "$k" "$m" "$e")"
        done
      done
    done
  done
  # the omitted-kind default path fm-spawn also exercises
  for h in "${HARNESSES[@]}"; do
    printf '%s|<kind omitted>|default|default\t%s\n' "$h" "$(launch_template "$h")"
  done
}

# --- base side ---------------------------------------------------------------
( set -u; . "$TMPD/base-fns.sh"; dump_matrix ) > "$TMPD/base-matrix.txt"
# --- head side ---------------------------------------------------------------
( set -u; . "$HEAD_ROOT/bin/fm-launch-lib.sh"; dump_matrix ) > "$TMPD/head-matrix.txt"

echo "=============================================================================="
echo " CREWMATE / SCOUT / SECONDMATE LAUNCH COMMANDS: base a5fe1bc  vs  HEAD b08b230"
echo "=============================================================================="
echo "combinations compared (harness x kind x model x effort, fully substituted): $(wc -l < "$TMPD/head-matrix.txt")"
echo
if diff -u "$TMPD/base-matrix.txt" "$TMPD/head-matrix.txt" > "$TMPD/matrix.diff"; then
  echo "RESULT: 0 differences. Every composed launch command is byte-identical."
else
  echo "RESULT: DRIFT DETECTED"
  cat "$TMPD/matrix.diff"
fi
echo
echo "--- the exact commands a crewmate spawn types into the pane (kind=ship, model=opus, effort=high) ---"
( set -u; . "$HEAD_ROOT/bin/fm-launch-lib.sh"
  for h in "${HARNESSES[@]}"; do printf '  %-9s %s\n' "$h" "$(compose "$h" ship opus high)"; done )
echo
echo "--- ghost-text suppression survived the extraction (the drift this refactor exists to prevent) ---"
( set -u; . "$HEAD_ROOT/bin/fm-launch-lib.sh"
  for k in ship scout secondmate primary; do
    case "$(compose claude "$k" default default)" in
      CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false*) printf '  claude %-11s carries CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false\n' "$k" ;;
      *) printf '  claude %-11s MISSING THE GHOST-TEXT VARIABLE\n' "$k" ;;
    esac
  done )
echo
echo "=============================================================================="
echo " NEW 'primary' KIND: what the fleet launcher will get from the one owner"
echo "=============================================================================="
( set -u; . "$HEAD_ROOT/bin/fm-launch-lib.sh"
  for h in "${HARNESSES[@]}"; do
    if out=$(launch_template "$h" primary); then
      printf '  %-9s %s\n' "$h" "$(compose "$h" primary opus high)"
    else
      printf '  %-9s REFUSED (exit %s, no output) - not a verified primary harness\n' "$h" "$?"
    fi
  done )
echo
echo "  (base commit had no primary kind at all:)"
( set -u; . "$TMPD/base-fns.sh"
  if launch_template claude primary >/dev/null 2>&1; then
    printf '  base launch_template claude primary -> %s\n' "$(launch_template claude primary)"
  fi )
echo
echo "--- a primary carries no task-scoped placeholder (no task, no worktree, no brief, no status file) ---"
( set -u; . "$HEAD_ROOT/bin/fm-launch-lib.sh"
  for h in claude codex opencode pi grok; do
    tpl=$(launch_template "$h" primary)
    case "$tpl" in
      *__BRIEF__*|*__OPINPUT__*|*__TURNEND__*|*__PIEXT__*|*__PITURNEND__*|*__PIWATCH__*)
        printf '  %-9s LEAKS a task-scoped placeholder: %s\n' "$h" "$tpl" ;;
      *) printf '  %-9s clean (only flag placeholders): %s\n' "$h" "$tpl" ;;
    esac
  done )
echo
echo "--- unverified adapter still refused for every kind (the fail-closed guard) ---"
( set -u; . "$HEAD_ROOT/bin/fm-launch-lib.sh"
  for k in ship scout secondmate primary; do
    if launch_template not-a-harness "$k" >/dev/null 2>&1; then
      printf '  kind=%-11s NOT REFUSED\n' "$k"
    else
      printf '  kind=%-11s refused (exit 1)\n' "$k"
    fi
  done )
Evidence: reproducible end-to-end spawn harness
#!/usr/bin/env bash
# End-to-end: run the REAL bin/fm-spawn.sh (not re-sourced helper functions) at
# base a5fe1bc and at HEAD b08b230, against a fake tmux that records the literal
# string sent with `tmux send-keys -l`. That string is the launch command typed
# into the crewmate's pane, i.e. exactly what the end user's agent receives.
set -u

HEAD_ROOT=$1
BASE_ROOT=$2
OUT_ROOT=$(mktemp -d "${TMPDIR:-/tmp}/fm-spawn-e2e.XXXXXX")
export GIT_AUTHOR_NAME=fmtest GIT_AUTHOR_EMAIL=fmtest@example.invalid
export GIT_COMMITTER_NAME=fmtest GIT_COMMITTER_EMAIL=fmtest@example.invalid

make_fake_tmux() {
  local fakebin=$1
  mkdir -p "$fakebin"
  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
        [ "$prev" = "-l" ] && printf '%s\n' "$a" >> "$FM_FAKE_LAUNCH_LOG"
        prev=$a
      done
    fi
    exit 0 ;;
esac
exit 0
SH
  chmod +x "$fakebin/tmux"
  printf '#!/usr/bin/env bash\nexit 0\n' > "$fakebin/treehouse"
  chmod +x "$fakebin/treehouse"
}

# spawn_once <root> <tag> <harness> <model> <effort>  -> prints the launch line
spawn_once() {
  local root=$1 tag=$2 harness=$3 model=$4 effort=$5
  local dir="$OUT_ROOT/$tag" home="$OUT_ROOT/$tag/home" proj="$OUT_ROOT/$tag/project"
  local wt="$OUT_ROOT/$tag/wt" fakebin="$OUT_ROOT/$tag/fakebin" log="$OUT_ROOT/$tag/launch.log"
  local id="task-$tag"
  mkdir -p "$home/data/$id" "$home/projects" "$home/state" "$home/config" "$proj"
  printf '%s\n' "$harness" > "$home/config/crew-harness"
  printf 'Ship the thing.\n' > "$home/data/$id/brief.md"
  touch "$home/state/.last-watcher-beat"
  make_fake_tmux "$fakebin"
  git init --quiet "$proj"
  ( cd "$proj" && printf 'x\n' > f.txt && git add f.txt && git commit --quiet -m init )
  git -C "$proj" worktree add --quiet -b "wt-$tag" "$wt"
  : > "$log"
  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_GATE_REFUSE_BYPASS=1 FM_SPAWN_NO_GUARD=1 FM_FAKE_PANE_PATH="$wt" TMUX="fake,1,0" \
    FM_FAKE_LAUNCH_LOG="$log" GROK_HOME="$home/grok-home" PATH="$fakebin:$PATH" \
    "$root/bin/fm-spawn.sh" ${model:+--model "$model"} ${effort:+--effort "$effort"} \
    "$id" "$proj" ${FM_E2E_HARNESS_ARG:-} >"$dir/spawn.out" 2>&1
  # the launch command is the last -l line (the GOTMPDIR export is sent first)
  grep -v '^export GOTMPDIR=' "$log" | tail -1
}

HARNESSES="claude codex opencode pi grok"
echo "=============================================================================="
echo " END TO END: the literal command bin/fm-spawn.sh types into the crewmate pane"
echo " (real fm-spawn.sh, real git worktree, fake tmux recording send-keys -l)"
echo "=============================================================================="
echo
status=same
for h in $HARNESSES; do
  base_line=$(spawn_once "$BASE_ROOT" "base-$h" "$h" opus high)
  head_line=$(spawn_once "$HEAD_ROOT" "head-$h" "$h" opus high)
  # normalise the per-run temp paths, checkout root, and task id so only the
  # command shape is compared
  b=${base_line//$OUT_ROOT\/base-$h/<RUN>}; b=${b//$BASE_ROOT/<FM_ROOT>}; b=${b//task-base-$h/<TASK>}
  hh=${head_line//$OUT_ROOT\/head-$h/<RUN>}; hh=${hh//$HEAD_ROOT/<FM_ROOT>}; hh=${hh//task-head-$h/<TASK>}
  printf -- '--- %s -------------------------------------------------------------\n' "$h"
  printf '  HEAD b08b230 (bin/fm-launch-lib.sh):\n    %s\n' "$hh"
  if [ "$b" = "$hh" ]; then
    printf '  base a5fe1bc (pre-extraction fm-spawn.sh): IDENTICAL\n\n'
  else
    status=drift
    printf '  base a5fe1bc (pre-extraction fm-spawn.sh):\n    %s\n  *** DRIFT ***\n\n' "$b"
  fi
done
echo "RESULT: real spawned launch lines are $( [ "$status" = same ] && echo 'byte-identical at base and HEAD' || echo 'DIFFERENT' )"
echo
echo "--- fail-closed unverified-adapter guard, still enforced through the sourced library ---"
FM_E2E_HARNESS_ARG=not-a-harness spawn_once "$HEAD_ROOT" guard-unverified claude '' '' >/dev/null 2>&1
printf '  $ fm-spawn.sh <task> <project> not-a-harness\n  '
sed -n '1,3p' "$OUT_ROOT/guard-unverified/spawn.out"
rm -rf "$OUT_ROOT"
Evidence: changed-file test selection now includes tests/fm-launch-lib.test.sh
  tests/fm-arm-pretool-check.test.sh
  tests/fm-ask-user-authority.test.sh
  tests/fm-brief.test.sh
  tests/fm-calm-pi-extension.test.sh
  tests/fm-captain-translation-contract.test.sh
  tests/fm-cd-pretool-check.test.sh
  tests/fm-composer-ghost.test.sh
  tests/fm-composer-lib.test.sh
  tests/fm-crew-state.test.sh
  tests/fm-decision-hold-lifecycle.test.sh
  tests/fm-documentation-audiences.test.sh
  tests/fm-ensure-agents-md.test.sh
  tests/fm-grok-harness.test.sh
  tests/fm-herdr-lab.test.sh
  tests/fm-install-herdr.test.sh
  tests/fm-instruction-owners.test.sh
  tests/fm-kimi-harness.test.sh
  tests/fm-launch-lib.test.sh
  tests/fm-lint.test.sh
  tests/fm-nm-test-contract.test.sh
  tests/fm-no-mistakes-ownership.test.sh
  tests/fm-operational-input.test.sh
  tests/fm-pi-primary-types.test.sh
  tests/fm-send-popup-settle.test.sh
  tests/fm-send-settle.test.sh
  tests/fm-stow-contract.test.sh
  tests/fm-subagent-pretool-check.test.sh
  tests/fm-supervision-instructions.test.sh
  tests/fm-test-isolation-proof.test.sh
  tests/fm-test-run.test.sh
  tests/fm-tmux-submit-busy.test.sh
  tests/fm-transition-lib.test.sh
  tests/fm-backend-herdr.test.sh
  tests/fm-backend-tmux-smoke.test.sh
  tests/fm-backend.test.sh
  tests/fm-herdr-session-cleanup.test.sh
  tests/fm-send-strict.test.sh
  tests/fm-spawn-batch.test.sh
  tests/fm-spawn-dispatch-profile.test.sh
  tests/fm-spawn-worktree-settle.test.sh
  tests/fm-pi-watch-extension.test.sh
- Outcome: ⚠️ 1 info across 1 run (7m8s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 4 issues found → auto-fixed (4) ✅
  • ⚠️ bin/fm-launch-lib.sh:83 - The new grok primary template is grok --always-approve __MODELFLAG____EFFORTFLAG__, dropping --trust. Every other owner of grok primary knowledge says that flag is load-bearing: README.md:96 documents the primary launch as grok --trust, README.md:105 says "--trust is needed once per clone so project hooks and the turn-end guard load", docs/turnend-guard.md:63 repeats it, and .agents/skills/harness-adapters/SKILL.md:345 states that without folder trust "this primary guard fails open". The empirical evidence for a grok PRIMARY launch is tests/fm-grok-continuity-live-e2e.test.sh:76, which launches it as grok --trust --always-approve --reasoning-effort low - the same class of citation the author correctly used to justify opencode's --auto shape. A launcher inheriting this template on a not-yet-trusted clone silently loses the "no turn ends blind" backstop, which is precisely the hand-copied-command-drops-a-flag failure this whole change exists to prevent. Counter-argument worth the author's call: trust is granted once per clone, so an already-trusted captain home would not notice, and --trust may be considered setup rather than launch knowledge - hence asking rather than fixing. tests/fm-launch-lib.test.sh:90 currently pins the trustless shape, so it would need updating too.
  • ⚠️ bin/fm-launch-lib.sh:86 - launch_template kimi primary returns 0 with __KIMIBIN__ __MODELFLAG__--auto, but README.md:61 lists only Claude Code, Grok, Pi, Codex, and OpenCode as verified primary harnesses (docs/configuration.md:177 explicitly defers the narrower primary set to README), and __KIMIBIN__ is resolvable only inside bin/fm-spawn.sh by design. The result is the one primary arm that cannot produce a runnable command for any consumer other than fm-spawn.sh - and fm-spawn.sh never passes kind=primary (KIND is only ship|scout|secondmate at bin/fm-spawn.sh:145,172,173). A fleet launcher enumerating verified adapters and calling launch_template &#34;$h&#34; primary would get a literal __KIMIBIN__ --auto string instead of the clean non-zero refusal the unverified-adapter guard gives everywhere else, and tests/fm-launch-lib.test.sh:91 pins that shape. This is a separate question from the already-decided "resolve_kimi_binary stays private" call: given that decision, the consistent shape is for the primary kind to return 1 for kimi (matching kimi's absence from the supported primary set), so the guard refuses rather than emitting an unsubstitutable command. Flagging for the author because making launch_template total across all six verified adapters may itself be the intended contract.
  • ℹ️ tests/fm-launch-lib.test.sh:243 - The one-owner guard's --auto([^-a-z]|$) marker greps all of bin/ and fails if any file other than fm-launch-lib.sh matches. --auto is a common flag outside launch commands (gh pr merge --auto is the obvious candidate for the existing bin/fm-pr-*.sh scripts), so an unrelated future change would trip "a launch command is hand-written outside bin/fm-launch-lib.sh" with a misleading message. The guard's own self-check only proves the pattern still matches the library, not that it is specific to a launch command. Anchoring it to its adapter context (e.g. opencode [^&#39;]*--auto and __KIMIBIN__ [^&#39;]*--auto, or --auto&#39;$) keeps the hand-copy detection while removing the false positive. The other five markers are already adapter-specific enough not to have this problem.
  • ℹ️ bin/fm-launch-lib.sh:26 - shell_quote is a general-purpose helper, not launch knowledge, but it now lives in the launch library. bin/fm-spawn.sh calls it from several non-launch sites (lines 1253, 1273, 1355-1360, 1372), so sourcing fm-launch-lib.sh is silently load-bearing for code paths unrelated to launching; bin/fm-brief.sh:102 and bin/fm-supervision-instructions.sh:94 still carry their own copies, so this is now the third definition in bin/. The header comment already acknowledges the placement, and the intent explicitly excludes fm-brief.sh from this change, so noting only - a shared quoting lib would be the natural follow-up once those files are free.

🔧 Fix: add grok --trust, refuse kimi primary, pin every primary shape
3 issues (1 warning, 2 infos) still open:

  • ⚠️ bin/fm-launch-lib.sh:90 - Raising this for escalation exactly as the owner's instruction fix(bin): coalesce watcher signals into one wake #4 directed ("where a change would alter the AUTONOMY or PERMISSION posture of a primary session ... do NOT silently change it. Raise it as a finding"). The claude primary (line 90) carries --dangerously-skip-permissions and the codex primary (line 95) carries --dangerously-bypass-approvals-and-sandbox, both inherited from the crewmate command, and the author's own arm comments now state the repo has no interactive-primary evidence for either. I swept every in-repo use and confirm that: README.md:90 documents the primary launch as bare claude; the only claude launch carrying the flag is tests/fm-claude-stop-autoarm-live-e2e.test.sh:115 (claude -p ..., headless print mode), matching docs/arm-pretool-check.md:201, docs/cd-guard.md:158, and docs/subagent-guard.md:248 which are all -p too; every codex use (tests/fm-codex-continuity-live-e2e.test.sh:40-42, docs/arm-pretool-check.md:202, docs/cd-guard.md:159, docs/subagent-guard.md:185, docs/verification/supervision.md:17) is codex exec, headless. No doc anywhere states the primary session's permission posture. A COUNTER-ARGUMENT the arm comments miss and that likely settles it: this library's own header (line 38) defines the secondmate kind as "a firstmate PRIMARY launched in a provisioned secondmate home", and the shipped, byte-pinned secondmate templates (lines 142 and 146) launch that interactive primary firstmate session with exactly these two flags. That is real in-repo evidence for an interactive primary shape. The distinction left for the owner is that a secondmate is an unattended firstmate while the fleet launcher's primary is the captain's own attended session, so the flags may or may not belong. Decide and record it; the pins at tests/fm-launch-lib.test.sh:94 and :105 currently freeze the inherited shape either way.
  • ℹ️ bin/fm-launch-lib.sh:32 - The function contract still reads "Returns 1 for a harness with no verified adapter - that non-zero return is the unverified-adapter guard every caller relies on". After this round that is incomplete: kimi IS a verified adapter (its crewmate template is live at line 170 and fm-spawn.sh depends on it) yet line 125 now returns 1 for kind=primary. A caller cannot distinguish "unverified adapter" from "verified adapter, unsupported kind", and bin/fm-spawn.sh:437/441 hardcode the first reading in their error text ("unknown harness '$HARNESS'; pass a raw launch command to use an unverified adapter"). fm-spawn never passes kind=primary so nothing misreports today, but the fleet launcher this library exists to serve would tell a user kimi is an unverified adapter and point them at the raw-launch escape hatch, when the real reason is that kimi is deliberately outside the verified primary set. Extend the header sentence to cover both refusal reasons so the next consumer writes the right message.
  • ℹ️ bin/fm-launch-lib.sh:73 - The new header sentence asserts unconditionally that "Every primary template below is the shape this repo empirically verified for a briefless PRIMARY launch, not the crewmate command with its brief argument subtracted". Two of the five arms directly beneath it say the opposite: line 84-89 records that for claude "the repo pins the prefix but not this flag's place in an interactive primary", and line 91-94 records that for codex "No in-repo primary codex TUI launch exists to pin" with the flag carried over from the crewmate command. In the file that is now the single owner of launch knowledge, a header making an unqualified verification claim that its own arms disclaim is the same knowledge-drift risk this change exists to close - a future reader trusting the header would treat the claude and codex primary shapes as empirically settled. Qualify it (e.g. "each arm cites its evidence, and where the repo has none the arm says so explicitly"), which the arms already support.

🔧 Fix: record primary autonomy evidence and consumer obligation
1 warning still open:

  • ⚠️ bin/fm-launch-lib.sh:91 - The new CONSUMER OBLIGATION enumerates the primary templates whose posture a consumer must disclose: "the claude and codex primary templates launch a session that runs with NO permission prompts, and the opencode primary template allows every permission outright". Four of the five primary templates carry a permission-bypass flag, not three - the grok primary at line 158 is grok --trust --always-approve ..., and .agents/skills/harness-adapters/SKILL.md:304 defines --always-approve as "auto-approves every tool execution, verified to run fully unattended", explicitly equating it with --permission-mode bypassPermissions. That is the same no-permission-prompt posture the obligation exists to disclose. (pi at line 152 is correctly absent - it carries no autonomy flag.) Because the obligation is worded as binding and enumerative, a consumer implementing it literally would surface the posture for claude, codex, and opencode and silently ship grok's, which defeats the point of putting the obligation in the one owner. The same omission shows in the arm comments: the claude arm (lines 121-123) and codex arm both point back at the CONSUMER OBLIGATION, the grok arm does not. This is mechanical to close and matches the author's evident intent - the owner's instruction named only claude and codex, and the author already generalized it to opencode unprompted, so grok is the same generalization.

🔧 Fix: bind consumer obligation to any bypass flag, add grok
1 warning still open:

  • ⚠️ bin/fm-launch-lib.sh:167 - The new justification for excluding pi from the CONSUMER OBLIGATION is contradicted by the repo's own verified adapter knowledge. Lines 165-167 assert "--approve is part of that scaffolding, and leaving it out is why pi is the one primary template the header's CONSUMER OBLIGATION does not catch: a pi primary still prompts", and line 176 in the header repeats "a pi primary still prompts". But .agents/skills/harness-adapters/SKILL.md:272 states flatly: "Pi has no permission system, so crewmates are always autonomous." If pi has no permission system, a pi primary does not prompt - the correct reason pi is excluded is that there is no permission gate to bypass and therefore nothing for a consumer to disclose, not that the template withholds a bypass flag. The conclusion (exclude pi) is right; only the recorded reason is wrong. Two concrete harms, both the exact drift class this library exists to close. First, this file is now the designated single owner of launch knowledge, and it records a per-harness permission fact that its own skill contradicts. Second, the wording actively invites the next maintainer to "fix" it: read against the header's rule, "a pi primary still prompts" reads as pi missing an autonomy flag its siblings have, and the obvious next edit is to add --approve to the template - a silent permission-posture change of exactly the kind the owner has twice required be escalated rather than made locally. Separately, the claim that --approve is test-isolation scaffolding is unsupported: it appears in all nine pi invocations repo-wide (tests/fm-pi-primary-live-e2e.test.sh:124, 220, 233, 266 and tests/fm-calm-pi-extension.test.sh:1064, 1192, 1349, 1673, 1948) alongside completely different --no-* isolation sets, it is the only one of those flags without a --no- prefix, and no in-repo source documents its role - SKILL.md has Autonomy rows for grok (:304) and one at :361 but none for pi. Fix by grounding the exclusion in SKILL.md:272 and dropping the unsupported --approve characterization; do not add --approve to the template.

🔧 Fix: bind consumer obligation to every primary, fix pi rationale
✅ Re-checked - no issues remain.

⚠️ **Test** - 1 info
  • ℹ️ tests/fm-pi-watch-extension.test.sh:119 - tests/fm-pi-watch-extension.test.sh:test_pi_extension_reports_external_healthy_watcher fails in this environment ("expected exit 0, got 1"). I verified it fails identically at base commit a5fe1bc with the branch's changes absent, so it is pre-existing and unrelated to the launch-library extraction. The two assertions in that file that this change actually touches (the Pi secondmate launch wiring now read from bin/fm-launch-lib.sh) both pass.
  • bin/fm-test-run.sh tests/fm-launch-lib.test.sh tests/fm-kimi-harness.test.sh tests/fm-pi-watch-extension.test.sh tests/fm-captain-translation-contract.test.sh
  • bin/fm-test-run.sh tests/fm-spawn-batch.test.sh tests/fm-spawn-dispatch-profile.test.sh tests/fm-spawn-worktree-settle.test.sh (drives real spawns and pins the typed per-harness model/effort flags)
  • Differential sweep: sourced the pre-extraction launch_template/model_flag_for_harness/effort_flag_for_harness out of base a5fe1bc's bin/fm-spawn.sh and HEAD's bin/fm-launch-lib.sh, replayed fm-spawn.sh's own placeholder substitution, and diffed 438 fully composed commands (/tmp/no-mistakes-evidence/01KYFJ46F1VNA026V80MW8WQVD/launch-command-differential.sh)
  • End-to-end spawn: ran the real bin/fm-spawn.sh --model opus --effort high &lt;task&gt; &lt;project&gt; at base and at HEAD with a fake tmux recording send-keys -l, and compared the literal launch line typed into the pane for claude, codex, opencode, pi, grok (/tmp/no-mistakes-evidence/01KYFJ46F1VNA026V80MW8WQVD/spawn-e2e-launchline.sh)
  • Manual guard check: bin/fm-spawn.sh &lt;task&gt; &lt;project&gt; not-a-harness still refuses with error: unknown harness ... through the sourced library
  • Mutation check in a throwaway copy of HEAD: planted a hand-copied claude --dangerously-skip-permissions in a new bin/ script, redefined launch_template inside fm-spawn.sh, dropped grok primary --trust, reverted opencode primary to --prompt, and dropped claude's CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION prefix - each reverted after bash tests/fm-launch-lib.test.sh / bash tests/fm-kimi-harness.test.sh
  • bin/fm-test-run.sh --list --changed --base a5fe1bc to confirm the new bin/fm-launch-lib.sh maps into test selection
  • bash tests/fm-pi-watch-extension.test.sh run against a base-commit (a5fe1bc) checkout to confirm the one failing assertion is pre-existing
⚠️ **Document** - 1 info
  • ℹ️ bin/fm-launch-lib.sh:82 - bin/fm-launch-lib.sh's header and per-arm comments cite evidence by exact line number (README.md:61/90/96/102/105/106, .agents/skills/harness-adapters/SKILL.md:272/276-278/304/345, CONTRIBUTING.md:50, docs/configuration.md:177, docs/turnend-guard.md:63, several tests). Those citations are correct today and I preserved them by keeping every edit to a single-line, in-place rewrite, but nothing enforces them: any future insertion in those files silently slides the citations onto unrelated lines, and the argument each arm rests on becomes unverifiable. Out of scope here; worth a follow-up that either anchors the citations to quoted text/section headings or adds a check that resolves each cited line.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

A fleet launcher will soon open PRIMARY firstmate sessions alongside the
crewmate sessions fm-spawn.sh opens, so both need the same verified launch
commands. Today that knowledge lives only inside bin/fm-spawn.sh, and the
drift a second copy causes is not hypothetical: a downstream registry
hand-copied claude's command as `claude --dangerously-skip-permissions`,
dropping CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false - the ghost-text
suppression that keeps firstmate from reading predicted-prompt text as real
typed input when it captures a pane.

Extract launch_template, model_flag_for_harness, and effort_flag_for_harness
(plus the shell_quote both flag resolvers depend on) into a new sourced
bin/fm-launch-lib.sh, and have fm-spawn.sh source it. Every crewmate, scout,
and secondmate template is byte-identical to before, so spawn behavior is
unchanged on all six verified adapters.

launch_template also gains a `primary` kind for the launcher. A primary
session has no task, no worktree, no brief, and no status file, so it launches
bare and is greeted by the session-start adapters already installed in the
home; each primary template keeps its adapter's verified autonomy flag and
claude's ghost-text prefix. An unrecognized kind still resolves to the
crewmate shape, and an unverified adapter still returns non-zero for every
kind.

tests/fm-launch-lib.test.sh pins both arms directly, including a proof that
fm-spawn.sh redefines none of the functions and that no other script under
bin/ hand-writes a launch command. Existing suites that read the template
bytes now read them from their new owner.
@sbracewell64

Copy link
Copy Markdown
Author

CI verification available. Cross-fork PRs here do not run workflows without maintainer approval, so this PR shows no checks. The repository's own CI and no-mistakes-required workflows were run on the identical commit via a mirror PR in the author's fork: sbracewell64#2 — both green. No action requested; posting so the absence of checks here is not read as unverified.

@sbracewell64 sbracewell64 changed the title refactor(bin): give harness launch knowledge one owner in fm-launch-lib.sh feat(bin): add a single owner for verified harness launch commands Jul 27, 2026
@kunchenguid

kunchenguid commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Automated reminder: thanks for the PR! This branch currently has a merge conflict with the base branch.

When you get a chance, please rebase onto (or merge) the latest base branch, resolve the conflict, and push. After that, checks will re-run and the PR will get looked at again.

Noted for firstmate#1080 at 338f7aab.

@kunchenguid

Copy link
Copy Markdown
Owner

Automated reminder: this PR still looks blocked on a rebase or merge conflict fix.

If you are still interested, please rebase onto the current base branch, resolve the conflict, and push.

If I do not hear back, I may close this as inactive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants