Skip to content

feat(bin): load pi-dynamic-workflows for Pi crewmates and scouts only - #4

Merged
Vhailors merged 9 commits into
mainfrom
fm/crewmate-pi-workflows-only-20260731
Jul 31, 2026
Merged

feat(bin): load pi-dynamic-workflows for Pi crewmates and scouts only#4
Vhailors merged 9 commits into
mainfrom
fm/crewmate-pi-workflows-only-20260731

Conversation

@Vhailors

Copy link
Copy Markdown
Owner

Intent

Enable the captain's 2026-07-31 Pi workflow role split: Firstmate primaries and persistent secondmates are orchestrators and must not load or use pi-dynamic-workflows, while Pi crewmates and scouts must load the installed extensions/workflow.ts by absolute path after --no-extensions for multi-agent fan-out. Preserve extension isolation, refuse a crewmate spawn before endpoint creation when the workflow extension is missing, keep personal Pi sessions outside Firstmate unchanged, add the role-split brief and documentation plus launch-template tests, and deliver the committed change through push and an open pull request without merging.

What Changed

  • bin/fm-spawn.sh now resolves the installed pi-dynamic-workflows extensions/workflow.ts by absolute path and appends it to the non-secondmate Pi launch template after --no-extensions, via a new __PIWORKFLOW__ placeholder. The path defaults to ${PI_CODING_AGENT_DIR:-~/.pi/agent}/git/github.com/QuintinShaw/pi-dynamic-workflows/extensions/workflow.ts and can be overridden with FM_PI_DYNAMIC_WORKFLOWS_EXTENSION; a relative or missing path refuses the crewmate/scout spawn before the endpoint is created. The Pi secondmate template and bin/fm-pi-primary.sh are left as orchestrators that never load the extension.
  • A tracked .pi/settings.json applies an all-resource exclusion filter (!** for extensions, skills, prompts, themes) to the global pi-dynamic-workflows package so a plain Pi session in this repo does not pick it up, and .gitignore now covers .pi/git/ and .pi/npm/ so project-scoped package installs cannot dirty the worktree. bin/fm-brief.sh adds a role-split line to the crewmate brief.
  • docs/configuration.md documents the project-settings filter, the crewmate/scout absolute-path load and its pre-endpoint refusal, and the new FM_PI_DYNAMIC_WORKFLOWS_EXTENSION environment variable. tests/fm-spawn-dispatch-profile.test.sh gains the missing-extension refusal case plus updated crewmate/secondmate launch-template assertions, with matching assertions in tests/fm-pi-watch-extension.test.sh and tests/fm-brief.test.sh.

Risk Assessment

✅ Low: The change is well-bounded - one launch placeholder, one pre-endpoint guard, a tracked project settings delta, docs and tests - and its two load-bearing assumptions (the autoload: false delta actually disables the package, and --no-extensions plus explicit -e is unaffected by that disable) were both confirmed against pi 0.82's package-manager and resource-loader internals, leaving only informational documentation and test-coverage gaps.

Testing

I ran the three test files this change touches (fm-spawn-dispatch-profile, fm-pi-watch-extension, fm-brief) plus the small gitignore and documentation-audience suites, and all pass. Because passing unit tests alone would not show the operator-visible behavior, I also built a manual harness that drives the real fm-spawn.sh and fm-pi-primary.sh against a fake tmux/pi and captured the literal launch commands: a Pi crewmate and a Pi scout each get --no-extensions -e '<abs>/pi-dynamic-workflows/extensions/workflow.ts' -e '<state>/<id>.pi-ext.ts', while the persistent Pi secondmate and the Firstmate Pi primary launcher get only the turn-end guard and watcher extensions with no workflow package anywhere in the command. The refusal path was exercised twice — a missing absolute path and a relative override — and both exit 1 with an actionable message naming crewmates and scouts, leave no task metadata behind, and type nothing into a pane. I additionally confirmed the default resolution honors PI_CODING_AGENT_DIR, that the tracked .pi/settings.json parses and applies the all-resource exclusion filter, and that the generated crewmate brief renders the role-split sentence. This change is CLI/config only with no rendered UI surface, so the reviewer-visible evidence is a CLI transcript rather than a screenshot. Push and PR delivery belong to later phases and were not performed here.

