feat(bin): load pi-dynamic-workflows for Pi crewmates and scouts only - #4
Merged
Merged
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.shnow resolves the installedpi-dynamic-workflowsextensions/workflow.tsby 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.tsand can be overridden withFM_PI_DYNAMIC_WORKFLOWS_EXTENSION; a relative or missing path refuses the crewmate/scout spawn before the endpoint is created. The Pi secondmate template andbin/fm-pi-primary.share left as orchestrators that never load the extension..pi/settings.jsonapplies 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.gitignorenow covers.pi/git/and.pi/npm/so project-scoped package installs cannot dirty the worktree.bin/fm-brief.shadds a role-split line to the crewmate brief.docs/configuration.mddocuments the project-settings filter, the crewmate/scout absolute-path load and its pre-endpoint refusal, and the newFM_PI_DYNAMIC_WORKFLOWS_EXTENSIONenvironment variable.tests/fm-spawn-dispatch-profile.test.shgains the missing-extension refusal case plus updated crewmate/secondmate launch-template assertions, with matching assertions intests/fm-pi-watch-extension.test.shandtests/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: falsedelta actually disables the package, and--no-extensionsplus explicit-eis 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.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 pathEvidence: Evidence harness script (real fm-spawn/fm-pi-primary + fake tmux capture)
Evidence: Default-path evidence harness script
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
.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/<host>/<path>inside the repo..gitignorecovers 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 plainpisession in the repo can leave an untracked.pi/git/github.com/QuintinShaw/pi-dynamic-workflows/tree behind. Untracked entries count as dirty forbin/fm-ff-lib.sh:228andbin/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-[ "$KIND" != secondmate ]is redundant with the*__PIWORKFLOW__*placeholder test on the same line: only the non-secondmate branch oflaunch_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--scoutspawns (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_EXTENSIONis a new operator-facing override that decides whether a Pi crewmate/scout can spawn at all, and the refusal message atbin/fm-spawn.sh:607names it as the remedy ("set FM_PI_DYNAMIC_WORKFLOWS_EXTENSION to its absolute extensions/workflow.ts path"). It is documented only in thebin/fm-spawn.shheader comment, not in the## Environment variablesreference 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--scoutthrough the Pi template, so a future change that gives scouts their ownlaunch_templatebranch (the function already switches on$kind) would silently drop the workflow extension for scouts with a green suite. Add a--scoutvariant asserting-e '<workflow.ts>' -e '<...pi-ext.ts>'in the launch command..pi/settings.json:4- Verified residual behavior, noted as a tradeoff rather than a defect. In pi 0.82resolvePackageSources,findAutoloadDeltaBasereturns undefined when no matching user-scoped entry exists, soresolvedScopestays "project" and pi clones the repo plus runsnpm installinto.pi/git/github.com/QuintinShaw/pi-dynamic-workflowspurely to then disable every resource viaapplyPackageDeltaFilter. Impact is bounded: it is gitignored by the new.gitignoreentries, it is gated behind project trust (untrusted projects loadprojectSettings = {}, 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 projectextensions/skills/prompts/themesoverride in.pi/settings.json(whatpi config -lwrites, 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 newtest_pi_crewmate_missing_workflow_extension_refuses_before_endpointand the updated pi crewmate / pi-signed secondmate launch-template assertionsbash tests/fm-pi-watch-extension.test.sh— passes, including the new assertion that the Pi primary launcher never loads pi-dynamic-workflowsbash tests/fm-brief.test.sh— passes, including the new crewmate-brief role-split line assertionbash tests/fm-gitignore-config.test.sh— passes with the added.pi/git/and.pi/npm/ignore entriesbash tests/fm-documentation-audiences.test.sh— passes for the editeddocs/configuration.mdManual end-to-end harness/tmp/no-mistakes-evidence/01KYVNK2XKQXE49SBD0HZ1V0HE/role-split-evidence.shdriving realbin/fm-spawn.shwith a fake tmux that captures the literalsend-keys -llaunch command for a Pi crewmate, a Pi scout, and a persistent Pi secondmateManual refusal checks:FM_PI_DYNAMIC_WORKFLOWS_EXTENSION=<absent>.ts fm-spawn.sh <id> <project>andFM_PI_DYNAMIC_WORKFLOWS_EXTENSION=extensions/workflow.ts fm-spawn.sh <id> <project>— both exit 1, write nostate/<id>.meta, and type no launch commandManual orchestrator check:bash bin/fm-pi-primary.sh --model test/model "launch brief"against a fakepithat prints its argvManual default-path check/tmp/no-mistakes-evidence/01KYVNK2XKQXE49SBD0HZ1V0HE/default-path-check.sh— spawn withPI_CODING_AGENT_DIRset and noFM_PI_DYNAMIC_WORKFLOWS_EXTENSIONoverride resolves the installed package pathManual config check:python3 -c "json.load(open('.pi/settings.json'))"plus rendering the tracked exclusion filterManual brief render:bin/fm-brief.sh evidence-brief-a6 demo-repothen grep for the pi-dynamic-workflows role-split linegit status --porcelain— worktree clean, no transient test artifacts left behinddocs/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.