Evidence: Pi workflow role-split end-to-end CLI transcript (crewmate, scout, secondmate, refusals, primary launcher, project override, brief)

1. Pi CREWMATE FM_PI_HARNESS=pi FM_PI_EXTENSION_ISOLATION=1 pi --no-extensions -e '<case>/pi-dynamic-workflows/extensions/workflow.ts' -e '<home>/state/evidence-pi-crewmate-a1.pi-ext.ts' "$('.../bin/fm-operational-input.sh' encode launch-brief < '<home>/data/evidence-pi-crewmate-a1/brief.md')" 2. Pi SCOUT FM_PI_HARNESS=pi FM_PI_EXTENSION_ISOLATION=1 pi --no-extensions -e '<case>/pi-dynamic-workflows/extensions/workflow.ts' -e '<home>/state/evidence-pi-scout-a2.pi-ext.ts' ... 3. Persistent Pi SECONDMATE (orchestrator) ... FM_PI_HARNESS=pi FM_PI_EXTENSION_ISOLATION=1 pi --no-extensions -e '<sm>/.pi/extensions/fm-primary-turnend-guard.ts' -e '<sm>/.pi/extensions/fm-primary-pi-watch.ts' ... RESULT: OK - no pi-dynamic-workflows extension in the secondmate launch 4. Missing workflow extension error: pi crewmate/scout workflow extension is missing: <case>/absent-workflow.ts (install pi-dynamic-workflows or set FM_PI_DYNAMIC_WORKFLOWS_EXTENSION to its absolute extensions/workflow.ts path) exit status: 1 task metadata state/evidence-pi-missing-a4.meta written? NO (no endpoint created) launch command typed? NO (nothing typed into a pane) 5. Relative override error: FM_PI_DYNAMIC_WORKFLOWS_EXTENSION must be an absolute path: extensions/workflow.ts exit status: 1 6. Firstmate Pi PRIMARY orchestrator launcher pi --no-extensions -e <repo>/.pi/extensions/fm-primary-turnend-guard.ts -e <repo>/.pi/extensions/fm-primary-pi-watch.ts --model test/model launch brief RESULT: OK - no pi-dynamic-workflows extension in the primary launch 8. Crewmate brief text the Pi crewmate actually reads 18:Pi crewmates may use the pi-dynamic-workflows workflow tool for multi-agent fan-out when it is loaded; secondmates are orchestrators and do not run it.


============================================================
1. Pi CREWMATE - loads the installed workflow.ts by absolute path after --no-extensions
============================================================
$ fm-spawn.sh evidence-pi-crewmate-a1 <project>
warn: no registry at /tmp/fm-role-split-evidence.mXZ1EY/crewmate/home/data/projects.md; defaulting project to no-mistakes off
spawned evidence-pi-crewmate-a1 harness=pi kind=ship mode=no-mistakes yolo=off window=firstmate:fm-evidence-pi-crewmate-a1 worktree=/tmp/fm-role-split-evidence.mXZ1EY/crewmate/wt

--- launch command typed into the crewmate pane ---
FM_PI_HARNESS=pi FM_PI_EXTENSION_ISOLATION=1 pi --no-extensions -e '/tmp/fm-role-split-evidence.mXZ1EY/crewmate/pi-dynamic-workflows/extensions/workflow.ts' -e '/tmp/fm-role-split-evidence.mXZ1EY/crewmate/home/state/evidence-pi-crewmate-a1.pi-ext.ts' "$('/home/dhajczuk/.no-mistakes/worktrees/31702870740f/01KYVNK2XKQXE49SBD0HZ1V0HE/bin/fm-operational-input.sh' encode launch-brief < '/tmp/fm-role-split-evidence.mXZ1EY/crewmate/home/data/evidence-pi-crewmate-a1/brief.md')"

============================================================
2. Pi SCOUT - same fan-out capability as a crewmate
============================================================
$ fm-spawn.sh evidence-pi-scout-a2 <project> --scout
warn: no registry at /tmp/fm-role-split-evidence.mXZ1EY/scout/home/data/projects.md; defaulting project to no-mistakes off
spawned evidence-pi-scout-a2 harness=pi kind=scout mode=no-mistakes yolo=off window=firstmate:fm-evidence-pi-scout-a2 worktree=/tmp/fm-role-split-evidence.mXZ1EY/scout/wt

--- launch command typed into the scout pane ---
FM_PI_HARNESS=pi FM_PI_EXTENSION_ISOLATION=1 pi --no-extensions -e '/tmp/fm-role-split-evidence.mXZ1EY/scout/pi-dynamic-workflows/extensions/workflow.ts' -e '/tmp/fm-role-split-evidence.mXZ1EY/scout/home/state/evidence-pi-scout-a2.pi-ext.ts' "$('/home/dhajczuk/.no-mistakes/worktrees/31702870740f/01KYVNK2XKQXE49SBD0HZ1V0HE/bin/fm-operational-input.sh' encode launch-brief < '/tmp/fm-role-split-evidence.mXZ1EY/scout/home/data/evidence-pi-scout-a2/brief.md')"

============================================================
3. Persistent Pi SECONDMATE (orchestrator) - workflow extension NEVER loaded
============================================================
$ fm-spawn.sh evidence-pi-secondmate-a3 <secondmate-home> --secondmate
warning: secondmate evidence-pi-secondmate-a3 sync skipped before launch: primary default-branch commit cannot be resolved
spawned evidence-pi-secondmate-a3 harness=pi kind=secondmate mode=secondmate yolo=off window=firstmate:fm-evidence-pi-secondmate-a3 worktree=/tmp/fm-role-split-evidence.mXZ1EY/secondmate/secondmate-home

--- launch command typed into the secondmate pane ---
FM_ROOT_OVERRIDE= FM_STATE_OVERRIDE= FM_DATA_OVERRIDE= FM_PROJECTS_OVERRIDE= FM_CONFIG_OVERRIDE= FM_HOME='/tmp/fm-role-split-evidence.mXZ1EY/secondmate/secondmate-home' FM_PI_HARNESS=pi FM_PI_EXTENSION_ISOLATION=1 pi --no-extensions -e '/tmp/fm-role-split-evidence.mXZ1EY/secondmate/secondmate-home/.pi/extensions/fm-primary-turnend-guard.ts' -e '/tmp/fm-role-split-evidence.mXZ1EY/secondmate/secondmate-home/.pi/extensions/fm-primary-pi-watch.ts' "$('/home/dhajczuk/.no-mistakes/worktrees/31702870740f/01KYVNK2XKQXE49SBD0HZ1V0HE/bin/fm-operational-input.sh' encode launch-brief < '/tmp/fm-role-split-evidence.mXZ1EY/secondmate/secondmate-home/data/charter.md')"

RESULT: OK - no pi-dynamic-workflows extension in the secondmate launch

============================================================
4. Missing workflow extension - crewmate REFUSED before endpoint creation
============================================================
$ FM_PI_DYNAMIC_WORKFLOWS_EXTENSION=/tmp/fm-role-split-evidence.mXZ1EY/missing/absent-workflow.ts fm-spawn.sh evidence-pi-missing-a4 <project>
error: pi crewmate/scout workflow extension is missing: /tmp/fm-role-split-evidence.mXZ1EY/missing/absent-workflow.ts (install pi-dynamic-workflows or set FM_PI_DYNAMIC_WORKFLOWS_EXTENSION to its absolute extensions/workflow.ts path)

exit status: 1
task metadata state/evidence-pi-missing-a4.meta written? NO (no endpoint created)
launch command typed?                 NO (nothing typed into a pane)

============================================================
5. Relative override path - refused as non-absolute
============================================================
$ FM_PI_DYNAMIC_WORKFLOWS_EXTENSION=extensions/workflow.ts fm-spawn.sh evidence-pi-relative-a5 <project>
error: FM_PI_DYNAMIC_WORKFLOWS_EXTENSION must be an absolute path: extensions/workflow.ts
exit status: 1

============================================================
6. Firstmate Pi PRIMARY orchestrator launcher - workflow extension NEVER loaded
============================================================
$ bin/fm-pi-primary.sh --model test/model "launch brief"

--- argv the Pi orchestrator process actually receives ---
pi --no-extensions -e /home/dhajczuk/.no-mistakes/worktrees/31702870740f/01KYVNK2XKQXE49SBD0HZ1V0HE/.pi/extensions/fm-primary-turnend-guard.ts -e /home/dhajczuk/.no-mistakes/worktrees/31702870740f/01KYVNK2XKQXE49SBD0HZ1V0HE/.pi/extensions/fm-primary-pi-watch.ts --model test/model launch brief

RESULT: OK - no pi-dynamic-workflows extension in the primary launch

============================================================
7. Tracked .pi/settings.json - plain Pi in the Firstmate repo excludes the package
============================================================
{
  "packages": [
    {
      "source": "https://github.com/QuintinShaw/pi-dynamic-workflows",
      "autoload": false,
      "extensions": ["!**"],
      "skills": ["!**"],
      "prompts": ["!**"],
      "themes": ["!**"]
    }
  ]
}

JSON valid: yes

============================================================
8. Crewmate brief text the Pi crewmate actually reads
============================================================
18:Pi crewmates may use the pi-dynamic-workflows workflow tool for multi-agent fan-out when it is loaded; secondmates are orchestrators and do not run it.
Evidence: Default extension-path resolution honors PI_CODING_AGENT_DIR (no env override)

$ PI_CODING_AGENT_DIR=<tmp>/pi-agent fm-spawn.sh evidence-default-path-a7 <project> # no FM_PI_DYNAMIC_WORKFLOWS_EXTENSION override spawned evidence-default-path-a7 harness=pi kind=ship ... --- launch command --- FM_PI_HARNESS=pi FM_PI_EXTENSION_ISOLATION=1 pi --no-extensions -e '<tmp>/pi-agent/git/github.com/QuintinShaw/pi-dynamic-workflows/extensions/workflow.ts' -e '<home>/state/evidence-default-path-a7.pi-ext.ts' ... RESULT: OK - default resolution used the installed package path

$ PI_CODING_AGENT_DIR=/tmp/fm-role-split-default.B3ph2A/pi-agent fm-spawn.sh evidence-default-path-a7 <project>   # no FM_PI_DYNAMIC_WORKFLOWS_EXTENSION override

warn: no registry at /tmp/fm-role-split-default.B3ph2A/home/data/projects.md; defaulting project to no-mistakes off
spawned evidence-default-path-a7 harness=pi kind=ship mode=no-mistakes yolo=off window=firstmate:fm-evidence-default-path-a7 worktree=/tmp/fm-role-split-default.B3ph2A/wt

--- launch command ---
FM_PI_HARNESS=pi FM_PI_EXTENSION_ISOLATION=1 pi --no-extensions -e '/tmp/fm-role-split-default.B3ph2A/pi-agent/git/github.com/QuintinShaw/pi-dynamic-workflows/extensions/workflow.ts' -e '/tmp/fm-role-split-default.B3ph2A/home/state/evidence-default-path-a7.pi-ext.ts' "$('/home/dhajczuk/.no-mistakes/worktrees/31702870740f/01KYVNK2XKQXE49SBD0HZ1V0HE/bin/fm-operational-input.sh' encode launch-brief < '/tmp/fm-role-split-default.B3ph2A/home/data/evidence-default-path-a7/brief.md')"

RESULT: OK - default resolution used the installed package path
Evidence: Evidence harness script (real fm-spawn/fm-pi-primary + fake tmux capture)
#!/usr/bin/env bash
# End-to-end evidence for the 2026-07-31 Pi workflow role split.
# Drives the real bin/fm-spawn.sh and bin/fm-pi-primary.sh with a fake tmux /
# fake pi so the literal launch command Firstmate would run is captured.
set -u

ROOT=${FM_REPO_ROOT:?set FM_REPO_ROOT to the firstmate worktree}
. "$ROOT/tests/lib.sh"

SPAWN="$ROOT/bin/fm-spawn.sh"
TMP_ROOT=$(fm_test_tmproot fm-role-split-evidence)

hr() { printf '\n============================================================\n%s\n============================================================\n' "$1"; }

make_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
        [ "$prev" = "-l" ] && printf '%s\n' "$a" >> "$FM_FAKE_LAUNCH_LOG"
        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_case() {
  local name=$1 harness=$2 id=$3 case_dir home proj wt fakebin
  case_dir="$TMP_ROOT/$name"
  home="$case_dir/home"; proj="$case_dir/project"; wt="$case_dir/wt"
  fakebin=$(make_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" >/dev/null 2>&1
  touch "$home/state/.last-watcher-beat"
  mkdir -p "$home/data/$id"
  printf 'brief for %s\n' "$id" > "$home/data/$id/brief.md"
  printf '%s\n' "$case_dir|$home|$proj|$wt|$fakebin|$case_dir/launch.log"
}

run_spawn() {
  local home=$1 wt=$2 fakebin=$3 launchlog=$4 ext=$5
  shift 5
  : > "$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="$ext" \
    FM_FAKE_LAUNCH_LOG="$launchlog" GROK_HOME="$home/grok-home" PATH="$fakebin:$PATH" \
    "$SPAWN" "$@" 2>&1
}

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

# ---------------------------------------------------------------- 1. crewmate
hr "1. Pi CREWMATE - loads the installed workflow.ts by absolute path after --no-extensions"
id=evidence-pi-crewmate-a1
read_case "$(make_case crewmate pi "$id")"
EXT="$CASE_DIR/pi-dynamic-workflows/extensions/workflow.ts"
printf '$ fm-spawn.sh %s <project>\n' "$id"
run_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" "$EXT" "$id" "$PROJ_DIR"
printf '\n--- launch command typed into the crewmate pane ---\n'
cat "$LAUNCH_LOG"

# ------------------------------------------------------------------- 2. scout
hr "2. Pi SCOUT - same fan-out capability as a crewmate"
id=evidence-pi-scout-a2
read_case "$(make_case scout pi "$id")"
EXT="$CASE_DIR/pi-dynamic-workflows/extensions/workflow.ts"
printf '$ fm-spawn.sh %s <project> --scout\n' "$id"
run_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" "$EXT" "$id" "$PROJ_DIR" --scout
printf '\n--- launch command typed into the scout pane ---\n'
cat "$LAUNCH_LOG"

# -------------------------------------------------------------- 3. secondmate
hr "3. Persistent Pi SECONDMATE (orchestrator) - workflow extension NEVER loaded"
id=evidence-pi-secondmate-a3
read_case "$(make_case secondmate codex "$id")"
EXT="$CASE_DIR/pi-dynamic-workflows/extensions/workflow.ts"
printf '%s\n' pi > "$HOME_DIR/config/secondmate-harness"
SM="$CASE_DIR/secondmate-home"
mkdir -p "$SM/bin" "$SM/data"
printf '# Firstmate\n' > "$SM/AGENTS.md"
printf '%s\n' "$id" > "$SM/.fm-secondmate-home"
printf 'charter for %s\n' "$id" > "$SM/data/charter.md"
SM=$(cd "$SM" && pwd -P)
printf '$ fm-spawn.sh %s <secondmate-home> --secondmate\n' "$id"
run_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" "$EXT" "$id" "$SM" --secondmate
printf '\n--- launch command typed into the secondmate pane ---\n'
cat "$LAUNCH_LOG"
if grep -q pi-dynamic-workflows "$LAUNCH_LOG"; then
  printf '\nRESULT: FAIL - secondmate loaded the workflow extension\n'
else
  printf '\nRESULT: OK - no pi-dynamic-workflows extension in the secondmate launch\n'
fi

# ---------------------------------------------------------------- 4. refusal
hr "4. Missing workflow extension - crewmate REFUSED before endpoint creation"
id=evidence-pi-missing-a4
read_case "$(make_case missing pi "$id")"
printf '$ FM_PI_DYNAMIC_WORKFLOWS_EXTENSION=%s/absent-workflow.ts fm-spawn.sh %s <project>\n' "$CASE_DIR" "$id"
out=$(run_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" "$CASE_DIR/absent-workflow.ts" "$id" "$PROJ_DIR")
status=$?
printf '%s\n' "$out"
printf '\nexit status: %s\n' "$status"
printf 'task metadata state/%s.meta written? %s\n' "$id" \
  "$([ -e "$HOME_DIR/state/$id.meta" ] && echo YES || echo 'NO (no endpoint created)')"
printf 'launch command typed?                 %s\n' \
  "$([ -s "$LAUNCH_LOG" ] && echo YES || echo 'NO (nothing typed into a pane)')"

hr "5. Relative override path - refused as non-absolute"
id=evidence-pi-relative-a5
read_case "$(make_case relative pi "$id")"
printf '$ FM_PI_DYNAMIC_WORKFLOWS_EXTENSION=extensions/workflow.ts fm-spawn.sh %s <project>\n' "$id"
run_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" "extensions/workflow.ts" "$id" "$PROJ_DIR"
printf 'exit status: %s\n' "$?"

# ------------------------------------------------------------- 6. primary
hr "6. Firstmate Pi PRIMARY orchestrator launcher - workflow extension NEVER loaded"
PRIMARY_DIR="$TMP_ROOT/primary"
PBIN=$(fm_fakebin "$PRIMARY_DIR")
cat > "$PBIN/pi" <<'SH'
#!/usr/bin/env bash
printf 'pi'; for a in "$@"; do printf ' %s' "$a"; done; printf '\n'
SH
chmod +x "$PBIN/pi"
printf '$ bin/fm-pi-primary.sh --model test/model "launch brief"\n'
printf '\n--- argv the Pi orchestrator process actually receives ---\n'
PATH="$PBIN:$PATH" bash "$ROOT/bin/fm-pi-primary.sh" --model test/model "launch brief" | tee "$TMP_ROOT/primary.out"
if grep -q pi-dynamic-workflows "$TMP_ROOT/primary.out"; then
  printf '\nRESULT: FAIL - primary launcher loaded the workflow extension\n'
else
  printf '\nRESULT: OK - no pi-dynamic-workflows extension in the primary launch\n'
fi

# ------------------------------------------------------- 7. project override
hr "7. Tracked .pi/settings.json - plain Pi in the Firstmate repo excludes the package"
cat "$ROOT/.pi/settings.json"
printf '\nJSON valid: '
python3 -c "import json,sys; json.load(open('$ROOT/.pi/settings.json')); print('yes')"

# ---------------------------------------------------------------- 8. brief
hr "8. Crewmate brief text the Pi crewmate actually reads"
BHOME="$TMP_ROOT/brief-home"
mkdir -p "$BHOME/data"
FM_HOME="$BHOME" FM_DATA_OVERRIDE="$BHOME/data" bash "$ROOT/bin/fm-brief.sh" evidence-brief-a6 demo-repo >/dev/null 2>&1
grep -n "pi-dynamic-workflows" "$BHOME/data/evidence-brief-a6/brief.md"

printf '\n'
Evidence: Default-path evidence harness script
#!/usr/bin/env bash
# Verify the DEFAULT (unset FM_PI_DYNAMIC_WORKFLOWS_EXTENSION) resolution of the
# installed pi-dynamic-workflows extension honours PI_CODING_AGENT_DIR.
set -u
ROOT=${FM_REPO_ROOT:?}
. "$ROOT/tests/lib.sh"
TMP=$(fm_test_tmproot fm-role-split-default)
SPAWN="$ROOT/bin/fm-spawn.sh"

AGENT_DIR="$TMP/pi-agent"
PKG="$AGENT_DIR/git/github.com/QuintinShaw/pi-dynamic-workflows/extensions"
mkdir -p "$PKG"; : > "$PKG/workflow.ts"

home="$TMP/home"; proj="$TMP/project"; wt="$TMP/wt"; log="$TMP/launch.log"
fakebin=$(fm_fakebin "$TMP/fake")
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|has-session|new-session|new-window|kill-window) exit 0 ;;
  send-keys) prev=; for a in "$@"; do [ "$prev" = "-l" ] && printf '%s\n' "$a" >> "$FM_FAKE_LAUNCH_LOG"; prev=$a; done; exit 0 ;;
esac
exit 0
SH
chmod +x "$fakebin/tmux"; fm_fake_exit0 "$fakebin" treehouse
mkdir -p "$home/data" "$home/projects" "$home/state" "$home/config"
printf 'pi\n' > "$home/config/crew-harness"
fm_git_worktree "$proj" "$wt" wt-default >/dev/null 2>&1
touch "$home/state/.last-watcher-beat"
id=evidence-default-path-a7
mkdir -p "$home/data/$id"; printf 'brief\n' > "$home/data/$id/brief.md"
: > "$log"

printf '$ PI_CODING_AGENT_DIR=%s fm-spawn.sh %s <project>   # no FM_PI_DYNAMIC_WORKFLOWS_EXTENSION override\n\n' "$AGENT_DIR" "$id"
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='' \
  PI_CODING_AGENT_DIR="$AGENT_DIR" FM_FAKE_LAUNCH_LOG="$log" PATH="$fakebin:$PATH" \
  "$SPAWN" "$id" "$proj" 2>&1
printf '\n--- launch command ---\n'
cat "$log"
grep -q "$PKG/workflow.ts" "$log" && printf '\nRESULT: OK - default resolution used the installed package path\n' \
  || printf '\nRESULT: FAIL - default path not used\n'

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 3 infos
  • ⚠️ .pi/settings.json:4 - The new tracked project settings declare a git package. Pi's packages.md states "pi installs any missing packages automatically on startup after the project is trusted", and project-scoped git packages clone to .pi/git/&lt;host&gt;/&lt;path&gt; inside the repo. .gitignore covers neither .pi/git/ nor .pi/npm/. On any machine where pi-dynamic-workflows is not already installed globally (a fresh clone, a secondmate home, a treehouse worktree), a plain pi session in the repo can leave an untracked .pi/git/github.com/QuintinShaw/pi-dynamic-workflows/ tree behind. Untracked entries count as dirty for bin/fm-ff-lib.sh:228 and bin/fm-merge-local.sh:63, which then refuse the fast-forward/merge. Add .pi/git/ and .pi/npm/ to .gitignore.
  • ℹ️ bin/fm-spawn.sh:699 - [ &#34;$KIND&#34; != secondmate ] is redundant with the *__PIWORKFLOW__* placeholder test on the same line: only the non-secondmate branch of launch_template (line 641) emits __PIWORKFLOW__, and that branch is selected by the same $KIND. The placeholder check alone is the authoritative and simpler condition. Noting it as an optional simplification, not a defect.
  • ℹ️ bin/fm-spawn.sh:607 - The refusal reads "pi crewmate workflow extension is missing", but the same guard gates --scout spawns (scouts take the non-secondmate template and therefore carry __PIWORKFLOW__). An operator whose scout spawn is refused sees a message naming the wrong kind. Widening it to "pi crewmate/scout" keeps the operator-facing text accurate.

🔧 Fix: ignore project pi package installs; name scouts in refusal
3 infos still open:

  • ℹ️ docs/configuration.md:384 - FM_PI_DYNAMIC_WORKFLOWS_EXTENSION is a new operator-facing override that decides whether a Pi crewmate/scout can spawn at all, and the refusal message at bin/fm-spawn.sh:607 names it as the remedy ("set FM_PI_DYNAMIC_WORKFLOWS_EXTENSION to its absolute extensions/workflow.ts path"). It is documented only in the bin/fm-spawn.sh header comment, not in the ## Environment variables reference block in docs/configuration.md, which lists dozens of comparable FM_* knobs with their defaults (FM_BACKEND, FM_PI_ARM_READY_TIMEOUT_MS, GROK_HOME, ...). An operator whose spawn is refused has no doc entry to look up. Add a line to that block, e.g. FM_PI_DYNAMIC_WORKFLOWS_EXTENSION= # absolute path to the installed pi-dynamic-workflows extensions/workflow.ts for Pi crewmate/scout launches; defaults to $PI_CODING_AGENT_DIR (then ~/.pi/agent)/git/github.com/QuintinShaw/pi-dynamic-workflows/extensions/workflow.ts.
  • ℹ️ tests/fm-spawn-dispatch-profile.test.sh:509 - The user intent explicitly requires that "Pi crewmates and scouts must load the installed extensions/workflow.ts by absolute path after --no-extensions", and the round-1 fix instruction specifically asked the refusal diagnostic to name scouts. The new launch-template coverage is: pi ship crewmate positive (line 500), pi-signed secondmate negative (line 588), and a missing-extension refusal on a ship spawn (line 509). No test drives --scout through the Pi template, so a future change that gives scouts their own launch_template branch (the function already switches on $kind) would silently drop the workflow extension for scouts with a green suite. Add a --scout variant asserting -e &#39;&lt;workflow.ts&gt;&#39; -e &#39;&lt;...pi-ext.ts&gt;&#39; in the launch command.
  • ℹ️ .pi/settings.json:4 - Verified residual behavior, noted as a tradeoff rather than a defect. In pi 0.82 resolvePackageSources, findAutoloadDeltaBase returns undefined when no matching user-scoped entry exists, so resolvedScope stays "project" and pi clones the repo plus runs npm install into .pi/git/github.com/QuintinShaw/pi-dynamic-workflows purely to then disable every resource via applyPackageDeltaFilter. Impact is bounded: it is gitignored by the new .gitignore entries, it is gated behind project trust (untrusted projects load projectSettings = {}, so fresh crewmate worktrees never trigger it), and it only affects a trusted Firstmate checkout on a machine lacking the global package. A no-install alternative exists if it ever matters - a top-level project extensions/skills/prompts/themes override in .pi/settings.json (what pi config -l writes, and the same !** idiom the global settings already use) disables inherited package resources without declaring the package source. No action needed for the captain's own machine, where the global entry is present and the delta resolves to the existing ~/.pi/agent/git/... install.
✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-spawn-dispatch-profile.test.sh — 27 tests pass, including the new test_pi_crewmate_missing_workflow_extension_refuses_before_endpoint and the updated pi crewmate / pi-signed secondmate launch-template assertions
  • bash tests/fm-pi-watch-extension.test.sh — passes, including the new assertion that the Pi primary launcher never loads pi-dynamic-workflows
  • bash tests/fm-brief.test.sh — passes, including the new crewmate-brief role-split line assertion
  • bash tests/fm-gitignore-config.test.sh — passes with the added .pi/git/ and .pi/npm/ ignore entries
  • bash tests/fm-documentation-audiences.test.sh — passes for the edited docs/configuration.md
  • Manual end-to-end harness /tmp/no-mistakes-evidence/01KYVNK2XKQXE49SBD0HZ1V0HE/role-split-evidence.sh driving real bin/fm-spawn.sh with a fake tmux that captures the literal send-keys -l launch command for a Pi crewmate, a Pi scout, and a persistent Pi secondmate
  • Manual refusal checks: FM_PI_DYNAMIC_WORKFLOWS_EXTENSION=&lt;absent&gt;.ts fm-spawn.sh &lt;id&gt; &lt;project&gt; and FM_PI_DYNAMIC_WORKFLOWS_EXTENSION=extensions/workflow.ts fm-spawn.sh &lt;id&gt; &lt;project&gt; — both exit 1, write no state/&lt;id&gt;.meta, and type no launch command
  • Manual orchestrator check: bash bin/fm-pi-primary.sh --model test/model &#34;launch brief&#34; against a fake pi that prints its argv
  • Manual default-path check /tmp/no-mistakes-evidence/01KYVNK2XKQXE49SBD0HZ1V0HE/default-path-check.sh — spawn with PI_CODING_AGENT_DIR set and no FM_PI_DYNAMIC_WORKFLOWS_EXTENSION override resolves the installed package path
  • Manual config check: python3 -c &#34;json.load(open(&#39;.pi/settings.json&#39;))&#34; plus rendering the tracked exclusion filter
  • Manual brief render: bin/fm-brief.sh evidence-brief-a6 demo-repo then grep for the pi-dynamic-workflows role-split line
  • git status --porcelain — worktree clean, no transient test artifacts left behind
⚠️ **Document** - 1 info
  • ℹ️ docs/configuration.md:264 - Pi crewmate/scout spawns now hard-require the installed pi-dynamic-workflows package, but bootstrap's toolchain detection does not check for it, so an operator only learns of the requirement at the spawn refusal. I documented the path and override in the Environment variables section rather than in docs/configuration.md "Toolchain", because that section is the single owner of the bootstrap-detected toolchain and listing an undetected requirement there would imply detection that does not exist. If the requirement should become discoverable at session start, the follow-up is a bootstrap check plus a Toolchain entry, not a docs-only edit.
✅ **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 1300bde into main Jul 31, 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