feat(bin): mark crewmate and scout steers as from-firstmate and teach briefs to read the marker - #9
Merged
Merged
Conversation
* feat: add verified pi-signed adapter * no-mistakes(review): Correct pi-signed maintainer verification date * no-mistakes(review): Correct remaining pi-signed verification dates * no-mistakes(review): Preserve authoritative pi-signed runtime identity * no-mistakes(document): Document pi-signed shared adapter semantics * no-mistakes: apply CI fixes
* fix(pi): rearm watcher across same-process session transitions Pi emits session_shutdown for ordinary /new, /resume, and /fork replacement as well as terminal quit. The primary watcher extension latched a module-level stopping flag on every shutdown, so a replacement session in the same process could not arm monitoring until Pi restarted. Own arm authority per session generation so only the active live generation may start, stop, or rearm the child. Replacement sessions can arm again without restarting Pi, stale prior-generation callbacks cannot mutate the active cycle, and real quit still blocks late rearm. * no-mistakes(review): Preserve Pi generation isolation and exit cleanup * no-mistakes(document): Correct Pi watcher transition documentation
* Consume quota-axi pace signals in dispatch profile array selection. Add quota-array-dispatch as the single owner of the pace-aware candidate choice, keep AGENTS.md to the intake boundary and load trigger, and cover the acceptance cases with sanitized schemaVersion 3 fixtures. * no-mistakes(review): Stop and report genuine quota dispatch ties * no-mistakes(document): Document quota pace freshness and uncertainty
…nguid#1171) * fix(grok): adapt Stop continuation to runtime capability * no-mistakes(review): Reject ambiguous Grok Stop payloads * no-mistakes(review): Reject duplicate Grok fields and accept spaced tmux sessions * no-mistakes(review): Enforce exact tmux cleanup selectors * no-mistakes(test): Fix historical tmux fixture and validate Grok Stop * no-mistakes: apply CI fixes
* fix(brief): make DOD scaffolding parse-safe on stock macOS Bash 3.2 fm-brief.sh built each Definition-of-done block and the not-enabled Herdr declaration with `VAR=$(cat <<EOF ... EOF)`. On Bash 3.2 (macOS /bin/bash) the lexer scans for the command substitution's closing `)` textually and tracks quote state through the heredoc body, so a single apostrophe, unbalanced quote, or unbalanced paren in that prose breaks parsing of the whole script. Every ship-brief scaffold (no-mistakes, direct-PR, local-only) failed with `unexpected EOF while looking for matching )`. Bash 4+ parses it fine, so the breakage stayed invisible everywhere except stock macOS. Replace all four command-substitution heredocs with `IFS= read -r -d '' VAR <<EOF || true`. That removes the `$(...)` wrapper and the entire defect class regardless of future prose, and preserves the variable expansion the direct-PR and local-only bodies need. `read` keeps the heredoc's trailing newline that `$(...)` used to strip, so trim one newline to keep every generated brief byte-identical to prior output. Guard the structure, not one historical phrase: a new test rejects any heredoc nested in a command substitution anywhere in fm-brief.sh, where the old assertion pinned a single apostrophe phrase and so missed the reintroduction. Extend the stock-macOS Bash CI job from parsing one script to the whole maintained shell surface (bin/*.sh, bin/backends/*.sh, tests/*.sh), matching bin/fm-lint.sh's canonical file set so parse scope and lint scope cannot drift apart. * no-mistakes(review): Captain: harden Bash structure and inventory guards * no-mistakes(document): Align stock macOS Bash contributor checks * no-mistakes(lint): Suppress deliberate SC2016 literal fixture warnings
* fix(test): pin teardown tmux baseline to historical kill selectors merge-base HEAD main collapses to HEAD after the exact-selector change lands on the default branch, so the old teardown fixture was accidentally exercising current exact targets. Resolve a content-historical permissive tmux adapter from first-parent history and force that post-squash topology inside the conformance case so main and feature branches keep the same old-vs-new contract. * no-mistakes(lint): Suppress intentional literal-pattern ShellCheck warnings
…id#1197) Cut the runtime skill to the compact pace-aware selection procedure plus minimum owner pointers. Keep every distinct decision rule and move expanded acceptance scenarios to deterministic fixture ownership assertions. Size: 170/1374/10187 -> 63/544/4068 (about 63%/60%/60% reduction).
…1219) * Inherit config/backend into secondmate homes with deliberate-override preservation Add backend to the shared inheritable config allowlist so launch, locked bootstrap, and config-push converge a primary pin into secondmate homes as each home local future-spawn default. Track last-inherited bytes in a private state provenance marker so deliberate per-home overrides survive present and absent primary convergence, keep --backend and FM_BACKEND stronger, and extend the existing inheritance tests plus docs and skill claims. * no-mistakes(review): Preserve equal unprovenanced backend overrides * no-mistakes(review): Preserve symlink overrides and verify spawn precedence * no-mistakes(review): Snapshot backend inheritance for consistent provenance * no-mistakes(review): Simplify backend inheritance to primary-authoritative convergence * no-mistakes(document): Document inherited backend override preservation * fix: restore primary-authoritative backend inheritance after document regression The document step reintroduced provenance and deliberate per-home override semantics after review had simplified config/backend to plain primary-authoritative allowlist membership. Restore the primary-always-wins path: present overwrites, absent removes, no provenance marker, and docs/tests match that contract. * no-mistakes(review): Add divergent backend precedence regression fixtures * no-mistakes(document): Document backend inheritance contract
* fix(pi): remove Calm's exclusive Pi upper-version ceiling
tests/fm-calm-pi-extension.test.sh gated on a closed PI_COMPAT_VERSIONS
allowlist ("0.81.1 0.82.0") that refused any other installed Pi, and docs
described that range as "supported" rather than verified evidence. The
Calm CHANGELOG shows no API introduced at either version, so there is no
evidence for a real minimum; the presentation adapters already probe the
exact method they patch rather than checking a version.
Replace the allowlist with dated version evidence that never rejects a
newer Pi, and make each presentation adapter degrade independently with
a diagnostic if a future Pi removes its API, instead of the whole Calm
extension failing to load. Rewrite the feasibility doc's "Pi 0.81.1
through 0.82.0" phrasing to state it as verified evidence, not a
ceiling.
* no-mistakes(review): Probe missing Calm adapter exports safely
* no-mistakes(document): Document Calm's unbounded Pi compatibility
…enguid#1204) * fix(guard): allow session-local todo tools in the primary The delegation-shape guard denied TaskCreate and TaskUpdate because their normalized names contain the `task` stem. Those tools write only the harness's session-local todo list, which has no executor: it spawns no agent, allocates no worktree, registers no schedule, and starts nothing that outlives the session. That is not the unaccounted work the guard exists to stop, so the stem match was a false positive, and the deny text told the primary to run bin/fm-brief.sh and bin/fm-spawn.sh to create a todo entry. Add a separately-reasoned PLAN_ONLY_TOOLS exact-name exclusion rather than widening OBSERVE_ONLY_TOOLS, whose documented contract is tools that only observe or stop existing work. Both lists stay exact-name so neither can widen by substring. Tests cover the two allowed names and six near-miss names that a substring or shortened-stem widening would release; both mutations were watched red. * no-mistakes(review): drop session-local todo tools from recommended deny list * no-mistakes: apply CI fixes
…claude pid (kunchenguid#1206) * fix(session-lock): resolve Claude bg-spare ancestry to the outermost claude pid fm_harness_ancestry_pid() previously returned the first ancestor process whose command matched a verified harness name. Claude Code's Stop hook fires as a bg-spare worker several levels below the session's actual lock-owning claude process (hook shell -> claude bg-spare -> claude bg-pty-host -> claude -> claude(lock)), so the first match was the bg-spare worker, not the lock owner. fm_session_lock_owned_by_self() then never matched state/.lock, and the Claude Stop auto-arm silently treated its own primary session as an unrelated live owner and never armed the watcher. The walk now keeps going past a claude-named match, looking for a still more ancestral claude-named match, and stops the instant a non-match follows an already-found match (bounding it to a contiguous run rather than the literal ancestry top, so an unrelated claude-named process further up the real process tree is never mistaken for part of this session's own nested chain). Every other harness keeps the original first-match-wins behavior, since e.g. Pi's shared signed-wrapper ancestry actually holds the session at the inner engine pid, not an outer wrapper pid. Hop limit raised from 8 to 16 to cover the deeper bg-spare chain. * no-mistakes(review): Add nested-claude-ancestry regression test; fix nudge doc depth claim * no-mistakes: apply CI fixes
* fix: confirm watcher startup on MSYS * no-mistakes(review): gate MSYS arm ready timeout, cache uname, harden locale test * no-mistakes(review): validate OpenCode ready timeout, make uname cache internal
…d#1195) * fix(spawn): forward firstmate's CLAUDE_CONFIG_DIR to claude crewmates Crewmate panes are created by a long-lived tmux/herdr daemon that does not inherit firstmate's current environment. When firstmate runs under a non-default CLAUDE_CONFIG_DIR (for example a work-vs-personal subscription split), a bare `claude` in the crewmate pane fell back to the default ~/.claude store and launched unauthenticated, blocking the crewmate before it could do any work. fm-spawn now prefixes the claude launch with firstmate's own resolved CLAUDE_CONFIG_DIR when set, so the crewmate uses the same credential/config store firstmate is authenticated with. An unset value is the single-store default and adds no prefix; non-claude harnesses are unaffected. Adds three tests in fm-spawn-dispatch-profile.test.sh (forwarded-when-set, omitted-when-unset, non-claude-ignored) and pins CLAUDE_CONFIG_DIR in the test helper so launch assertions no longer depend on the developer's environment. * no-mistakes: apply CI fixes
…guid#1233) * fix: preserve dispatch harness identity * no-mistakes(review): Fix Grok counterfactual tuple validation * no-mistakes(document): Scope dispatch authentication to selected tuple * fix: restore dispatch instruction budget * no-mistakes(review): Scope dispatch authentication after candidate selection
* fix(bin): handle dash-leading harness process names (#2) * fix: handle dash-leading harness process names * no-mistakes(review): Make dash-leading harness regression hermetic * fix: preserve secondmate reply routes across relative homes Resolve relative home, data, and state inputs before durable charter generation, and fail when caller-relative directories cannot be resolved. Use absolute paths at the related spawn, AFK daemon, and X-mode cross-process handoffs so later processes cannot reinterpret them from another working directory. * no-mistakes(review): Preserve absolute overrides and normalize relative durable paths * no-mistakes(review): Normalize relative home before deriving durable paths * no-mistakes(document): Document relative durable-path normalization * no-mistakes(review): Captain: Ignore inherited CDPATH during relative path normalization * no-mistakes(lint): Fix empty CDPATH assignments for ShellCheck
* Add internal status skill * no-mistakes(document): register /status skill in documentation-audiences inventory * no-mistakes(lint): replace grep|wc -l with grep -c in status skill test * test: silence literal status skill patterns * Refactor bearings default to chat-only --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
* docs: add captain-approved project operation exception to hard rule 1 Firstmate stays read-only over projects by default, but when the captain clearly approves a concrete project operation and scope in the moment, firstmate may perform exactly that approved operation with its own tools. The approval is never inferred, broadened, or standing, and it does not relax the existing force, discard, unlanded-work, or merge-authority boundaries. * no-mistakes(review): Clarify captain-approved project operation boundaries * no-mistakes(document): Clarify captain-approved project operation scope * docs: cover directories and preserve the operation-or-scope alternative Widen the captain-approved project operation exception in AGENTS.md to files or directories, and restore the explicit operation-or-scope alternative that a prior pipeline auto-fix had collapsed into "and". Rework project-management SKILL.md's Remove section, which previously told firstmate to refuse project removal until a guarded helper existed; that helper was never built, so the text directly contradicted the new instruction-only exception. It now points at the exception plus the existing removal preflight it still requires unchanged. Update the one instruction-owners test assertion that hard-coded the sentence removed above, so the suite tracks current, not obsolete, text. * docs: add captain-approved project operation exception to hard rule 1 Firstmate stays read-only over projects by default, but when the captain clearly approves a concrete project operation and scope in the moment, firstmate may perform exactly that approved operation with its own tools. The approval is never inferred, broadened, or standing, and it does not relax the existing force, discard, unlanded-work, or merge-authority boundaries. * no-mistakes(review): Clarify captain-approved project operation boundaries * no-mistakes(document): Clarify captain-approved project operation scope * docs: cover directories and preserve the operation-or-scope alternative Widen the captain-approved project operation exception in AGENTS.md to files or directories, and restore the explicit operation-or-scope alternative that a prior pipeline auto-fix had collapsed into "and". Rework project-management SKILL.md's Remove section, which previously told firstmate to refuse project removal until a guarded helper existed; that helper was never built, so the text directly contradicted the new instruction-only exception. It now points at the exception plus the existing removal preflight it still requires unchanged. Update the one instruction-owners test assertion that hard-coded the sentence removed above, so the suite tracks current, not obsolete, text. * no-mistakes(review): Align project removal preflight with approved exception * no-mistakes(document): Align project removal documentation with approved exception * fix: restore removal test byte-for-byte and preserve the default sentence tests/fm-instruction-owners.test.sh had been changed to assert different text; restore it byte-for-byte to origin/main. project-management SKILL.md's Remove section now keeps the exact default "Never issue a raw removal command from Firstmate." sentence that test still asserts, immediately followed by the already-approved captain-operation-or-scope exception, so the default and the exception both stay explicit and consistent. * no-mistakes(document): Align project-write boundary documentation
…henguid#1275) * Route project intake through secondmate scopes * no-mistakes(test): Guard all main-home project registry mutations * no-mistakes(document): Consolidate secondmate routing documentation * no-mistakes: apply CI fixes * Restore new-project routing scope * no-mistakes(document): Clarify secondmate routing for new-project intake * no-mistakes: apply CI fixes
…#1282) * test: remove source-content assertions * no-mistakes(review): Replace source assertions with runtime behavior coverage * no-mistakes(review): Isolate Kimi task temp runtime coverage * no-mistakes(document): Refresh test cleanup documentation * no-mistakes: apply CI fixes
…#1286) * fix(watch): bound how long a busy pane may run with no completed turn A busy pane (backend busy state or the harness's rendered footer) was unconditional, unbounded proof of liveness in every escalation path, so a hung foreground tool call behind a busy signature could run for hours undetected (2026-07 hibit-agent-focus-nonsteal-r1 incident: a catastrophic- backtracking regex hung one bash call for 25h behind an unchanging "Working..." footer). FM_BUSY_TURN_MAX_SECS (default 3600s) now bounds how long a busy pane may run with no completed turn (state/<id>.turn-ended, or its spawn record before any turn has completed). Past the bound, busy_turn_over_age routes the pane through the existing wedge_timer_check, reusing the identical stale reason, escalation counter, and demand-deep-inspection marker for human inspection only - never an automatic interrupt, signal, or restart of the worker or its tool process. A completed turn resets the age. Reproduced end-to-end against the real installed Pi TUI: a foreground `sleep 999999` bash call with no timeout renders the actual busy footer, and two captures ~15s apart show the elapsed counter changing the pane hash while the same turn stays unfinished. Running the pre-fix watcher against the real captures showed it never starts a wedge timer no matter how long the pane stays busy; the fixed watcher starts and escalates the timer through the same mechanism, while the real hung process remained untouched and alive throughout. * no-mistakes(review): fix: parse enriched AFK stale reasons * no-mistakes(review): fix: preserve enriched wedges during AFK supervision * no-mistakes(review): fix: route all enriched AFK wedges * no-mistakes(document): Clarify busy-turn age supervision documentation
…unchenguid#1261) A name-by-name list of config/ entries silently stops ignoring any new or home-local file placed there, which makes the working tree read as dirty and blocks guarded sync paths that refuse to touch a dirty home. AGENTS.md already documents config/ as captain-private and gitignored as a category; this makes .gitignore match that contract.
…al coverage (kunchenguid#1304) The second assertion in fm-gitignore-config.test.sh (added by kunchenguid#1261) greps .gitignore for a specific spelling of the config/ ignore pattern. It fails on a semantically equivalent pattern like config/** and does not prove Git actually ignores anything, per the completed source-content-test audit. Replace it with a real git check-ignore control test on a generated unrelated path, and strengthen the existing directory-coverage test with generated unpredictable direct and nested config/ paths.
A steer lands in the receiving agent's own chat, where nothing else told firstmate's instructions apart from a human typing into that pane. The gap was proven in both directions on 2026-07-26: the captain opened a crewmate pane believing it was firstmate and issued cross-lane instructions there, and a Pi crewmate at an ask-user gate addressed "Captain, ..." into its own pane and sat parked - nobody reads a crewmate pane, and a parked pipeline emits no wake, so that direction fails silently. AGENTS.md section 1 rule 4 already required workers to honor a distinction the system gave them no means to make. fm-send now applies the existing from-firstmate carrier to every text steer whose target resolves through this home's meta, not just kind=secondmate. A crewmate or scout carries the marker alone; the corr= correlation token and the parent pending-reply record stay secondmate-only, because a crewmate already answers on its own status file. Explicit backend targets and the --key path are unchanged. Command-shaped text is the one exclusion. A harness recognizes a slash command, or a codex $<skill> invocation, only at the very start of the composer line, so any prefix silently demotes it to prose. Verified on claude 2.1.220 and pi 0.82.0: with either marker shape prepended, /no-mistakes stops opening the completion popup entirely and would submit as ordinary text. Crewmate sends of that shape therefore stay unmarked and byte-identical, which also keeps every documented popup hazard out of this change's blast radius: the only bytes that move are plain text no harness parses specially. The exclusion deliberately does not reach a secondmate, whose marker is what creates its reply guarantee. The ship and scout scaffolds gain a "Who is speaking to you" section teaching the reader side: marked is firstmate, unmarked is a human who may believe the pane is firstmate, self-identify as a worker on this task before acting, and escalation is always the status file. AGENTS.md states the provenance principle once in rule 4; the away-mode stub and the secondmate charter keep their own distinct consequences.
… to fm-send predicate
sbracewell64
force-pushed
the
fm/pi-crewmate-addresses-captain
branch
from
July 30, 2026 16:54
137c982 to
ef36a7c
Compare
sbracewell64
added a commit
that referenced
this pull request
Aug 9, 2026
… briefs to read the marker (#9) * feat(bin): add verified pi-signed runtime adapter (kunchenguid#1145) * feat: add verified pi-signed adapter * no-mistakes(review): Correct pi-signed maintainer verification date * no-mistakes(review): Correct remaining pi-signed verification dates * no-mistakes(review): Preserve authoritative pi-signed runtime identity * no-mistakes(document): Document pi-signed shared adapter semantics * no-mistakes: apply CI fixes * fix(pi): rearm watcher across session transitions (kunchenguid#1166) * fix(pi): rearm watcher across same-process session transitions Pi emits session_shutdown for ordinary /new, /resume, and /fork replacement as well as terminal quit. The primary watcher extension latched a module-level stopping flag on every shutdown, so a replacement session in the same process could not arm monitoring until Pi restarted. Own arm authority per session generation so only the active live generation may start, stop, or rearm the child. Replacement sessions can arm again without restarting Pi, stale prior-generation callbacks cannot mutate the active cycle, and real quit still blocks late rearm. * no-mistakes(review): Preserve Pi generation isolation and exit cleanup * no-mistakes(document): Correct Pi watcher transition documentation * feat: route crew dispatch using quota-window pace (kunchenguid#1172) * Consume quota-axi pace signals in dispatch profile array selection. Add quota-array-dispatch as the single owner of the pace-aware candidate choice, keep AGENTS.md to the intake boundary and load trigger, and cover the acceptance cases with sanitized schemaVersion 3 fixtures. * no-mistakes(review): Stop and report genuine quota dispatch ties * no-mistakes(document): Document quota pace freshness and uncertainty * fix: adapt Grok Stop continuation and harden endpoint cleanup (kunchenguid#1171) * fix(grok): adapt Stop continuation to runtime capability * no-mistakes(review): Reject ambiguous Grok Stop payloads * no-mistakes(review): Reject duplicate Grok fields and accept spaced tmux sessions * no-mistakes(review): Enforce exact tmux cleanup selectors * no-mistakes(test): Fix historical tmux fixture and validate Grok Stop * no-mistakes: apply CI fixes * fix: restore stock macOS Bash 3.2 brief scaffolding (kunchenguid#1093) * fix(brief): make DOD scaffolding parse-safe on stock macOS Bash 3.2 fm-brief.sh built each Definition-of-done block and the not-enabled Herdr declaration with `VAR=$(cat <<EOF ... EOF)`. On Bash 3.2 (macOS /bin/bash) the lexer scans for the command substitution's closing `)` textually and tracks quote state through the heredoc body, so a single apostrophe, unbalanced quote, or unbalanced paren in that prose breaks parsing of the whole script. Every ship-brief scaffold (no-mistakes, direct-PR, local-only) failed with `unexpected EOF while looking for matching )`. Bash 4+ parses it fine, so the breakage stayed invisible everywhere except stock macOS. Replace all four command-substitution heredocs with `IFS= read -r -d '' VAR <<EOF || true`. That removes the `$(...)` wrapper and the entire defect class regardless of future prose, and preserves the variable expansion the direct-PR and local-only bodies need. `read` keeps the heredoc's trailing newline that `$(...)` used to strip, so trim one newline to keep every generated brief byte-identical to prior output. Guard the structure, not one historical phrase: a new test rejects any heredoc nested in a command substitution anywhere in fm-brief.sh, where the old assertion pinned a single apostrophe phrase and so missed the reintroduction. Extend the stock-macOS Bash CI job from parsing one script to the whole maintained shell surface (bin/*.sh, bin/backends/*.sh, tests/*.sh), matching bin/fm-lint.sh's canonical file set so parse scope and lint scope cannot drift apart. * no-mistakes(review): Captain: harden Bash structure and inventory guards * no-mistakes(document): Align stock macOS Bash contributor checks * no-mistakes(lint): Suppress deliberate SC2016 literal fixture warnings * test: stabilize tmux teardown conformance baseline (kunchenguid#1209) * fix(test): pin teardown tmux baseline to historical kill selectors merge-base HEAD main collapses to HEAD after the exact-selector change lands on the default branch, so the old teardown fixture was accidentally exercising current exact targets. Resolve a content-historical permissive tmux adapter from first-parent history and force that post-squash topology inside the conformance case so main and feature branches keep the same old-vs-new contract. * no-mistakes(lint): Suppress intentional literal-pattern ShellCheck warnings * docs: slim quota-array-dispatch to the pace selection core (kunchenguid#1197) Cut the runtime skill to the compact pace-aware selection procedure plus minimum owner pointers. Keep every distinct decision rule and move expanded acceptance scenarios to deterministic fixture ownership assertions. Size: 170/1374/10187 -> 63/544/4068 (about 63%/60%/60% reduction). * feat(bin): inherit backend config into secondmate homes (kunchenguid#1219) * Inherit config/backend into secondmate homes with deliberate-override preservation Add backend to the shared inheritable config allowlist so launch, locked bootstrap, and config-push converge a primary pin into secondmate homes as each home local future-spawn default. Track last-inherited bytes in a private state provenance marker so deliberate per-home overrides survive present and absent primary convergence, keep --backend and FM_BACKEND stronger, and extend the existing inheritance tests plus docs and skill claims. * no-mistakes(review): Preserve equal unprovenanced backend overrides * no-mistakes(review): Preserve symlink overrides and verify spawn precedence * no-mistakes(review): Snapshot backend inheritance for consistent provenance * no-mistakes(review): Simplify backend inheritance to primary-authoritative convergence * no-mistakes(document): Document inherited backend override preservation * fix: restore primary-authoritative backend inheritance after document regression The document step reintroduced provenance and deliberate per-home override semantics after review had simplified config/backend to plain primary-authoritative allowlist membership. Restore the primary-always-wins path: present overwrites, absent removes, no provenance marker, and docs/tests match that contract. * no-mistakes(review): Add divergent backend precedence regression fixtures * no-mistakes(document): Document backend inheritance contract * fix(pi): remove Calm's upper version ceiling (kunchenguid#1226) * fix(pi): remove Calm's exclusive Pi upper-version ceiling tests/fm-calm-pi-extension.test.sh gated on a closed PI_COMPAT_VERSIONS allowlist ("0.81.1 0.82.0") that refused any other installed Pi, and docs described that range as "supported" rather than verified evidence. The Calm CHANGELOG shows no API introduced at either version, so there is no evidence for a real minimum; the presentation adapters already probe the exact method they patch rather than checking a version. Replace the allowlist with dated version evidence that never rejects a newer Pi, and make each presentation adapter degrade independently with a diagnostic if a future Pi removes its API, instead of the whole Calm extension failing to load. Rewrite the feasibility doc's "Pi 0.81.1 through 0.82.0" phrasing to state it as verified evidence, not a ceiling. * no-mistakes(review): Probe missing Calm adapter exports safely * no-mistakes(document): Document Calm's unbounded Pi compatibility * fix(bin): allow session-local todo tools in the subagent guard (kunchenguid#1204) * fix(guard): allow session-local todo tools in the primary The delegation-shape guard denied TaskCreate and TaskUpdate because their normalized names contain the `task` stem. Those tools write only the harness's session-local todo list, which has no executor: it spawns no agent, allocates no worktree, registers no schedule, and starts nothing that outlives the session. That is not the unaccounted work the guard exists to stop, so the stem match was a false positive, and the deny text told the primary to run bin/fm-brief.sh and bin/fm-spawn.sh to create a todo entry. Add a separately-reasoned PLAN_ONLY_TOOLS exact-name exclusion rather than widening OBSERVE_ONLY_TOOLS, whose documented contract is tools that only observe or stop existing work. Both lists stay exact-name so neither can widen by substring. Tests cover the two allowed names and six near-miss names that a substring or shortened-stem widening would release; both mutations were watched red. * no-mistakes(review): drop session-local todo tools from recommended deny list * no-mistakes: apply CI fixes * fix(session-lock): resolve Claude bg-spare ancestry to the outermost claude pid (kunchenguid#1206) * fix(session-lock): resolve Claude bg-spare ancestry to the outermost claude pid fm_harness_ancestry_pid() previously returned the first ancestor process whose command matched a verified harness name. Claude Code's Stop hook fires as a bg-spare worker several levels below the session's actual lock-owning claude process (hook shell -> claude bg-spare -> claude bg-pty-host -> claude -> claude(lock)), so the first match was the bg-spare worker, not the lock owner. fm_session_lock_owned_by_self() then never matched state/.lock, and the Claude Stop auto-arm silently treated its own primary session as an unrelated live owner and never armed the watcher. The walk now keeps going past a claude-named match, looking for a still more ancestral claude-named match, and stops the instant a non-match follows an already-found match (bounding it to a contiguous run rather than the literal ancestry top, so an unrelated claude-named process further up the real process tree is never mistaken for part of this session's own nested chain). Every other harness keeps the original first-match-wins behavior, since e.g. Pi's shared signed-wrapper ancestry actually holds the session at the inner engine pid, not an outer wrapper pid. Hop limit raised from 8 to 16 to cover the deeper bg-spare chain. * no-mistakes(review): Add nested-claude-ancestry regression test; fix nudge doc depth claim * no-mistakes: apply CI fixes * fix: conferma l'avvio del watcher su Windows/MSYS (kunchenguid#1212) * fix: confirm watcher startup on MSYS * no-mistakes(review): gate MSYS arm ready timeout, cache uname, harden locale test * no-mistakes(review): validate OpenCode ready timeout, make uname cache internal * fix(spawn): forward CLAUDE_CONFIG_DIR to claude crewmates (kunchenguid#1195) * fix(spawn): forward firstmate's CLAUDE_CONFIG_DIR to claude crewmates Crewmate panes are created by a long-lived tmux/herdr daemon that does not inherit firstmate's current environment. When firstmate runs under a non-default CLAUDE_CONFIG_DIR (for example a work-vs-personal subscription split), a bare `claude` in the crewmate pane fell back to the default ~/.claude store and launched unauthenticated, blocking the crewmate before it could do any work. fm-spawn now prefixes the claude launch with firstmate's own resolved CLAUDE_CONFIG_DIR when set, so the crewmate uses the same credential/config store firstmate is authenticated with. An unset value is the single-store default and adds no prefix; non-claude harnesses are unaffected. Adds three tests in fm-spawn-dispatch-profile.test.sh (forwarded-when-set, omitted-when-unset, non-claude-ignored) and pins CLAUDE_CONFIG_DIR in the test helper so launch assertions no longer depend on the developer's environment. * no-mistakes: apply CI fixes * fix: preserve dispatch identity across authentication checks (kunchenguid#1233) * fix: preserve dispatch harness identity * no-mistakes(review): Fix Grok counterfactual tuple validation * no-mistakes(document): Scope dispatch authentication to selected tuple * fix: restore dispatch instruction budget * no-mistakes(review): Scope dispatch authentication after candidate selection * fix(bin): normalize relative durable paths (kunchenguid#1256) * fix(bin): handle dash-leading harness process names (#2) * fix: handle dash-leading harness process names * no-mistakes(review): Make dash-leading harness regression hermetic * fix: preserve secondmate reply routes across relative homes Resolve relative home, data, and state inputs before durable charter generation, and fail when caller-relative directories cannot be resolved. Use absolute paths at the related spawn, AFK daemon, and X-mode cross-process handoffs so later processes cannot reinterpret them from another working directory. * no-mistakes(review): Preserve absolute overrides and normalize relative durable paths * no-mistakes(review): Normalize relative home before deriving durable paths * no-mistakes(document): Document relative durable-path normalization * no-mistakes(review): Captain: Ignore inherited CDPATH during relative path normalization * no-mistakes(lint): Fix empty CDPATH assignments for ShellCheck * refactor(skills): make Bearings chat-only by default (kunchenguid#1136) * Add internal status skill * no-mistakes(document): register /status skill in documentation-audiences inventory * no-mistakes(lint): replace grep|wc -l with grep -c in status skill test * test: silence literal status skill patterns * Refactor bearings default to chat-only --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com> * Clarify follow-up routing during validation (kunchenguid#1277) * fix: honor concrete approval for project operations (kunchenguid#1272) * docs: add captain-approved project operation exception to hard rule 1 Firstmate stays read-only over projects by default, but when the captain clearly approves a concrete project operation and scope in the moment, firstmate may perform exactly that approved operation with its own tools. The approval is never inferred, broadened, or standing, and it does not relax the existing force, discard, unlanded-work, or merge-authority boundaries. * no-mistakes(review): Clarify captain-approved project operation boundaries * no-mistakes(document): Clarify captain-approved project operation scope * docs: cover directories and preserve the operation-or-scope alternative Widen the captain-approved project operation exception in AGENTS.md to files or directories, and restore the explicit operation-or-scope alternative that a prior pipeline auto-fix had collapsed into "and". Rework project-management SKILL.md's Remove section, which previously told firstmate to refuse project removal until a guarded helper existed; that helper was never built, so the text directly contradicted the new instruction-only exception. It now points at the exception plus the existing removal preflight it still requires unchanged. Update the one instruction-owners test assertion that hard-coded the sentence removed above, so the suite tracks current, not obsolete, text. * docs: add captain-approved project operation exception to hard rule 1 Firstmate stays read-only over projects by default, but when the captain clearly approves a concrete project operation and scope in the moment, firstmate may perform exactly that approved operation with its own tools. The approval is never inferred, broadened, or standing, and it does not relax the existing force, discard, unlanded-work, or merge-authority boundaries. * no-mistakes(review): Clarify captain-approved project operation boundaries * no-mistakes(document): Clarify captain-approved project operation scope * docs: cover directories and preserve the operation-or-scope alternative Widen the captain-approved project operation exception in AGENTS.md to files or directories, and restore the explicit operation-or-scope alternative that a prior pipeline auto-fix had collapsed into "and". Rework project-management SKILL.md's Remove section, which previously told firstmate to refuse project removal until a guarded helper existed; that helper was never built, so the text directly contradicted the new instruction-only exception. It now points at the exception plus the existing removal preflight it still requires unchanged. Update the one instruction-owners test assertion that hard-coded the sentence removed above, so the suite tracks current, not obsolete, text. * no-mistakes(review): Align project removal preflight with approved exception * no-mistakes(document): Align project removal documentation with approved exception * fix: restore removal test byte-for-byte and preserve the default sentence tests/fm-instruction-owners.test.sh had been changed to assert different text; restore it byte-for-byte to origin/main. project-management SKILL.md's Remove section now keeps the exact default "Never issue a raw removal command from Firstmate." sentence that test still asserts, immediately followed by the already-approved captain-operation-or-scope exception, so the default and the exception both stay explicit and consistent. * no-mistakes(document): Align project-write boundary documentation * fix(skills): route new project intake through secondmate scopes (kunchenguid#1275) * Route project intake through secondmate scopes * no-mistakes(test): Guard all main-home project registry mutations * no-mistakes(document): Consolidate secondmate routing documentation * no-mistakes: apply CI fixes * Restore new-project routing scope * no-mistakes(document): Clarify secondmate routing for new-project intake * no-mistakes: apply CI fixes * fix: scope validation corrections by accepted behavior (kunchenguid#1281) * fix: scope validation corrections by accepted behavior * no-mistakes(review): Classify stale delivery evidence as an autonomous correction * test: replace source assertions with behavioral coverage (kunchenguid#1282) * test: remove source-content assertions * no-mistakes(review): Replace source assertions with runtime behavior coverage * no-mistakes(review): Isolate Kimi task temp runtime coverage * no-mistakes(document): Refresh test cleanup documentation * no-mistakes: apply CI fixes * fix(watch): escalate busy workers with no completed turn (kunchenguid#1286) * fix(watch): bound how long a busy pane may run with no completed turn A busy pane (backend busy state or the harness's rendered footer) was unconditional, unbounded proof of liveness in every escalation path, so a hung foreground tool call behind a busy signature could run for hours undetected (2026-07 hibit-agent-focus-nonsteal-r1 incident: a catastrophic- backtracking regex hung one bash call for 25h behind an unchanging "Working..." footer). FM_BUSY_TURN_MAX_SECS (default 3600s) now bounds how long a busy pane may run with no completed turn (state/<id>.turn-ended, or its spawn record before any turn has completed). Past the bound, busy_turn_over_age routes the pane through the existing wedge_timer_check, reusing the identical stale reason, escalation counter, and demand-deep-inspection marker for human inspection only - never an automatic interrupt, signal, or restart of the worker or its tool process. A completed turn resets the age. Reproduced end-to-end against the real installed Pi TUI: a foreground `sleep 999999` bash call with no timeout renders the actual busy footer, and two captures ~15s apart show the elapsed counter changing the pane hash while the same turn stays unfinished. Running the pre-fix watcher against the real captures showed it never starts a wedge timer no matter how long the pane stays busy; the fixed watcher starts and escalates the timer through the same mechanism, while the real hung process remained untouched and alive throughout. * no-mistakes(review): fix: parse enriched AFK stale reasons * no-mistakes(review): fix: preserve enriched wedges during AFK supervision * no-mistakes(review): fix: route all enriched AFK wedges * no-mistakes(document): Clarify busy-turn age supervision documentation * fix(gitignore): ignore config/ as a directory, not by exact filename (kunchenguid#1261) A name-by-name list of config/ entries silently stops ignoring any new or home-local file placed there, which makes the working tree read as dirty and blocks guarded sync paths that refuse to touch a dirty home. AGENTS.md already documents config/ as captain-private and gitignored as a category; this makes .gitignore match that contract. * fix(tests): replace source-content .gitignore assertion with behavioral coverage (kunchenguid#1304) The second assertion in fm-gitignore-config.test.sh (added by kunchenguid#1261) greps .gitignore for a specific spelling of the config/ ignore pattern. It fails on a semantically equivalent pattern like config/** and does not prove Git actually ignores anything, per the completed source-content-test audit. Replace it with a real git check-ignore control test on a generated unrelated path, and strengthen the existing directory-coverage test with generated unpredictable direct and nested config/ paths. * feat: bound and consolidate startup memory during stow (kunchenguid#1303) * Add bounded startup memory curation * no-mistakes(review): Record reproducible stow verification evidence * no-mistakes(review): Validate inherited secondmate stow evidence * no-mistakes(document): Document editable startup-memory budget propagation * feat(bin): mark crewmate and scout steers as from-firstmate A steer lands in the receiving agent's own chat, where nothing else told firstmate's instructions apart from a human typing into that pane. The gap was proven in both directions on 2026-07-26: the captain opened a crewmate pane believing it was firstmate and issued cross-lane instructions there, and a Pi crewmate at an ask-user gate addressed "Captain, ..." into its own pane and sat parked - nobody reads a crewmate pane, and a parked pipeline emits no wake, so that direction fails silently. AGENTS.md section 1 rule 4 already required workers to honor a distinction the system gave them no means to make. fm-send now applies the existing from-firstmate carrier to every text steer whose target resolves through this home's meta, not just kind=secondmate. A crewmate or scout carries the marker alone; the corr= correlation token and the parent pending-reply record stay secondmate-only, because a crewmate already answers on its own status file. Explicit backend targets and the --key path are unchanged. Command-shaped text is the one exclusion. A harness recognizes a slash command, or a codex $<skill> invocation, only at the very start of the composer line, so any prefix silently demotes it to prose. Verified on claude 2.1.220 and pi 0.82.0: with either marker shape prepended, /no-mistakes stops opening the completion popup entirely and would submit as ordinary text. Crewmate sends of that shape therefore stay unmarked and byte-identical, which also keeps every documented popup hazard out of this change's blast radius: the only bytes that move are plain text no harness parses specially. The exclusion deliberately does not reach a secondmate, whose marker is what creates its reply guarantee. The ship and scout scaffolds gain a "Who is speaking to you" section teaching the reader side: marked is firstmate, unmarked is a human who may believe the pane is firstmate, self-identify as a worker on this task before acting, and escalation is always the status file. AGENTS.md states the provenance principle once in rule 4; the away-mode stub and the secondmate charter keep their own distinct consequences. * no-mistakes(review): align brief's unmarked-message exception wording to fm-send predicate * no-mistakes(test): fix stale corr-less assertion in Pi/Herdr marker e2e * no-mistakes(document): generalize task-selector marker context to from-firstmate --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com> Co-authored-by: Christopher McKay <101884182+karotkriss@users.noreply.github.com> Co-authored-by: Daniel Kuykendall IV <danielkuykendall23@gmail.com> Co-authored-by: Trillium Smith <Spiteless@gmail.com> Co-authored-by: Unknownzed <45267749+Unknownzed@users.noreply.github.com> Co-authored-by: lhalbert <lucashalbert@users.noreply.github.com> Co-authored-by: AG <ag@agw3.org> Co-authored-by: deeto15 <92119640+deeto15@users.noreply.github.com>
sbracewell64
added a commit
that referenced
this pull request
Aug 9, 2026
… briefs to read the marker (#9) * feat(bin): add verified pi-signed runtime adapter (kunchenguid#1145) * feat: add verified pi-signed adapter * no-mistakes(review): Correct pi-signed maintainer verification date * no-mistakes(review): Correct remaining pi-signed verification dates * no-mistakes(review): Preserve authoritative pi-signed runtime identity * no-mistakes(document): Document pi-signed shared adapter semantics * no-mistakes: apply CI fixes * fix(pi): rearm watcher across session transitions (kunchenguid#1166) * fix(pi): rearm watcher across same-process session transitions Pi emits session_shutdown for ordinary /new, /resume, and /fork replacement as well as terminal quit. The primary watcher extension latched a module-level stopping flag on every shutdown, so a replacement session in the same process could not arm monitoring until Pi restarted. Own arm authority per session generation so only the active live generation may start, stop, or rearm the child. Replacement sessions can arm again without restarting Pi, stale prior-generation callbacks cannot mutate the active cycle, and real quit still blocks late rearm. * no-mistakes(review): Preserve Pi generation isolation and exit cleanup * no-mistakes(document): Correct Pi watcher transition documentation * feat: route crew dispatch using quota-window pace (kunchenguid#1172) * Consume quota-axi pace signals in dispatch profile array selection. Add quota-array-dispatch as the single owner of the pace-aware candidate choice, keep AGENTS.md to the intake boundary and load trigger, and cover the acceptance cases with sanitized schemaVersion 3 fixtures. * no-mistakes(review): Stop and report genuine quota dispatch ties * no-mistakes(document): Document quota pace freshness and uncertainty * fix: adapt Grok Stop continuation and harden endpoint cleanup (kunchenguid#1171) * fix(grok): adapt Stop continuation to runtime capability * no-mistakes(review): Reject ambiguous Grok Stop payloads * no-mistakes(review): Reject duplicate Grok fields and accept spaced tmux sessions * no-mistakes(review): Enforce exact tmux cleanup selectors * no-mistakes(test): Fix historical tmux fixture and validate Grok Stop * no-mistakes: apply CI fixes * fix: restore stock macOS Bash 3.2 brief scaffolding (kunchenguid#1093) * fix(brief): make DOD scaffolding parse-safe on stock macOS Bash 3.2 fm-brief.sh built each Definition-of-done block and the not-enabled Herdr declaration with `VAR=$(cat <<EOF ... EOF)`. On Bash 3.2 (macOS /bin/bash) the lexer scans for the command substitution's closing `)` textually and tracks quote state through the heredoc body, so a single apostrophe, unbalanced quote, or unbalanced paren in that prose breaks parsing of the whole script. Every ship-brief scaffold (no-mistakes, direct-PR, local-only) failed with `unexpected EOF while looking for matching )`. Bash 4+ parses it fine, so the breakage stayed invisible everywhere except stock macOS. Replace all four command-substitution heredocs with `IFS= read -r -d '' VAR <<EOF || true`. That removes the `$(...)` wrapper and the entire defect class regardless of future prose, and preserves the variable expansion the direct-PR and local-only bodies need. `read` keeps the heredoc's trailing newline that `$(...)` used to strip, so trim one newline to keep every generated brief byte-identical to prior output. Guard the structure, not one historical phrase: a new test rejects any heredoc nested in a command substitution anywhere in fm-brief.sh, where the old assertion pinned a single apostrophe phrase and so missed the reintroduction. Extend the stock-macOS Bash CI job from parsing one script to the whole maintained shell surface (bin/*.sh, bin/backends/*.sh, tests/*.sh), matching bin/fm-lint.sh's canonical file set so parse scope and lint scope cannot drift apart. * no-mistakes(review): Captain: harden Bash structure and inventory guards * no-mistakes(document): Align stock macOS Bash contributor checks * no-mistakes(lint): Suppress deliberate SC2016 literal fixture warnings * test: stabilize tmux teardown conformance baseline (kunchenguid#1209) * fix(test): pin teardown tmux baseline to historical kill selectors merge-base HEAD main collapses to HEAD after the exact-selector change lands on the default branch, so the old teardown fixture was accidentally exercising current exact targets. Resolve a content-historical permissive tmux adapter from first-parent history and force that post-squash topology inside the conformance case so main and feature branches keep the same old-vs-new contract. * no-mistakes(lint): Suppress intentional literal-pattern ShellCheck warnings * docs: slim quota-array-dispatch to the pace selection core (kunchenguid#1197) Cut the runtime skill to the compact pace-aware selection procedure plus minimum owner pointers. Keep every distinct decision rule and move expanded acceptance scenarios to deterministic fixture ownership assertions. Size: 170/1374/10187 -> 63/544/4068 (about 63%/60%/60% reduction). * feat(bin): inherit backend config into secondmate homes (kunchenguid#1219) * Inherit config/backend into secondmate homes with deliberate-override preservation Add backend to the shared inheritable config allowlist so launch, locked bootstrap, and config-push converge a primary pin into secondmate homes as each home local future-spawn default. Track last-inherited bytes in a private state provenance marker so deliberate per-home overrides survive present and absent primary convergence, keep --backend and FM_BACKEND stronger, and extend the existing inheritance tests plus docs and skill claims. * no-mistakes(review): Preserve equal unprovenanced backend overrides * no-mistakes(review): Preserve symlink overrides and verify spawn precedence * no-mistakes(review): Snapshot backend inheritance for consistent provenance * no-mistakes(review): Simplify backend inheritance to primary-authoritative convergence * no-mistakes(document): Document inherited backend override preservation * fix: restore primary-authoritative backend inheritance after document regression The document step reintroduced provenance and deliberate per-home override semantics after review had simplified config/backend to plain primary-authoritative allowlist membership. Restore the primary-always-wins path: present overwrites, absent removes, no provenance marker, and docs/tests match that contract. * no-mistakes(review): Add divergent backend precedence regression fixtures * no-mistakes(document): Document backend inheritance contract * fix(pi): remove Calm's upper version ceiling (kunchenguid#1226) * fix(pi): remove Calm's exclusive Pi upper-version ceiling tests/fm-calm-pi-extension.test.sh gated on a closed PI_COMPAT_VERSIONS allowlist ("0.81.1 0.82.0") that refused any other installed Pi, and docs described that range as "supported" rather than verified evidence. The Calm CHANGELOG shows no API introduced at either version, so there is no evidence for a real minimum; the presentation adapters already probe the exact method they patch rather than checking a version. Replace the allowlist with dated version evidence that never rejects a newer Pi, and make each presentation adapter degrade independently with a diagnostic if a future Pi removes its API, instead of the whole Calm extension failing to load. Rewrite the feasibility doc's "Pi 0.81.1 through 0.82.0" phrasing to state it as verified evidence, not a ceiling. * no-mistakes(review): Probe missing Calm adapter exports safely * no-mistakes(document): Document Calm's unbounded Pi compatibility * fix(bin): allow session-local todo tools in the subagent guard (kunchenguid#1204) * fix(guard): allow session-local todo tools in the primary The delegation-shape guard denied TaskCreate and TaskUpdate because their normalized names contain the `task` stem. Those tools write only the harness's session-local todo list, which has no executor: it spawns no agent, allocates no worktree, registers no schedule, and starts nothing that outlives the session. That is not the unaccounted work the guard exists to stop, so the stem match was a false positive, and the deny text told the primary to run bin/fm-brief.sh and bin/fm-spawn.sh to create a todo entry. Add a separately-reasoned PLAN_ONLY_TOOLS exact-name exclusion rather than widening OBSERVE_ONLY_TOOLS, whose documented contract is tools that only observe or stop existing work. Both lists stay exact-name so neither can widen by substring. Tests cover the two allowed names and six near-miss names that a substring or shortened-stem widening would release; both mutations were watched red. * no-mistakes(review): drop session-local todo tools from recommended deny list * no-mistakes: apply CI fixes * fix(session-lock): resolve Claude bg-spare ancestry to the outermost claude pid (kunchenguid#1206) * fix(session-lock): resolve Claude bg-spare ancestry to the outermost claude pid fm_harness_ancestry_pid() previously returned the first ancestor process whose command matched a verified harness name. Claude Code's Stop hook fires as a bg-spare worker several levels below the session's actual lock-owning claude process (hook shell -> claude bg-spare -> claude bg-pty-host -> claude -> claude(lock)), so the first match was the bg-spare worker, not the lock owner. fm_session_lock_owned_by_self() then never matched state/.lock, and the Claude Stop auto-arm silently treated its own primary session as an unrelated live owner and never armed the watcher. The walk now keeps going past a claude-named match, looking for a still more ancestral claude-named match, and stops the instant a non-match follows an already-found match (bounding it to a contiguous run rather than the literal ancestry top, so an unrelated claude-named process further up the real process tree is never mistaken for part of this session's own nested chain). Every other harness keeps the original first-match-wins behavior, since e.g. Pi's shared signed-wrapper ancestry actually holds the session at the inner engine pid, not an outer wrapper pid. Hop limit raised from 8 to 16 to cover the deeper bg-spare chain. * no-mistakes(review): Add nested-claude-ancestry regression test; fix nudge doc depth claim * no-mistakes: apply CI fixes * fix: conferma l'avvio del watcher su Windows/MSYS (kunchenguid#1212) * fix: confirm watcher startup on MSYS * no-mistakes(review): gate MSYS arm ready timeout, cache uname, harden locale test * no-mistakes(review): validate OpenCode ready timeout, make uname cache internal * fix(spawn): forward CLAUDE_CONFIG_DIR to claude crewmates (kunchenguid#1195) * fix(spawn): forward firstmate's CLAUDE_CONFIG_DIR to claude crewmates Crewmate panes are created by a long-lived tmux/herdr daemon that does not inherit firstmate's current environment. When firstmate runs under a non-default CLAUDE_CONFIG_DIR (for example a work-vs-personal subscription split), a bare `claude` in the crewmate pane fell back to the default ~/.claude store and launched unauthenticated, blocking the crewmate before it could do any work. fm-spawn now prefixes the claude launch with firstmate's own resolved CLAUDE_CONFIG_DIR when set, so the crewmate uses the same credential/config store firstmate is authenticated with. An unset value is the single-store default and adds no prefix; non-claude harnesses are unaffected. Adds three tests in fm-spawn-dispatch-profile.test.sh (forwarded-when-set, omitted-when-unset, non-claude-ignored) and pins CLAUDE_CONFIG_DIR in the test helper so launch assertions no longer depend on the developer's environment. * no-mistakes: apply CI fixes * fix: preserve dispatch identity across authentication checks (kunchenguid#1233) * fix: preserve dispatch harness identity * no-mistakes(review): Fix Grok counterfactual tuple validation * no-mistakes(document): Scope dispatch authentication to selected tuple * fix: restore dispatch instruction budget * no-mistakes(review): Scope dispatch authentication after candidate selection * fix(bin): normalize relative durable paths (kunchenguid#1256) * fix(bin): handle dash-leading harness process names (#2) * fix: handle dash-leading harness process names * no-mistakes(review): Make dash-leading harness regression hermetic * fix: preserve secondmate reply routes across relative homes Resolve relative home, data, and state inputs before durable charter generation, and fail when caller-relative directories cannot be resolved. Use absolute paths at the related spawn, AFK daemon, and X-mode cross-process handoffs so later processes cannot reinterpret them from another working directory. * no-mistakes(review): Preserve absolute overrides and normalize relative durable paths * no-mistakes(review): Normalize relative home before deriving durable paths * no-mistakes(document): Document relative durable-path normalization * no-mistakes(review): Captain: Ignore inherited CDPATH during relative path normalization * no-mistakes(lint): Fix empty CDPATH assignments for ShellCheck * refactor(skills): make Bearings chat-only by default (kunchenguid#1136) * Add internal status skill * no-mistakes(document): register /status skill in documentation-audiences inventory * no-mistakes(lint): replace grep|wc -l with grep -c in status skill test * test: silence literal status skill patterns * Refactor bearings default to chat-only --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com> * Clarify follow-up routing during validation (kunchenguid#1277) * fix: honor concrete approval for project operations (kunchenguid#1272) * docs: add captain-approved project operation exception to hard rule 1 Firstmate stays read-only over projects by default, but when the captain clearly approves a concrete project operation and scope in the moment, firstmate may perform exactly that approved operation with its own tools. The approval is never inferred, broadened, or standing, and it does not relax the existing force, discard, unlanded-work, or merge-authority boundaries. * no-mistakes(review): Clarify captain-approved project operation boundaries * no-mistakes(document): Clarify captain-approved project operation scope * docs: cover directories and preserve the operation-or-scope alternative Widen the captain-approved project operation exception in AGENTS.md to files or directories, and restore the explicit operation-or-scope alternative that a prior pipeline auto-fix had collapsed into "and". Rework project-management SKILL.md's Remove section, which previously told firstmate to refuse project removal until a guarded helper existed; that helper was never built, so the text directly contradicted the new instruction-only exception. It now points at the exception plus the existing removal preflight it still requires unchanged. Update the one instruction-owners test assertion that hard-coded the sentence removed above, so the suite tracks current, not obsolete, text. * docs: add captain-approved project operation exception to hard rule 1 Firstmate stays read-only over projects by default, but when the captain clearly approves a concrete project operation and scope in the moment, firstmate may perform exactly that approved operation with its own tools. The approval is never inferred, broadened, or standing, and it does not relax the existing force, discard, unlanded-work, or merge-authority boundaries. * no-mistakes(review): Clarify captain-approved project operation boundaries * no-mistakes(document): Clarify captain-approved project operation scope * docs: cover directories and preserve the operation-or-scope alternative Widen the captain-approved project operation exception in AGENTS.md to files or directories, and restore the explicit operation-or-scope alternative that a prior pipeline auto-fix had collapsed into "and". Rework project-management SKILL.md's Remove section, which previously told firstmate to refuse project removal until a guarded helper existed; that helper was never built, so the text directly contradicted the new instruction-only exception. It now points at the exception plus the existing removal preflight it still requires unchanged. Update the one instruction-owners test assertion that hard-coded the sentence removed above, so the suite tracks current, not obsolete, text. * no-mistakes(review): Align project removal preflight with approved exception * no-mistakes(document): Align project removal documentation with approved exception * fix: restore removal test byte-for-byte and preserve the default sentence tests/fm-instruction-owners.test.sh had been changed to assert different text; restore it byte-for-byte to origin/main. project-management SKILL.md's Remove section now keeps the exact default "Never issue a raw removal command from Firstmate." sentence that test still asserts, immediately followed by the already-approved captain-operation-or-scope exception, so the default and the exception both stay explicit and consistent. * no-mistakes(document): Align project-write boundary documentation * fix(skills): route new project intake through secondmate scopes (kunchenguid#1275) * Route project intake through secondmate scopes * no-mistakes(test): Guard all main-home project registry mutations * no-mistakes(document): Consolidate secondmate routing documentation * no-mistakes: apply CI fixes * Restore new-project routing scope * no-mistakes(document): Clarify secondmate routing for new-project intake * no-mistakes: apply CI fixes * fix: scope validation corrections by accepted behavior (kunchenguid#1281) * fix: scope validation corrections by accepted behavior * no-mistakes(review): Classify stale delivery evidence as an autonomous correction * test: replace source assertions with behavioral coverage (kunchenguid#1282) * test: remove source-content assertions * no-mistakes(review): Replace source assertions with runtime behavior coverage * no-mistakes(review): Isolate Kimi task temp runtime coverage * no-mistakes(document): Refresh test cleanup documentation * no-mistakes: apply CI fixes * fix(watch): escalate busy workers with no completed turn (kunchenguid#1286) * fix(watch): bound how long a busy pane may run with no completed turn A busy pane (backend busy state or the harness's rendered footer) was unconditional, unbounded proof of liveness in every escalation path, so a hung foreground tool call behind a busy signature could run for hours undetected (2026-07 hibit-agent-focus-nonsteal-r1 incident: a catastrophic- backtracking regex hung one bash call for 25h behind an unchanging "Working..." footer). FM_BUSY_TURN_MAX_SECS (default 3600s) now bounds how long a busy pane may run with no completed turn (state/<id>.turn-ended, or its spawn record before any turn has completed). Past the bound, busy_turn_over_age routes the pane through the existing wedge_timer_check, reusing the identical stale reason, escalation counter, and demand-deep-inspection marker for human inspection only - never an automatic interrupt, signal, or restart of the worker or its tool process. A completed turn resets the age. Reproduced end-to-end against the real installed Pi TUI: a foreground `sleep 999999` bash call with no timeout renders the actual busy footer, and two captures ~15s apart show the elapsed counter changing the pane hash while the same turn stays unfinished. Running the pre-fix watcher against the real captures showed it never starts a wedge timer no matter how long the pane stays busy; the fixed watcher starts and escalates the timer through the same mechanism, while the real hung process remained untouched and alive throughout. * no-mistakes(review): fix: parse enriched AFK stale reasons * no-mistakes(review): fix: preserve enriched wedges during AFK supervision * no-mistakes(review): fix: route all enriched AFK wedges * no-mistakes(document): Clarify busy-turn age supervision documentation * fix(gitignore): ignore config/ as a directory, not by exact filename (kunchenguid#1261) A name-by-name list of config/ entries silently stops ignoring any new or home-local file placed there, which makes the working tree read as dirty and blocks guarded sync paths that refuse to touch a dirty home. AGENTS.md already documents config/ as captain-private and gitignored as a category; this makes .gitignore match that contract. * fix(tests): replace source-content .gitignore assertion with behavioral coverage (kunchenguid#1304) The second assertion in fm-gitignore-config.test.sh (added by kunchenguid#1261) greps .gitignore for a specific spelling of the config/ ignore pattern. It fails on a semantically equivalent pattern like config/** and does not prove Git actually ignores anything, per the completed source-content-test audit. Replace it with a real git check-ignore control test on a generated unrelated path, and strengthen the existing directory-coverage test with generated unpredictable direct and nested config/ paths. * feat: bound and consolidate startup memory during stow (kunchenguid#1303) * Add bounded startup memory curation * no-mistakes(review): Record reproducible stow verification evidence * no-mistakes(review): Validate inherited secondmate stow evidence * no-mistakes(document): Document editable startup-memory budget propagation * feat(bin): mark crewmate and scout steers as from-firstmate A steer lands in the receiving agent's own chat, where nothing else told firstmate's instructions apart from a human typing into that pane. The gap was proven in both directions on 2026-07-26: the captain opened a crewmate pane believing it was firstmate and issued cross-lane instructions there, and a Pi crewmate at an ask-user gate addressed "Captain, ..." into its own pane and sat parked - nobody reads a crewmate pane, and a parked pipeline emits no wake, so that direction fails silently. AGENTS.md section 1 rule 4 already required workers to honor a distinction the system gave them no means to make. fm-send now applies the existing from-firstmate carrier to every text steer whose target resolves through this home's meta, not just kind=secondmate. A crewmate or scout carries the marker alone; the corr= correlation token and the parent pending-reply record stay secondmate-only, because a crewmate already answers on its own status file. Explicit backend targets and the --key path are unchanged. Command-shaped text is the one exclusion. A harness recognizes a slash command, or a codex $<skill> invocation, only at the very start of the composer line, so any prefix silently demotes it to prose. Verified on claude 2.1.220 and pi 0.82.0: with either marker shape prepended, /no-mistakes stops opening the completion popup entirely and would submit as ordinary text. Crewmate sends of that shape therefore stay unmarked and byte-identical, which also keeps every documented popup hazard out of this change's blast radius: the only bytes that move are plain text no harness parses specially. The exclusion deliberately does not reach a secondmate, whose marker is what creates its reply guarantee. The ship and scout scaffolds gain a "Who is speaking to you" section teaching the reader side: marked is firstmate, unmarked is a human who may believe the pane is firstmate, self-identify as a worker on this task before acting, and escalation is always the status file. AGENTS.md states the provenance principle once in rule 4; the away-mode stub and the secondmate charter keep their own distinct consequences. * no-mistakes(review): align brief's unmarked-message exception wording to fm-send predicate * no-mistakes(test): fix stale corr-less assertion in Pi/Herdr marker e2e * no-mistakes(document): generalize task-selector marker context to from-firstmate --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com> Co-authored-by: Christopher McKay <101884182+karotkriss@users.noreply.github.com> Co-authored-by: Daniel Kuykendall IV <danielkuykendall23@gmail.com> Co-authored-by: Trillium Smith <Spiteless@gmail.com> Co-authored-by: Unknownzed <45267749+Unknownzed@users.noreply.github.com> Co-authored-by: lhalbert <lucashalbert@users.noreply.github.com> Co-authored-by: AG <ag@agw3.org> Co-authored-by: deeto15 <92119640+deeto15@users.noreply.github.com>
sbracewell64
added a commit
that referenced
this pull request
Aug 10, 2026
… briefs to read the marker (#9) * feat(bin): add verified pi-signed runtime adapter (kunchenguid#1145) * feat: add verified pi-signed adapter * no-mistakes(review): Correct pi-signed maintainer verification date * no-mistakes(review): Correct remaining pi-signed verification dates * no-mistakes(review): Preserve authoritative pi-signed runtime identity * no-mistakes(document): Document pi-signed shared adapter semantics * no-mistakes: apply CI fixes * fix(pi): rearm watcher across session transitions (kunchenguid#1166) * fix(pi): rearm watcher across same-process session transitions Pi emits session_shutdown for ordinary /new, /resume, and /fork replacement as well as terminal quit. The primary watcher extension latched a module-level stopping flag on every shutdown, so a replacement session in the same process could not arm monitoring until Pi restarted. Own arm authority per session generation so only the active live generation may start, stop, or rearm the child. Replacement sessions can arm again without restarting Pi, stale prior-generation callbacks cannot mutate the active cycle, and real quit still blocks late rearm. * no-mistakes(review): Preserve Pi generation isolation and exit cleanup * no-mistakes(document): Correct Pi watcher transition documentation * feat: route crew dispatch using quota-window pace (kunchenguid#1172) * Consume quota-axi pace signals in dispatch profile array selection. Add quota-array-dispatch as the single owner of the pace-aware candidate choice, keep AGENTS.md to the intake boundary and load trigger, and cover the acceptance cases with sanitized schemaVersion 3 fixtures. * no-mistakes(review): Stop and report genuine quota dispatch ties * no-mistakes(document): Document quota pace freshness and uncertainty * fix: adapt Grok Stop continuation and harden endpoint cleanup (kunchenguid#1171) * fix(grok): adapt Stop continuation to runtime capability * no-mistakes(review): Reject ambiguous Grok Stop payloads * no-mistakes(review): Reject duplicate Grok fields and accept spaced tmux sessions * no-mistakes(review): Enforce exact tmux cleanup selectors * no-mistakes(test): Fix historical tmux fixture and validate Grok Stop * no-mistakes: apply CI fixes * fix: restore stock macOS Bash 3.2 brief scaffolding (kunchenguid#1093) * fix(brief): make DOD scaffolding parse-safe on stock macOS Bash 3.2 fm-brief.sh built each Definition-of-done block and the not-enabled Herdr declaration with `VAR=$(cat <<EOF ... EOF)`. On Bash 3.2 (macOS /bin/bash) the lexer scans for the command substitution's closing `)` textually and tracks quote state through the heredoc body, so a single apostrophe, unbalanced quote, or unbalanced paren in that prose breaks parsing of the whole script. Every ship-brief scaffold (no-mistakes, direct-PR, local-only) failed with `unexpected EOF while looking for matching )`. Bash 4+ parses it fine, so the breakage stayed invisible everywhere except stock macOS. Replace all four command-substitution heredocs with `IFS= read -r -d '' VAR <<EOF || true`. That removes the `$(...)` wrapper and the entire defect class regardless of future prose, and preserves the variable expansion the direct-PR and local-only bodies need. `read` keeps the heredoc's trailing newline that `$(...)` used to strip, so trim one newline to keep every generated brief byte-identical to prior output. Guard the structure, not one historical phrase: a new test rejects any heredoc nested in a command substitution anywhere in fm-brief.sh, where the old assertion pinned a single apostrophe phrase and so missed the reintroduction. Extend the stock-macOS Bash CI job from parsing one script to the whole maintained shell surface (bin/*.sh, bin/backends/*.sh, tests/*.sh), matching bin/fm-lint.sh's canonical file set so parse scope and lint scope cannot drift apart. * no-mistakes(review): Captain: harden Bash structure and inventory guards * no-mistakes(document): Align stock macOS Bash contributor checks * no-mistakes(lint): Suppress deliberate SC2016 literal fixture warnings * test: stabilize tmux teardown conformance baseline (kunchenguid#1209) * fix(test): pin teardown tmux baseline to historical kill selectors merge-base HEAD main collapses to HEAD after the exact-selector change lands on the default branch, so the old teardown fixture was accidentally exercising current exact targets. Resolve a content-historical permissive tmux adapter from first-parent history and force that post-squash topology inside the conformance case so main and feature branches keep the same old-vs-new contract. * no-mistakes(lint): Suppress intentional literal-pattern ShellCheck warnings * docs: slim quota-array-dispatch to the pace selection core (kunchenguid#1197) Cut the runtime skill to the compact pace-aware selection procedure plus minimum owner pointers. Keep every distinct decision rule and move expanded acceptance scenarios to deterministic fixture ownership assertions. Size: 170/1374/10187 -> 63/544/4068 (about 63%/60%/60% reduction). * feat(bin): inherit backend config into secondmate homes (kunchenguid#1219) * Inherit config/backend into secondmate homes with deliberate-override preservation Add backend to the shared inheritable config allowlist so launch, locked bootstrap, and config-push converge a primary pin into secondmate homes as each home local future-spawn default. Track last-inherited bytes in a private state provenance marker so deliberate per-home overrides survive present and absent primary convergence, keep --backend and FM_BACKEND stronger, and extend the existing inheritance tests plus docs and skill claims. * no-mistakes(review): Preserve equal unprovenanced backend overrides * no-mistakes(review): Preserve symlink overrides and verify spawn precedence * no-mistakes(review): Snapshot backend inheritance for consistent provenance * no-mistakes(review): Simplify backend inheritance to primary-authoritative convergence * no-mistakes(document): Document inherited backend override preservation * fix: restore primary-authoritative backend inheritance after document regression The document step reintroduced provenance and deliberate per-home override semantics after review had simplified config/backend to plain primary-authoritative allowlist membership. Restore the primary-always-wins path: present overwrites, absent removes, no provenance marker, and docs/tests match that contract. * no-mistakes(review): Add divergent backend precedence regression fixtures * no-mistakes(document): Document backend inheritance contract * fix(pi): remove Calm's upper version ceiling (kunchenguid#1226) * fix(pi): remove Calm's exclusive Pi upper-version ceiling tests/fm-calm-pi-extension.test.sh gated on a closed PI_COMPAT_VERSIONS allowlist ("0.81.1 0.82.0") that refused any other installed Pi, and docs described that range as "supported" rather than verified evidence. The Calm CHANGELOG shows no API introduced at either version, so there is no evidence for a real minimum; the presentation adapters already probe the exact method they patch rather than checking a version. Replace the allowlist with dated version evidence that never rejects a newer Pi, and make each presentation adapter degrade independently with a diagnostic if a future Pi removes its API, instead of the whole Calm extension failing to load. Rewrite the feasibility doc's "Pi 0.81.1 through 0.82.0" phrasing to state it as verified evidence, not a ceiling. * no-mistakes(review): Probe missing Calm adapter exports safely * no-mistakes(document): Document Calm's unbounded Pi compatibility * fix(bin): allow session-local todo tools in the subagent guard (kunchenguid#1204) * fix(guard): allow session-local todo tools in the primary The delegation-shape guard denied TaskCreate and TaskUpdate because their normalized names contain the `task` stem. Those tools write only the harness's session-local todo list, which has no executor: it spawns no agent, allocates no worktree, registers no schedule, and starts nothing that outlives the session. That is not the unaccounted work the guard exists to stop, so the stem match was a false positive, and the deny text told the primary to run bin/fm-brief.sh and bin/fm-spawn.sh to create a todo entry. Add a separately-reasoned PLAN_ONLY_TOOLS exact-name exclusion rather than widening OBSERVE_ONLY_TOOLS, whose documented contract is tools that only observe or stop existing work. Both lists stay exact-name so neither can widen by substring. Tests cover the two allowed names and six near-miss names that a substring or shortened-stem widening would release; both mutations were watched red. * no-mistakes(review): drop session-local todo tools from recommended deny list * no-mistakes: apply CI fixes * fix(session-lock): resolve Claude bg-spare ancestry to the outermost claude pid (kunchenguid#1206) * fix(session-lock): resolve Claude bg-spare ancestry to the outermost claude pid fm_harness_ancestry_pid() previously returned the first ancestor process whose command matched a verified harness name. Claude Code's Stop hook fires as a bg-spare worker several levels below the session's actual lock-owning claude process (hook shell -> claude bg-spare -> claude bg-pty-host -> claude -> claude(lock)), so the first match was the bg-spare worker, not the lock owner. fm_session_lock_owned_by_self() then never matched state/.lock, and the Claude Stop auto-arm silently treated its own primary session as an unrelated live owner and never armed the watcher. The walk now keeps going past a claude-named match, looking for a still more ancestral claude-named match, and stops the instant a non-match follows an already-found match (bounding it to a contiguous run rather than the literal ancestry top, so an unrelated claude-named process further up the real process tree is never mistaken for part of this session's own nested chain). Every other harness keeps the original first-match-wins behavior, since e.g. Pi's shared signed-wrapper ancestry actually holds the session at the inner engine pid, not an outer wrapper pid. Hop limit raised from 8 to 16 to cover the deeper bg-spare chain. * no-mistakes(review): Add nested-claude-ancestry regression test; fix nudge doc depth claim * no-mistakes: apply CI fixes * fix: conferma l'avvio del watcher su Windows/MSYS (kunchenguid#1212) * fix: confirm watcher startup on MSYS * no-mistakes(review): gate MSYS arm ready timeout, cache uname, harden locale test * no-mistakes(review): validate OpenCode ready timeout, make uname cache internal * fix(spawn): forward CLAUDE_CONFIG_DIR to claude crewmates (kunchenguid#1195) * fix(spawn): forward firstmate's CLAUDE_CONFIG_DIR to claude crewmates Crewmate panes are created by a long-lived tmux/herdr daemon that does not inherit firstmate's current environment. When firstmate runs under a non-default CLAUDE_CONFIG_DIR (for example a work-vs-personal subscription split), a bare `claude` in the crewmate pane fell back to the default ~/.claude store and launched unauthenticated, blocking the crewmate before it could do any work. fm-spawn now prefixes the claude launch with firstmate's own resolved CLAUDE_CONFIG_DIR when set, so the crewmate uses the same credential/config store firstmate is authenticated with. An unset value is the single-store default and adds no prefix; non-claude harnesses are unaffected. Adds three tests in fm-spawn-dispatch-profile.test.sh (forwarded-when-set, omitted-when-unset, non-claude-ignored) and pins CLAUDE_CONFIG_DIR in the test helper so launch assertions no longer depend on the developer's environment. * no-mistakes: apply CI fixes * fix: preserve dispatch identity across authentication checks (kunchenguid#1233) * fix: preserve dispatch harness identity * no-mistakes(review): Fix Grok counterfactual tuple validation * no-mistakes(document): Scope dispatch authentication to selected tuple * fix: restore dispatch instruction budget * no-mistakes(review): Scope dispatch authentication after candidate selection * fix(bin): normalize relative durable paths (kunchenguid#1256) * fix(bin): handle dash-leading harness process names (#2) * fix: handle dash-leading harness process names * no-mistakes(review): Make dash-leading harness regression hermetic * fix: preserve secondmate reply routes across relative homes Resolve relative home, data, and state inputs before durable charter generation, and fail when caller-relative directories cannot be resolved. Use absolute paths at the related spawn, AFK daemon, and X-mode cross-process handoffs so later processes cannot reinterpret them from another working directory. * no-mistakes(review): Preserve absolute overrides and normalize relative durable paths * no-mistakes(review): Normalize relative home before deriving durable paths * no-mistakes(document): Document relative durable-path normalization * no-mistakes(review): Captain: Ignore inherited CDPATH during relative path normalization * no-mistakes(lint): Fix empty CDPATH assignments for ShellCheck * refactor(skills): make Bearings chat-only by default (kunchenguid#1136) * Add internal status skill * no-mistakes(document): register /status skill in documentation-audiences inventory * no-mistakes(lint): replace grep|wc -l with grep -c in status skill test * test: silence literal status skill patterns * Refactor bearings default to chat-only --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com> * Clarify follow-up routing during validation (kunchenguid#1277) * fix: honor concrete approval for project operations (kunchenguid#1272) * docs: add captain-approved project operation exception to hard rule 1 Firstmate stays read-only over projects by default, but when the captain clearly approves a concrete project operation and scope in the moment, firstmate may perform exactly that approved operation with its own tools. The approval is never inferred, broadened, or standing, and it does not relax the existing force, discard, unlanded-work, or merge-authority boundaries. * no-mistakes(review): Clarify captain-approved project operation boundaries * no-mistakes(document): Clarify captain-approved project operation scope * docs: cover directories and preserve the operation-or-scope alternative Widen the captain-approved project operation exception in AGENTS.md to files or directories, and restore the explicit operation-or-scope alternative that a prior pipeline auto-fix had collapsed into "and". Rework project-management SKILL.md's Remove section, which previously told firstmate to refuse project removal until a guarded helper existed; that helper was never built, so the text directly contradicted the new instruction-only exception. It now points at the exception plus the existing removal preflight it still requires unchanged. Update the one instruction-owners test assertion that hard-coded the sentence removed above, so the suite tracks current, not obsolete, text. * docs: add captain-approved project operation exception to hard rule 1 Firstmate stays read-only over projects by default, but when the captain clearly approves a concrete project operation and scope in the moment, firstmate may perform exactly that approved operation with its own tools. The approval is never inferred, broadened, or standing, and it does not relax the existing force, discard, unlanded-work, or merge-authority boundaries. * no-mistakes(review): Clarify captain-approved project operation boundaries * no-mistakes(document): Clarify captain-approved project operation scope * docs: cover directories and preserve the operation-or-scope alternative Widen the captain-approved project operation exception in AGENTS.md to files or directories, and restore the explicit operation-or-scope alternative that a prior pipeline auto-fix had collapsed into "and". Rework project-management SKILL.md's Remove section, which previously told firstmate to refuse project removal until a guarded helper existed; that helper was never built, so the text directly contradicted the new instruction-only exception. It now points at the exception plus the existing removal preflight it still requires unchanged. Update the one instruction-owners test assertion that hard-coded the sentence removed above, so the suite tracks current, not obsolete, text. * no-mistakes(review): Align project removal preflight with approved exception * no-mistakes(document): Align project removal documentation with approved exception * fix: restore removal test byte-for-byte and preserve the default sentence tests/fm-instruction-owners.test.sh had been changed to assert different text; restore it byte-for-byte to origin/main. project-management SKILL.md's Remove section now keeps the exact default "Never issue a raw removal command from Firstmate." sentence that test still asserts, immediately followed by the already-approved captain-operation-or-scope exception, so the default and the exception both stay explicit and consistent. * no-mistakes(document): Align project-write boundary documentation * fix(skills): route new project intake through secondmate scopes (kunchenguid#1275) * Route project intake through secondmate scopes * no-mistakes(test): Guard all main-home project registry mutations * no-mistakes(document): Consolidate secondmate routing documentation * no-mistakes: apply CI fixes * Restore new-project routing scope * no-mistakes(document): Clarify secondmate routing for new-project intake * no-mistakes: apply CI fixes * fix: scope validation corrections by accepted behavior (kunchenguid#1281) * fix: scope validation corrections by accepted behavior * no-mistakes(review): Classify stale delivery evidence as an autonomous correction * test: replace source assertions with behavioral coverage (kunchenguid#1282) * test: remove source-content assertions * no-mistakes(review): Replace source assertions with runtime behavior coverage * no-mistakes(review): Isolate Kimi task temp runtime coverage * no-mistakes(document): Refresh test cleanup documentation * no-mistakes: apply CI fixes * fix(watch): escalate busy workers with no completed turn (kunchenguid#1286) * fix(watch): bound how long a busy pane may run with no completed turn A busy pane (backend busy state or the harness's rendered footer) was unconditional, unbounded proof of liveness in every escalation path, so a hung foreground tool call behind a busy signature could run for hours undetected (2026-07 hibit-agent-focus-nonsteal-r1 incident: a catastrophic- backtracking regex hung one bash call for 25h behind an unchanging "Working..." footer). FM_BUSY_TURN_MAX_SECS (default 3600s) now bounds how long a busy pane may run with no completed turn (state/<id>.turn-ended, or its spawn record before any turn has completed). Past the bound, busy_turn_over_age routes the pane through the existing wedge_timer_check, reusing the identical stale reason, escalation counter, and demand-deep-inspection marker for human inspection only - never an automatic interrupt, signal, or restart of the worker or its tool process. A completed turn resets the age. Reproduced end-to-end against the real installed Pi TUI: a foreground `sleep 999999` bash call with no timeout renders the actual busy footer, and two captures ~15s apart show the elapsed counter changing the pane hash while the same turn stays unfinished. Running the pre-fix watcher against the real captures showed it never starts a wedge timer no matter how long the pane stays busy; the fixed watcher starts and escalates the timer through the same mechanism, while the real hung process remained untouched and alive throughout. * no-mistakes(review): fix: parse enriched AFK stale reasons * no-mistakes(review): fix: preserve enriched wedges during AFK supervision * no-mistakes(review): fix: route all enriched AFK wedges * no-mistakes(document): Clarify busy-turn age supervision documentation * fix(gitignore): ignore config/ as a directory, not by exact filename (kunchenguid#1261) A name-by-name list of config/ entries silently stops ignoring any new or home-local file placed there, which makes the working tree read as dirty and blocks guarded sync paths that refuse to touch a dirty home. AGENTS.md already documents config/ as captain-private and gitignored as a category; this makes .gitignore match that contract. * fix(tests): replace source-content .gitignore assertion with behavioral coverage (kunchenguid#1304) The second assertion in fm-gitignore-config.test.sh (added by kunchenguid#1261) greps .gitignore for a specific spelling of the config/ ignore pattern. It fails on a semantically equivalent pattern like config/** and does not prove Git actually ignores anything, per the completed source-content-test audit. Replace it with a real git check-ignore control test on a generated unrelated path, and strengthen the existing directory-coverage test with generated unpredictable direct and nested config/ paths. * feat: bound and consolidate startup memory during stow (kunchenguid#1303) * Add bounded startup memory curation * no-mistakes(review): Record reproducible stow verification evidence * no-mistakes(review): Validate inherited secondmate stow evidence * no-mistakes(document): Document editable startup-memory budget propagation * feat(bin): mark crewmate and scout steers as from-firstmate A steer lands in the receiving agent's own chat, where nothing else told firstmate's instructions apart from a human typing into that pane. The gap was proven in both directions on 2026-07-26: the captain opened a crewmate pane believing it was firstmate and issued cross-lane instructions there, and a Pi crewmate at an ask-user gate addressed "Captain, ..." into its own pane and sat parked - nobody reads a crewmate pane, and a parked pipeline emits no wake, so that direction fails silently. AGENTS.md section 1 rule 4 already required workers to honor a distinction the system gave them no means to make. fm-send now applies the existing from-firstmate carrier to every text steer whose target resolves through this home's meta, not just kind=secondmate. A crewmate or scout carries the marker alone; the corr= correlation token and the parent pending-reply record stay secondmate-only, because a crewmate already answers on its own status file. Explicit backend targets and the --key path are unchanged. Command-shaped text is the one exclusion. A harness recognizes a slash command, or a codex $<skill> invocation, only at the very start of the composer line, so any prefix silently demotes it to prose. Verified on claude 2.1.220 and pi 0.82.0: with either marker shape prepended, /no-mistakes stops opening the completion popup entirely and would submit as ordinary text. Crewmate sends of that shape therefore stay unmarked and byte-identical, which also keeps every documented popup hazard out of this change's blast radius: the only bytes that move are plain text no harness parses specially. The exclusion deliberately does not reach a secondmate, whose marker is what creates its reply guarantee. The ship and scout scaffolds gain a "Who is speaking to you" section teaching the reader side: marked is firstmate, unmarked is a human who may believe the pane is firstmate, self-identify as a worker on this task before acting, and escalation is always the status file. AGENTS.md states the provenance principle once in rule 4; the away-mode stub and the secondmate charter keep their own distinct consequences. * no-mistakes(review): align brief's unmarked-message exception wording to fm-send predicate * no-mistakes(test): fix stale corr-less assertion in Pi/Herdr marker e2e * no-mistakes(document): generalize task-selector marker context to from-firstmate --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com> Co-authored-by: Christopher McKay <101884182+karotkriss@users.noreply.github.com> Co-authored-by: Daniel Kuykendall IV <danielkuykendall23@gmail.com> Co-authored-by: Trillium Smith <Spiteless@gmail.com> Co-authored-by: Unknownzed <45267749+Unknownzed@users.noreply.github.com> Co-authored-by: lhalbert <lucashalbert@users.noreply.github.com> Co-authored-by: AG <ag@agw3.org> Co-authored-by: deeto15 <92119640+deeto15@users.noreply.github.com>
sbracewell64
added a commit
that referenced
this pull request
Aug 11, 2026
… briefs to read the marker (#9) * feat(bin): add verified pi-signed runtime adapter (kunchenguid#1145) * feat: add verified pi-signed adapter * no-mistakes(review): Correct pi-signed maintainer verification date * no-mistakes(review): Correct remaining pi-signed verification dates * no-mistakes(review): Preserve authoritative pi-signed runtime identity * no-mistakes(document): Document pi-signed shared adapter semantics * no-mistakes: apply CI fixes * fix(pi): rearm watcher across session transitions (kunchenguid#1166) * fix(pi): rearm watcher across same-process session transitions Pi emits session_shutdown for ordinary /new, /resume, and /fork replacement as well as terminal quit. The primary watcher extension latched a module-level stopping flag on every shutdown, so a replacement session in the same process could not arm monitoring until Pi restarted. Own arm authority per session generation so only the active live generation may start, stop, or rearm the child. Replacement sessions can arm again without restarting Pi, stale prior-generation callbacks cannot mutate the active cycle, and real quit still blocks late rearm. * no-mistakes(review): Preserve Pi generation isolation and exit cleanup * no-mistakes(document): Correct Pi watcher transition documentation * feat: route crew dispatch using quota-window pace (kunchenguid#1172) * Consume quota-axi pace signals in dispatch profile array selection. Add quota-array-dispatch as the single owner of the pace-aware candidate choice, keep AGENTS.md to the intake boundary and load trigger, and cover the acceptance cases with sanitized schemaVersion 3 fixtures. * no-mistakes(review): Stop and report genuine quota dispatch ties * no-mistakes(document): Document quota pace freshness and uncertainty * fix: adapt Grok Stop continuation and harden endpoint cleanup (kunchenguid#1171) * fix(grok): adapt Stop continuation to runtime capability * no-mistakes(review): Reject ambiguous Grok Stop payloads * no-mistakes(review): Reject duplicate Grok fields and accept spaced tmux sessions * no-mistakes(review): Enforce exact tmux cleanup selectors * no-mistakes(test): Fix historical tmux fixture and validate Grok Stop * no-mistakes: apply CI fixes * fix: restore stock macOS Bash 3.2 brief scaffolding (kunchenguid#1093) * fix(brief): make DOD scaffolding parse-safe on stock macOS Bash 3.2 fm-brief.sh built each Definition-of-done block and the not-enabled Herdr declaration with `VAR=$(cat <<EOF ... EOF)`. On Bash 3.2 (macOS /bin/bash) the lexer scans for the command substitution's closing `)` textually and tracks quote state through the heredoc body, so a single apostrophe, unbalanced quote, or unbalanced paren in that prose breaks parsing of the whole script. Every ship-brief scaffold (no-mistakes, direct-PR, local-only) failed with `unexpected EOF while looking for matching )`. Bash 4+ parses it fine, so the breakage stayed invisible everywhere except stock macOS. Replace all four command-substitution heredocs with `IFS= read -r -d '' VAR <<EOF || true`. That removes the `$(...)` wrapper and the entire defect class regardless of future prose, and preserves the variable expansion the direct-PR and local-only bodies need. `read` keeps the heredoc's trailing newline that `$(...)` used to strip, so trim one newline to keep every generated brief byte-identical to prior output. Guard the structure, not one historical phrase: a new test rejects any heredoc nested in a command substitution anywhere in fm-brief.sh, where the old assertion pinned a single apostrophe phrase and so missed the reintroduction. Extend the stock-macOS Bash CI job from parsing one script to the whole maintained shell surface (bin/*.sh, bin/backends/*.sh, tests/*.sh), matching bin/fm-lint.sh's canonical file set so parse scope and lint scope cannot drift apart. * no-mistakes(review): Captain: harden Bash structure and inventory guards * no-mistakes(document): Align stock macOS Bash contributor checks * no-mistakes(lint): Suppress deliberate SC2016 literal fixture warnings * test: stabilize tmux teardown conformance baseline (kunchenguid#1209) * fix(test): pin teardown tmux baseline to historical kill selectors merge-base HEAD main collapses to HEAD after the exact-selector change lands on the default branch, so the old teardown fixture was accidentally exercising current exact targets. Resolve a content-historical permissive tmux adapter from first-parent history and force that post-squash topology inside the conformance case so main and feature branches keep the same old-vs-new contract. * no-mistakes(lint): Suppress intentional literal-pattern ShellCheck warnings * docs: slim quota-array-dispatch to the pace selection core (kunchenguid#1197) Cut the runtime skill to the compact pace-aware selection procedure plus minimum owner pointers. Keep every distinct decision rule and move expanded acceptance scenarios to deterministic fixture ownership assertions. Size: 170/1374/10187 -> 63/544/4068 (about 63%/60%/60% reduction). * feat(bin): inherit backend config into secondmate homes (kunchenguid#1219) * Inherit config/backend into secondmate homes with deliberate-override preservation Add backend to the shared inheritable config allowlist so launch, locked bootstrap, and config-push converge a primary pin into secondmate homes as each home local future-spawn default. Track last-inherited bytes in a private state provenance marker so deliberate per-home overrides survive present and absent primary convergence, keep --backend and FM_BACKEND stronger, and extend the existing inheritance tests plus docs and skill claims. * no-mistakes(review): Preserve equal unprovenanced backend overrides * no-mistakes(review): Preserve symlink overrides and verify spawn precedence * no-mistakes(review): Snapshot backend inheritance for consistent provenance * no-mistakes(review): Simplify backend inheritance to primary-authoritative convergence * no-mistakes(document): Document inherited backend override preservation * fix: restore primary-authoritative backend inheritance after document regression The document step reintroduced provenance and deliberate per-home override semantics after review had simplified config/backend to plain primary-authoritative allowlist membership. Restore the primary-always-wins path: present overwrites, absent removes, no provenance marker, and docs/tests match that contract. * no-mistakes(review): Add divergent backend precedence regression fixtures * no-mistakes(document): Document backend inheritance contract * fix(pi): remove Calm's upper version ceiling (kunchenguid#1226) * fix(pi): remove Calm's exclusive Pi upper-version ceiling tests/fm-calm-pi-extension.test.sh gated on a closed PI_COMPAT_VERSIONS allowlist ("0.81.1 0.82.0") that refused any other installed Pi, and docs described that range as "supported" rather than verified evidence. The Calm CHANGELOG shows no API introduced at either version, so there is no evidence for a real minimum; the presentation adapters already probe the exact method they patch rather than checking a version. Replace the allowlist with dated version evidence that never rejects a newer Pi, and make each presentation adapter degrade independently with a diagnostic if a future Pi removes its API, instead of the whole Calm extension failing to load. Rewrite the feasibility doc's "Pi 0.81.1 through 0.82.0" phrasing to state it as verified evidence, not a ceiling. * no-mistakes(review): Probe missing Calm adapter exports safely * no-mistakes(document): Document Calm's unbounded Pi compatibility * fix(bin): allow session-local todo tools in the subagent guard (kunchenguid#1204) * fix(guard): allow session-local todo tools in the primary The delegation-shape guard denied TaskCreate and TaskUpdate because their normalized names contain the `task` stem. Those tools write only the harness's session-local todo list, which has no executor: it spawns no agent, allocates no worktree, registers no schedule, and starts nothing that outlives the session. That is not the unaccounted work the guard exists to stop, so the stem match was a false positive, and the deny text told the primary to run bin/fm-brief.sh and bin/fm-spawn.sh to create a todo entry. Add a separately-reasoned PLAN_ONLY_TOOLS exact-name exclusion rather than widening OBSERVE_ONLY_TOOLS, whose documented contract is tools that only observe or stop existing work. Both lists stay exact-name so neither can widen by substring. Tests cover the two allowed names and six near-miss names that a substring or shortened-stem widening would release; both mutations were watched red. * no-mistakes(review): drop session-local todo tools from recommended deny list * no-mistakes: apply CI fixes * fix(session-lock): resolve Claude bg-spare ancestry to the outermost claude pid (kunchenguid#1206) * fix(session-lock): resolve Claude bg-spare ancestry to the outermost claude pid fm_harness_ancestry_pid() previously returned the first ancestor process whose command matched a verified harness name. Claude Code's Stop hook fires as a bg-spare worker several levels below the session's actual lock-owning claude process (hook shell -> claude bg-spare -> claude bg-pty-host -> claude -> claude(lock)), so the first match was the bg-spare worker, not the lock owner. fm_session_lock_owned_by_self() then never matched state/.lock, and the Claude Stop auto-arm silently treated its own primary session as an unrelated live owner and never armed the watcher. The walk now keeps going past a claude-named match, looking for a still more ancestral claude-named match, and stops the instant a non-match follows an already-found match (bounding it to a contiguous run rather than the literal ancestry top, so an unrelated claude-named process further up the real process tree is never mistaken for part of this session's own nested chain). Every other harness keeps the original first-match-wins behavior, since e.g. Pi's shared signed-wrapper ancestry actually holds the session at the inner engine pid, not an outer wrapper pid. Hop limit raised from 8 to 16 to cover the deeper bg-spare chain. * no-mistakes(review): Add nested-claude-ancestry regression test; fix nudge doc depth claim * no-mistakes: apply CI fixes * fix: conferma l'avvio del watcher su Windows/MSYS (kunchenguid#1212) * fix: confirm watcher startup on MSYS * no-mistakes(review): gate MSYS arm ready timeout, cache uname, harden locale test * no-mistakes(review): validate OpenCode ready timeout, make uname cache internal * fix(spawn): forward CLAUDE_CONFIG_DIR to claude crewmates (kunchenguid#1195) * fix(spawn): forward firstmate's CLAUDE_CONFIG_DIR to claude crewmates Crewmate panes are created by a long-lived tmux/herdr daemon that does not inherit firstmate's current environment. When firstmate runs under a non-default CLAUDE_CONFIG_DIR (for example a work-vs-personal subscription split), a bare `claude` in the crewmate pane fell back to the default ~/.claude store and launched unauthenticated, blocking the crewmate before it could do any work. fm-spawn now prefixes the claude launch with firstmate's own resolved CLAUDE_CONFIG_DIR when set, so the crewmate uses the same credential/config store firstmate is authenticated with. An unset value is the single-store default and adds no prefix; non-claude harnesses are unaffected. Adds three tests in fm-spawn-dispatch-profile.test.sh (forwarded-when-set, omitted-when-unset, non-claude-ignored) and pins CLAUDE_CONFIG_DIR in the test helper so launch assertions no longer depend on the developer's environment. * no-mistakes: apply CI fixes * fix: preserve dispatch identity across authentication checks (kunchenguid#1233) * fix: preserve dispatch harness identity * no-mistakes(review): Fix Grok counterfactual tuple validation * no-mistakes(document): Scope dispatch authentication to selected tuple * fix: restore dispatch instruction budget * no-mistakes(review): Scope dispatch authentication after candidate selection * fix(bin): normalize relative durable paths (kunchenguid#1256) * fix(bin): handle dash-leading harness process names (#2) * fix: handle dash-leading harness process names * no-mistakes(review): Make dash-leading harness regression hermetic * fix: preserve secondmate reply routes across relative homes Resolve relative home, data, and state inputs before durable charter generation, and fail when caller-relative directories cannot be resolved. Use absolute paths at the related spawn, AFK daemon, and X-mode cross-process handoffs so later processes cannot reinterpret them from another working directory. * no-mistakes(review): Preserve absolute overrides and normalize relative durable paths * no-mistakes(review): Normalize relative home before deriving durable paths * no-mistakes(document): Document relative durable-path normalization * no-mistakes(review): Captain: Ignore inherited CDPATH during relative path normalization * no-mistakes(lint): Fix empty CDPATH assignments for ShellCheck * refactor(skills): make Bearings chat-only by default (kunchenguid#1136) * Add internal status skill * no-mistakes(document): register /status skill in documentation-audiences inventory * no-mistakes(lint): replace grep|wc -l with grep -c in status skill test * test: silence literal status skill patterns * Refactor bearings default to chat-only --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com> * Clarify follow-up routing during validation (kunchenguid#1277) * fix: honor concrete approval for project operations (kunchenguid#1272) * docs: add captain-approved project operation exception to hard rule 1 Firstmate stays read-only over projects by default, but when the captain clearly approves a concrete project operation and scope in the moment, firstmate may perform exactly that approved operation with its own tools. The approval is never inferred, broadened, or standing, and it does not relax the existing force, discard, unlanded-work, or merge-authority boundaries. * no-mistakes(review): Clarify captain-approved project operation boundaries * no-mistakes(document): Clarify captain-approved project operation scope * docs: cover directories and preserve the operation-or-scope alternative Widen the captain-approved project operation exception in AGENTS.md to files or directories, and restore the explicit operation-or-scope alternative that a prior pipeline auto-fix had collapsed into "and". Rework project-management SKILL.md's Remove section, which previously told firstmate to refuse project removal until a guarded helper existed; that helper was never built, so the text directly contradicted the new instruction-only exception. It now points at the exception plus the existing removal preflight it still requires unchanged. Update the one instruction-owners test assertion that hard-coded the sentence removed above, so the suite tracks current, not obsolete, text. * docs: add captain-approved project operation exception to hard rule 1 Firstmate stays read-only over projects by default, but when the captain clearly approves a concrete project operation and scope in the moment, firstmate may perform exactly that approved operation with its own tools. The approval is never inferred, broadened, or standing, and it does not relax the existing force, discard, unlanded-work, or merge-authority boundaries. * no-mistakes(review): Clarify captain-approved project operation boundaries * no-mistakes(document): Clarify captain-approved project operation scope * docs: cover directories and preserve the operation-or-scope alternative Widen the captain-approved project operation exception in AGENTS.md to files or directories, and restore the explicit operation-or-scope alternative that a prior pipeline auto-fix had collapsed into "and". Rework project-management SKILL.md's Remove section, which previously told firstmate to refuse project removal until a guarded helper existed; that helper was never built, so the text directly contradicted the new instruction-only exception. It now points at the exception plus the existing removal preflight it still requires unchanged. Update the one instruction-owners test assertion that hard-coded the sentence removed above, so the suite tracks current, not obsolete, text. * no-mistakes(review): Align project removal preflight with approved exception * no-mistakes(document): Align project removal documentation with approved exception * fix: restore removal test byte-for-byte and preserve the default sentence tests/fm-instruction-owners.test.sh had been changed to assert different text; restore it byte-for-byte to origin/main. project-management SKILL.md's Remove section now keeps the exact default "Never issue a raw removal command from Firstmate." sentence that test still asserts, immediately followed by the already-approved captain-operation-or-scope exception, so the default and the exception both stay explicit and consistent. * no-mistakes(document): Align project-write boundary documentation * fix(skills): route new project intake through secondmate scopes (kunchenguid#1275) * Route project intake through secondmate scopes * no-mistakes(test): Guard all main-home project registry mutations * no-mistakes(document): Consolidate secondmate routing documentation * no-mistakes: apply CI fixes * Restore new-project routing scope * no-mistakes(document): Clarify secondmate routing for new-project intake * no-mistakes: apply CI fixes * fix: scope validation corrections by accepted behavior (kunchenguid#1281) * fix: scope validation corrections by accepted behavior * no-mistakes(review): Classify stale delivery evidence as an autonomous correction * test: replace source assertions with behavioral coverage (kunchenguid#1282) * test: remove source-content assertions * no-mistakes(review): Replace source assertions with runtime behavior coverage * no-mistakes(review): Isolate Kimi task temp runtime coverage * no-mistakes(document): Refresh test cleanup documentation * no-mistakes: apply CI fixes * fix(watch): escalate busy workers with no completed turn (kunchenguid#1286) * fix(watch): bound how long a busy pane may run with no completed turn A busy pane (backend busy state or the harness's rendered footer) was unconditional, unbounded proof of liveness in every escalation path, so a hung foreground tool call behind a busy signature could run for hours undetected (2026-07 hibit-agent-focus-nonsteal-r1 incident: a catastrophic- backtracking regex hung one bash call for 25h behind an unchanging "Working..." footer). FM_BUSY_TURN_MAX_SECS (default 3600s) now bounds how long a busy pane may run with no completed turn (state/<id>.turn-ended, or its spawn record before any turn has completed). Past the bound, busy_turn_over_age routes the pane through the existing wedge_timer_check, reusing the identical stale reason, escalation counter, and demand-deep-inspection marker for human inspection only - never an automatic interrupt, signal, or restart of the worker or its tool process. A completed turn resets the age. Reproduced end-to-end against the real installed Pi TUI: a foreground `sleep 999999` bash call with no timeout renders the actual busy footer, and two captures ~15s apart show the elapsed counter changing the pane hash while the same turn stays unfinished. Running the pre-fix watcher against the real captures showed it never starts a wedge timer no matter how long the pane stays busy; the fixed watcher starts and escalates the timer through the same mechanism, while the real hung process remained untouched and alive throughout. * no-mistakes(review): fix: parse enriched AFK stale reasons * no-mistakes(review): fix: preserve enriched wedges during AFK supervision * no-mistakes(review): fix: route all enriched AFK wedges * no-mistakes(document): Clarify busy-turn age supervision documentation * fix(gitignore): ignore config/ as a directory, not by exact filename (kunchenguid#1261) A name-by-name list of config/ entries silently stops ignoring any new or home-local file placed there, which makes the working tree read as dirty and blocks guarded sync paths that refuse to touch a dirty home. AGENTS.md already documents config/ as captain-private and gitignored as a category; this makes .gitignore match that contract. * fix(tests): replace source-content .gitignore assertion with behavioral coverage (kunchenguid#1304) The second assertion in fm-gitignore-config.test.sh (added by kunchenguid#1261) greps .gitignore for a specific spelling of the config/ ignore pattern. It fails on a semantically equivalent pattern like config/** and does not prove Git actually ignores anything, per the completed source-content-test audit. Replace it with a real git check-ignore control test on a generated unrelated path, and strengthen the existing directory-coverage test with generated unpredictable direct and nested config/ paths. * feat: bound and consolidate startup memory during stow (kunchenguid#1303) * Add bounded startup memory curation * no-mistakes(review): Record reproducible stow verification evidence * no-mistakes(review): Validate inherited secondmate stow evidence * no-mistakes(document): Document editable startup-memory budget propagation * feat(bin): mark crewmate and scout steers as from-firstmate A steer lands in the receiving agent's own chat, where nothing else told firstmate's instructions apart from a human typing into that pane. The gap was proven in both directions on 2026-07-26: the captain opened a crewmate pane believing it was firstmate and issued cross-lane instructions there, and a Pi crewmate at an ask-user gate addressed "Captain, ..." into its own pane and sat parked - nobody reads a crewmate pane, and a parked pipeline emits no wake, so that direction fails silently. AGENTS.md section 1 rule 4 already required workers to honor a distinction the system gave them no means to make. fm-send now applies the existing from-firstmate carrier to every text steer whose target resolves through this home's meta, not just kind=secondmate. A crewmate or scout carries the marker alone; the corr= correlation token and the parent pending-reply record stay secondmate-only, because a crewmate already answers on its own status file. Explicit backend targets and the --key path are unchanged. Command-shaped text is the one exclusion. A harness recognizes a slash command, or a codex $<skill> invocation, only at the very start of the composer line, so any prefix silently demotes it to prose. Verified on claude 2.1.220 and pi 0.82.0: with either marker shape prepended, /no-mistakes stops opening the completion popup entirely and would submit as ordinary text. Crewmate sends of that shape therefore stay unmarked and byte-identical, which also keeps every documented popup hazard out of this change's blast radius: the only bytes that move are plain text no harness parses specially. The exclusion deliberately does not reach a secondmate, whose marker is what creates its reply guarantee. The ship and scout scaffolds gain a "Who is speaking to you" section teaching the reader side: marked is firstmate, unmarked is a human who may believe the pane is firstmate, self-identify as a worker on this task before acting, and escalation is always the status file. AGENTS.md states the provenance principle once in rule 4; the away-mode stub and the secondmate charter keep their own distinct consequences. * no-mistakes(review): align brief's unmarked-message exception wording to fm-send predicate * no-mistakes(test): fix stale corr-less assertion in Pi/Herdr marker e2e * no-mistakes(document): generalize task-selector marker context to from-firstmate --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com> Co-authored-by: Christopher McKay <101884182+karotkriss@users.noreply.github.com> Co-authored-by: Daniel Kuykendall IV <danielkuykendall23@gmail.com> Co-authored-by: Trillium Smith <Spiteless@gmail.com> Co-authored-by: Unknownzed <45267749+Unknownzed@users.noreply.github.com> Co-authored-by: lhalbert <lucashalbert@users.noreply.github.com> Co-authored-by: AG <ag@agw3.org> Co-authored-by: deeto15 <92119640+deeto15@users.noreply.github.com>
sbracewell64
added a commit
that referenced
this pull request
Aug 11, 2026
… briefs to read the marker (#9) * feat(bin): add verified pi-signed runtime adapter (kunchenguid#1145) * feat: add verified pi-signed adapter * no-mistakes(review): Correct pi-signed maintainer verification date * no-mistakes(review): Correct remaining pi-signed verification dates * no-mistakes(review): Preserve authoritative pi-signed runtime identity * no-mistakes(document): Document pi-signed shared adapter semantics * no-mistakes: apply CI fixes * fix(pi): rearm watcher across session transitions (kunchenguid#1166) * fix(pi): rearm watcher across same-process session transitions Pi emits session_shutdown for ordinary /new, /resume, and /fork replacement as well as terminal quit. The primary watcher extension latched a module-level stopping flag on every shutdown, so a replacement session in the same process could not arm monitoring until Pi restarted. Own arm authority per session generation so only the active live generation may start, stop, or rearm the child. Replacement sessions can arm again without restarting Pi, stale prior-generation callbacks cannot mutate the active cycle, and real quit still blocks late rearm. * no-mistakes(review): Preserve Pi generation isolation and exit cleanup * no-mistakes(document): Correct Pi watcher transition documentation * feat: route crew dispatch using quota-window pace (kunchenguid#1172) * Consume quota-axi pace signals in dispatch profile array selection. Add quota-array-dispatch as the single owner of the pace-aware candidate choice, keep AGENTS.md to the intake boundary and load trigger, and cover the acceptance cases with sanitized schemaVersion 3 fixtures. * no-mistakes(review): Stop and report genuine quota dispatch ties * no-mistakes(document): Document quota pace freshness and uncertainty * fix: adapt Grok Stop continuation and harden endpoint cleanup (kunchenguid#1171) * fix(grok): adapt Stop continuation to runtime capability * no-mistakes(review): Reject ambiguous Grok Stop payloads * no-mistakes(review): Reject duplicate Grok fields and accept spaced tmux sessions * no-mistakes(review): Enforce exact tmux cleanup selectors * no-mistakes(test): Fix historical tmux fixture and validate Grok Stop * no-mistakes: apply CI fixes * fix: restore stock macOS Bash 3.2 brief scaffolding (kunchenguid#1093) * fix(brief): make DOD scaffolding parse-safe on stock macOS Bash 3.2 fm-brief.sh built each Definition-of-done block and the not-enabled Herdr declaration with `VAR=$(cat <<EOF ... EOF)`. On Bash 3.2 (macOS /bin/bash) the lexer scans for the command substitution's closing `)` textually and tracks quote state through the heredoc body, so a single apostrophe, unbalanced quote, or unbalanced paren in that prose breaks parsing of the whole script. Every ship-brief scaffold (no-mistakes, direct-PR, local-only) failed with `unexpected EOF while looking for matching )`. Bash 4+ parses it fine, so the breakage stayed invisible everywhere except stock macOS. Replace all four command-substitution heredocs with `IFS= read -r -d '' VAR <<EOF || true`. That removes the `$(...)` wrapper and the entire defect class regardless of future prose, and preserves the variable expansion the direct-PR and local-only bodies need. `read` keeps the heredoc's trailing newline that `$(...)` used to strip, so trim one newline to keep every generated brief byte-identical to prior output. Guard the structure, not one historical phrase: a new test rejects any heredoc nested in a command substitution anywhere in fm-brief.sh, where the old assertion pinned a single apostrophe phrase and so missed the reintroduction. Extend the stock-macOS Bash CI job from parsing one script to the whole maintained shell surface (bin/*.sh, bin/backends/*.sh, tests/*.sh), matching bin/fm-lint.sh's canonical file set so parse scope and lint scope cannot drift apart. * no-mistakes(review): Captain: harden Bash structure and inventory guards * no-mistakes(document): Align stock macOS Bash contributor checks * no-mistakes(lint): Suppress deliberate SC2016 literal fixture warnings * test: stabilize tmux teardown conformance baseline (kunchenguid#1209) * fix(test): pin teardown tmux baseline to historical kill selectors merge-base HEAD main collapses to HEAD after the exact-selector change lands on the default branch, so the old teardown fixture was accidentally exercising current exact targets. Resolve a content-historical permissive tmux adapter from first-parent history and force that post-squash topology inside the conformance case so main and feature branches keep the same old-vs-new contract. * no-mistakes(lint): Suppress intentional literal-pattern ShellCheck warnings * docs: slim quota-array-dispatch to the pace selection core (kunchenguid#1197) Cut the runtime skill to the compact pace-aware selection procedure plus minimum owner pointers. Keep every distinct decision rule and move expanded acceptance scenarios to deterministic fixture ownership assertions. Size: 170/1374/10187 -> 63/544/4068 (about 63%/60%/60% reduction). * feat(bin): inherit backend config into secondmate homes (kunchenguid#1219) * Inherit config/backend into secondmate homes with deliberate-override preservation Add backend to the shared inheritable config allowlist so launch, locked bootstrap, and config-push converge a primary pin into secondmate homes as each home local future-spawn default. Track last-inherited bytes in a private state provenance marker so deliberate per-home overrides survive present and absent primary convergence, keep --backend and FM_BACKEND stronger, and extend the existing inheritance tests plus docs and skill claims. * no-mistakes(review): Preserve equal unprovenanced backend overrides * no-mistakes(review): Preserve symlink overrides and verify spawn precedence * no-mistakes(review): Snapshot backend inheritance for consistent provenance * no-mistakes(review): Simplify backend inheritance to primary-authoritative convergence * no-mistakes(document): Document inherited backend override preservation * fix: restore primary-authoritative backend inheritance after document regression The document step reintroduced provenance and deliberate per-home override semantics after review had simplified config/backend to plain primary-authoritative allowlist membership. Restore the primary-always-wins path: present overwrites, absent removes, no provenance marker, and docs/tests match that contract. * no-mistakes(review): Add divergent backend precedence regression fixtures * no-mistakes(document): Document backend inheritance contract * fix(pi): remove Calm's upper version ceiling (kunchenguid#1226) * fix(pi): remove Calm's exclusive Pi upper-version ceiling tests/fm-calm-pi-extension.test.sh gated on a closed PI_COMPAT_VERSIONS allowlist ("0.81.1 0.82.0") that refused any other installed Pi, and docs described that range as "supported" rather than verified evidence. The Calm CHANGELOG shows no API introduced at either version, so there is no evidence for a real minimum; the presentation adapters already probe the exact method they patch rather than checking a version. Replace the allowlist with dated version evidence that never rejects a newer Pi, and make each presentation adapter degrade independently with a diagnostic if a future Pi removes its API, instead of the whole Calm extension failing to load. Rewrite the feasibility doc's "Pi 0.81.1 through 0.82.0" phrasing to state it as verified evidence, not a ceiling. * no-mistakes(review): Probe missing Calm adapter exports safely * no-mistakes(document): Document Calm's unbounded Pi compatibility * fix(bin): allow session-local todo tools in the subagent guard (kunchenguid#1204) * fix(guard): allow session-local todo tools in the primary The delegation-shape guard denied TaskCreate and TaskUpdate because their normalized names contain the `task` stem. Those tools write only the harness's session-local todo list, which has no executor: it spawns no agent, allocates no worktree, registers no schedule, and starts nothing that outlives the session. That is not the unaccounted work the guard exists to stop, so the stem match was a false positive, and the deny text told the primary to run bin/fm-brief.sh and bin/fm-spawn.sh to create a todo entry. Add a separately-reasoned PLAN_ONLY_TOOLS exact-name exclusion rather than widening OBSERVE_ONLY_TOOLS, whose documented contract is tools that only observe or stop existing work. Both lists stay exact-name so neither can widen by substring. Tests cover the two allowed names and six near-miss names that a substring or shortened-stem widening would release; both mutations were watched red. * no-mistakes(review): drop session-local todo tools from recommended deny list * no-mistakes: apply CI fixes * fix(session-lock): resolve Claude bg-spare ancestry to the outermost claude pid (kunchenguid#1206) * fix(session-lock): resolve Claude bg-spare ancestry to the outermost claude pid fm_harness_ancestry_pid() previously returned the first ancestor process whose command matched a verified harness name. Claude Code's Stop hook fires as a bg-spare worker several levels below the session's actual lock-owning claude process (hook shell -> claude bg-spare -> claude bg-pty-host -> claude -> claude(lock)), so the first match was the bg-spare worker, not the lock owner. fm_session_lock_owned_by_self() then never matched state/.lock, and the Claude Stop auto-arm silently treated its own primary session as an unrelated live owner and never armed the watcher. The walk now keeps going past a claude-named match, looking for a still more ancestral claude-named match, and stops the instant a non-match follows an already-found match (bounding it to a contiguous run rather than the literal ancestry top, so an unrelated claude-named process further up the real process tree is never mistaken for part of this session's own nested chain). Every other harness keeps the original first-match-wins behavior, since e.g. Pi's shared signed-wrapper ancestry actually holds the session at the inner engine pid, not an outer wrapper pid. Hop limit raised from 8 to 16 to cover the deeper bg-spare chain. * no-mistakes(review): Add nested-claude-ancestry regression test; fix nudge doc depth claim * no-mistakes: apply CI fixes * fix: conferma l'avvio del watcher su Windows/MSYS (kunchenguid#1212) * fix: confirm watcher startup on MSYS * no-mistakes(review): gate MSYS arm ready timeout, cache uname, harden locale test * no-mistakes(review): validate OpenCode ready timeout, make uname cache internal * fix(spawn): forward CLAUDE_CONFIG_DIR to claude crewmates (kunchenguid#1195) * fix(spawn): forward firstmate's CLAUDE_CONFIG_DIR to claude crewmates Crewmate panes are created by a long-lived tmux/herdr daemon that does not inherit firstmate's current environment. When firstmate runs under a non-default CLAUDE_CONFIG_DIR (for example a work-vs-personal subscription split), a bare `claude` in the crewmate pane fell back to the default ~/.claude store and launched unauthenticated, blocking the crewmate before it could do any work. fm-spawn now prefixes the claude launch with firstmate's own resolved CLAUDE_CONFIG_DIR when set, so the crewmate uses the same credential/config store firstmate is authenticated with. An unset value is the single-store default and adds no prefix; non-claude harnesses are unaffected. Adds three tests in fm-spawn-dispatch-profile.test.sh (forwarded-when-set, omitted-when-unset, non-claude-ignored) and pins CLAUDE_CONFIG_DIR in the test helper so launch assertions no longer depend on the developer's environment. * no-mistakes: apply CI fixes * fix: preserve dispatch identity across authentication checks (kunchenguid#1233) * fix: preserve dispatch harness identity * no-mistakes(review): Fix Grok counterfactual tuple validation * no-mistakes(document): Scope dispatch authentication to selected tuple * fix: restore dispatch instruction budget * no-mistakes(review): Scope dispatch authentication after candidate selection * fix(bin): normalize relative durable paths (kunchenguid#1256) * fix(bin): handle dash-leading harness process names (#2) * fix: handle dash-leading harness process names * no-mistakes(review): Make dash-leading harness regression hermetic * fix: preserve secondmate reply routes across relative homes Resolve relative home, data, and state inputs before durable charter generation, and fail when caller-relative directories cannot be resolved. Use absolute paths at the related spawn, AFK daemon, and X-mode cross-process handoffs so later processes cannot reinterpret them from another working directory. * no-mistakes(review): Preserve absolute overrides and normalize relative durable paths * no-mistakes(review): Normalize relative home before deriving durable paths * no-mistakes(document): Document relative durable-path normalization * no-mistakes(review): Captain: Ignore inherited CDPATH during relative path normalization * no-mistakes(lint): Fix empty CDPATH assignments for ShellCheck * refactor(skills): make Bearings chat-only by default (kunchenguid#1136) * Add internal status skill * no-mistakes(document): register /status skill in documentation-audiences inventory * no-mistakes(lint): replace grep|wc -l with grep -c in status skill test * test: silence literal status skill patterns * Refactor bearings default to chat-only --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com> * Clarify follow-up routing during validation (kunchenguid#1277) * fix: honor concrete approval for project operations (kunchenguid#1272) * docs: add captain-approved project operation exception to hard rule 1 Firstmate stays read-only over projects by default, but when the captain clearly approves a concrete project operation and scope in the moment, firstmate may perform exactly that approved operation with its own tools. The approval is never inferred, broadened, or standing, and it does not relax the existing force, discard, unlanded-work, or merge-authority boundaries. * no-mistakes(review): Clarify captain-approved project operation boundaries * no-mistakes(document): Clarify captain-approved project operation scope * docs: cover directories and preserve the operation-or-scope alternative Widen the captain-approved project operation exception in AGENTS.md to files or directories, and restore the explicit operation-or-scope alternative that a prior pipeline auto-fix had collapsed into "and". Rework project-management SKILL.md's Remove section, which previously told firstmate to refuse project removal until a guarded helper existed; that helper was never built, so the text directly contradicted the new instruction-only exception. It now points at the exception plus the existing removal preflight it still requires unchanged. Update the one instruction-owners test assertion that hard-coded the sentence removed above, so the suite tracks current, not obsolete, text. * docs: add captain-approved project operation exception to hard rule 1 Firstmate stays read-only over projects by default, but when the captain clearly approves a concrete project operation and scope in the moment, firstmate may perform exactly that approved operation with its own tools. The approval is never inferred, broadened, or standing, and it does not relax the existing force, discard, unlanded-work, or merge-authority boundaries. * no-mistakes(review): Clarify captain-approved project operation boundaries * no-mistakes(document): Clarify captain-approved project operation scope * docs: cover directories and preserve the operation-or-scope alternative Widen the captain-approved project operation exception in AGENTS.md to files or directories, and restore the explicit operation-or-scope alternative that a prior pipeline auto-fix had collapsed into "and". Rework project-management SKILL.md's Remove section, which previously told firstmate to refuse project removal until a guarded helper existed; that helper was never built, so the text directly contradicted the new instruction-only exception. It now points at the exception plus the existing removal preflight it still requires unchanged. Update the one instruction-owners test assertion that hard-coded the sentence removed above, so the suite tracks current, not obsolete, text. * no-mistakes(review): Align project removal preflight with approved exception * no-mistakes(document): Align project removal documentation with approved exception * fix: restore removal test byte-for-byte and preserve the default sentence tests/fm-instruction-owners.test.sh had been changed to assert different text; restore it byte-for-byte to origin/main. project-management SKILL.md's Remove section now keeps the exact default "Never issue a raw removal command from Firstmate." sentence that test still asserts, immediately followed by the already-approved captain-operation-or-scope exception, so the default and the exception both stay explicit and consistent. * no-mistakes(document): Align project-write boundary documentation * fix(skills): route new project intake through secondmate scopes (kunchenguid#1275) * Route project intake through secondmate scopes * no-mistakes(test): Guard all main-home project registry mutations * no-mistakes(document): Consolidate secondmate routing documentation * no-mistakes: apply CI fixes * Restore new-project routing scope * no-mistakes(document): Clarify secondmate routing for new-project intake * no-mistakes: apply CI fixes * fix: scope validation corrections by accepted behavior (kunchenguid#1281) * fix: scope validation corrections by accepted behavior * no-mistakes(review): Classify stale delivery evidence as an autonomous correction * test: replace source assertions with behavioral coverage (kunchenguid#1282) * test: remove source-content assertions * no-mistakes(review): Replace source assertions with runtime behavior coverage * no-mistakes(review): Isolate Kimi task temp runtime coverage * no-mistakes(document): Refresh test cleanup documentation * no-mistakes: apply CI fixes * fix(watch): escalate busy workers with no completed turn (kunchenguid#1286) * fix(watch): bound how long a busy pane may run with no completed turn A busy pane (backend busy state or the harness's rendered footer) was unconditional, unbounded proof of liveness in every escalation path, so a hung foreground tool call behind a busy signature could run for hours undetected (2026-07 hibit-agent-focus-nonsteal-r1 incident: a catastrophic- backtracking regex hung one bash call for 25h behind an unchanging "Working..." footer). FM_BUSY_TURN_MAX_SECS (default 3600s) now bounds how long a busy pane may run with no completed turn (state/<id>.turn-ended, or its spawn record before any turn has completed). Past the bound, busy_turn_over_age routes the pane through the existing wedge_timer_check, reusing the identical stale reason, escalation counter, and demand-deep-inspection marker for human inspection only - never an automatic interrupt, signal, or restart of the worker or its tool process. A completed turn resets the age. Reproduced end-to-end against the real installed Pi TUI: a foreground `sleep 999999` bash call with no timeout renders the actual busy footer, and two captures ~15s apart show the elapsed counter changing the pane hash while the same turn stays unfinished. Running the pre-fix watcher against the real captures showed it never starts a wedge timer no matter how long the pane stays busy; the fixed watcher starts and escalates the timer through the same mechanism, while the real hung process remained untouched and alive throughout. * no-mistakes(review): fix: parse enriched AFK stale reasons * no-mistakes(review): fix: preserve enriched wedges during AFK supervision * no-mistakes(review): fix: route all enriched AFK wedges * no-mistakes(document): Clarify busy-turn age supervision documentation * fix(gitignore): ignore config/ as a directory, not by exact filename (kunchenguid#1261) A name-by-name list of config/ entries silently stops ignoring any new or home-local file placed there, which makes the working tree read as dirty and blocks guarded sync paths that refuse to touch a dirty home. AGENTS.md already documents config/ as captain-private and gitignored as a category; this makes .gitignore match that contract. * fix(tests): replace source-content .gitignore assertion with behavioral coverage (kunchenguid#1304) The second assertion in fm-gitignore-config.test.sh (added by kunchenguid#1261) greps .gitignore for a specific spelling of the config/ ignore pattern. It fails on a semantically equivalent pattern like config/** and does not prove Git actually ignores anything, per the completed source-content-test audit. Replace it with a real git check-ignore control test on a generated unrelated path, and strengthen the existing directory-coverage test with generated unpredictable direct and nested config/ paths. * feat: bound and consolidate startup memory during stow (kunchenguid#1303) * Add bounded startup memory curation * no-mistakes(review): Record reproducible stow verification evidence * no-mistakes(review): Validate inherited secondmate stow evidence * no-mistakes(document): Document editable startup-memory budget propagation * feat(bin): mark crewmate and scout steers as from-firstmate A steer lands in the receiving agent's own chat, where nothing else told firstmate's instructions apart from a human typing into that pane. The gap was proven in both directions on 2026-07-26: the captain opened a crewmate pane believing it was firstmate and issued cross-lane instructions there, and a Pi crewmate at an ask-user gate addressed "Captain, ..." into its own pane and sat parked - nobody reads a crewmate pane, and a parked pipeline emits no wake, so that direction fails silently. AGENTS.md section 1 rule 4 already required workers to honor a distinction the system gave them no means to make. fm-send now applies the existing from-firstmate carrier to every text steer whose target resolves through this home's meta, not just kind=secondmate. A crewmate or scout carries the marker alone; the corr= correlation token and the parent pending-reply record stay secondmate-only, because a crewmate already answers on its own status file. Explicit backend targets and the --key path are unchanged. Command-shaped text is the one exclusion. A harness recognizes a slash command, or a codex $<skill> invocation, only at the very start of the composer line, so any prefix silently demotes it to prose. Verified on claude 2.1.220 and pi 0.82.0: with either marker shape prepended, /no-mistakes stops opening the completion popup entirely and would submit as ordinary text. Crewmate sends of that shape therefore stay unmarked and byte-identical, which also keeps every documented popup hazard out of this change's blast radius: the only bytes that move are plain text no harness parses specially. The exclusion deliberately does not reach a secondmate, whose marker is what creates its reply guarantee. The ship and scout scaffolds gain a "Who is speaking to you" section teaching the reader side: marked is firstmate, unmarked is a human who may believe the pane is firstmate, self-identify as a worker on this task before acting, and escalation is always the status file. AGENTS.md states the provenance principle once in rule 4; the away-mode stub and the secondmate charter keep their own distinct consequences. * no-mistakes(review): align brief's unmarked-message exception wording to fm-send predicate * no-mistakes(test): fix stale corr-less assertion in Pi/Herdr marker e2e * no-mistakes(document): generalize task-selector marker context to from-firstmate --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com> Co-authored-by: Christopher McKay <101884182+karotkriss@users.noreply.github.com> Co-authored-by: Daniel Kuykendall IV <danielkuykendall23@gmail.com> Co-authored-by: Trillium Smith <Spiteless@gmail.com> Co-authored-by: Unknownzed <45267749+Unknownzed@users.noreply.github.com> Co-authored-by: lhalbert <lucashalbert@users.noreply.github.com> Co-authored-by: AG <ag@agw3.org> Co-authored-by: deeto15 <92119640+deeto15@users.noreply.github.com>
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
Make it possible for a firstmate worker (crewmate or scout) to tell who is speaking to it, by extending the EXISTING from-firstmate message marking to crewmate/scout steers and teaching the generated brief to read the marker.
Why: proven in both directions on 2026-07-26. The captain opened a crewmate pane believing it was firstmate and issued cross-lane instructions there (that incident produced PR 1084's self-identification rule, still open); and a Pi crewmate parked at an ask-user gate addressed 'Captain, ...' into its own pane and sat there past ten minutes - nobody reads a crewmate pane and a parked pipeline emits no wake, so that direction fails silently. AGENTS.md section 1 rule 4 already required workers to honor a distinction the system gave them no mechanism to make. bin/fm-send.sh deliberately excluded crewmate/scout targets from the marking it already applied to secondmate requests and launch briefs, so every steer arrived looking exactly like something the captain might have typed.
This shape was reviewed and explicitly approved by firstmate before implementation; the design doc is data/pi-crewmate-addresses-captain/design.md. Four decisions were made there and are deliberate, not oversights:
REUSE the existing from-firstmate carrier (label + U+2063) rather than adding a new generic 'crew-steer' kind to FM_OPERATIONAL_KINDS. Chosen because fm_message_from_firstmate() already means exactly 'firstmate sent this', it needs no change to the shell kind enum, the Pi TS kind lists, or Calm classification, and the wire form is already E2E-verified through fm-send on tmux and herdr. A new kind was considered and rejected as wider blast radius for no behavioural gain.
COMMAND-SHAPED TEXT IS DELIBERATELY LEFT UNMARKED for crewmate/scout targets. This is the single most important decision in the change and it is empirically grounded, not a convenience hole. A harness recognizes a slash command, or a codex $ invocation, only at the very start of the composer line. Measured 2026-07-28 on claude 2.1.220 and pi 0.82.0 by typing the bytes with tmux send-keys -l and capturing the pane: plain '/no-mistakes' opens the completion popup, while both '[fm-from-firstmate]<U+2063>/no-mistakes' and '<U+2063>FIRSTMATE_OP: v1 crew-steer: /no-mistakes' suppress it entirely and would submit as ordinary prose. Blanket marking would therefore have silently broken every '/no-mistakes' validation trigger - firstmate's most common crewmate steer and the one that drives the whole delivery pipeline - with no error, just a pipeline that never starts. The exclusion predicate deliberately reuses the SAME classification fm-send already computed for its pre-Enter popup settle, so it is not a new ad-hoc rule; the duplicated case statement was collapsed into one COMMAND_SHAPED variable that now drives both the marker exclusion and the settle, preserving the exact prior settle values (1.2 for slash, 1.2 for $ on codex, 0.3 otherwise). A leading $ on a NON-codex harness is ordinary text ('$5/month', '$HOME') and is still marked - the exclusion must not widen.
The command-shaped exclusion deliberately does NOT extend to a kind=secondmate target, even though the same breakage would apply there. A secondmate's marker is what creates its corr= correlation token and its durable parent pending-reply record, so dropping the marker would silently lose the reply guarantee - a worse failure than prose. No current caller sends a secondmate a command-shaped message (every secondmate send is natural language: bin/fm-bootstrap.sh's nudge, the updatefirstmate skill, the config-reread nudge), and this latent hazard is documented in the fm-send header as the cheapest mitigation firstmate asked for, with a test pinning that the exclusion did not leak there. Widening or fixing that was explicitly ruled OUT of scope.
Marking now applies to ANY task selector resolved through this home's meta whose kind is not secondmate, including a meta predating the kind= field (empty kind reads as the crewmate default - the correct fallback, pinned by a test). Explicit backend-target sends and the --key path stay unmarked and byte-identical, matching the landed secondmate rule exactly; an explicit target is the escape hatch for endpoints outside this home, so the recipient's identity is not known from a durable record.
Also deliberate:
Verification performed: the shared composer classifier (bin/fm-composer-lib.sh, the one owner of the empty/pending verdict) reads marked-but-unsubmitted text as 'pending' and empty as 'empty', so a swallowed Enter on a marked message cannot be misread as a successful submit. Real fm-send against a real claude pane delivered marked plain steers with rc=0, composer cleared, agent responded. '/help' and '/clear' steers arrived unmarked and were recognized as commands, and running the PRE-CHANGE fm-send.sh side by side on '/help' produced an identical outcome, confirming the command-shaped path is byte-identical. codex, opencode, grok and kimi are NOT installed in this worktree and were not live-verified; per firstmate's explicit decision the structural argument is accepted and must be stated plainly in the PR body - the only bytes that move are plain text no harness parses specially, because command-shaped text is exactly what stays unmarked. The crewmate case added to tests/fm-send-secondmate-marker-herdr-e2e.test.sh was written but NOT executed, because this task's brief was scaffolded without --herdr-lab and therefore forbids driving Herdr lifecycle; that must be flagged rather than presented as proven.
Three test failures in the portable-serial lane are PRE-EXISTING and were confirmed to fail identically on the unmodified baseline: fm-backend-tmux-smoke (real-tmux window creation in this worktree), fm-calm-pi-extension and fm-pi-watch-extension (Node ERR_UNKNOWN_FILE_EXTENSION on .ts, and no .ts file was touched). bin/fm-lint.sh and bin/fm-doc-audience-check.sh are clean; portable lanes 1 and 2 pass.
What Changed
bin/fm-send.shnow applies the existing from-firstmate marker (label + U+2063 carrier) to every text steer whose target is a task selector resolved through this home's meta, not justkind=secondmate— crewmate and scout steers carry the marker alone, while secondmate requests keep the corr= token and durable pending-reply record; an empty/missingkindfalls back to the crewmate behavior. Command-shaped text (leading/, or$on a codex target) deliberately stays unmarked and byte-identical for crewmate/scout, because any prefix demotes a slash command or codex$<skill>invocation to plain prose; that exclusion does not extend to secondmate targets, whose marker is what carries the reply guarantee. The classification was collapsed into a singleCOMMAND_SHAPEDvariable that drives both the marker exclusion and the pre-Enter popup settle, preserving the prior settle values. Explicit backend targets and the--keypath remain unmarked.bin/fm-brief.shcrewmate and scout scaffolds gain a named "# Who is speaking to you" section that teaches the worker to read the marker, states the command-shaped exception, and restates that escalation always goes through the status file; only the marker's shape is shared (oneFROMFIRST_MARKER_FACTvariable) and the secondmate charter keeps its own distinct consequence.AGENTS.mdgains one provenance sentence pointing atbin/fm-operational-input.sh, and the harness-adapters skill gains a one-line warning that a new leading sigil must be taught to fm-send's command-shaped classification.tests/fm-send-secondmate-marker.test.shwas renamed totests/fm-send-marker.test.shsince its subject is now the whole marking policy; five existing tests pinning the old "crewmate stays unmarked" contract were updated; and the opt-in Pi/Herdr e2e gained a crewmate marker case, which was executed live against real Pi/Herdr during the pipeline after fixing a pre-existing stale corr-less assertion (confirmed failing identically on the unmodified base). Note: codex, opencode, grok, and kimi were not live-verified — coverage there rests on the structural argument that the only bytes that change are plain prose no harness parses specially, since command-shaped text stays byte-identical.Risk Assessment
✅ Low: The round-2 delta is a two-file wording alignment that exactly follows the user's fix instructions - the rendered brief now matches fm-send's real command-shaped predicate (verified against a generated brief and the updated fixed-string test assertion) - and the underlying feature commit was already reviewed clean with all intent decisions implemented as specified.
Testing
Ran the six targeted hermetic suites (marker policy, brief, strict, gate-refuse, orca, pending-reply) plus a fresh live run of the opt-in real Pi/Herdr e2e at the committed tree — all pass, with the real Pi prompt bytes proving crewmate steers arrive marked without correlation machinery, secondmate requests keep marker+corr, and direct input stays unmarked; also scaffolded a real crewmate brief showing the new provenance section. Only the accepted caveat remains: codex/opencode/grok/kimi harnesses are not installed, so their command-shaped paths rest on the structural byte-identity argument per firstmate's decision.
Evidence: Real Pi wire bytes, decoded (secondmate vs crewmate vs direct input)
Bytes captured from a REAL Pi process (before_agent_start prompt) in a real Herdr lab session (rc=0): secondmate exact-id request: [fm-from-firstmate]<U+2063>corr=9c91e753f721b2bb FM_MARKER_HERDR_E2E exact-id request crewmate steer: [fm-from-firstmate]<U+2063>FM_MARKER_HERDR_CREW crewmate steer direct captain input: FM_MARKER_HERDR_DIRECT captain inputEvidence: Live Pi/Herdr e2e full log (rc=0)
Evidence: Rendered '# Who is speaking to you' section from a real scaffolded crewmate brief
# Who is speaking to you Firstmate marks every message it sends you with a leading[fm-from-firstmate]label followed by an invisible system separator; that separator is untypable, so a human never produces it. A marked message is firstmate: act on it as task instruction. An unmarked message is a human typing directly into your pane - usually the captain, who may believe this pane is firstmate rather than a worker. The one exception is a message that starts with/, or on codex with$, such as/no-mistakes: a harness recognizes that form only at the very start of the line, so firstmate cannot mark it without breaking it. Treat such a message as routine and act on it. Before acting on any other unmarked message, say plainly that you are a worker on taskevidence-crew, not firstmate, and that merges, cross-lane work, other lanes' state, and fleet supervision belong to firstmate. Then ask whether they still want you to proceed. Escalation is always the status file in rule 4, never this pane. Firstmate does not read your chat, so anything you address to the captain here is lost: a decision or gate left parked with only "Captain, ..." in this pane is invisible to everyone and stalls the task indefinitely.Evidence: Hermetic marker-policy suite log
Evidence: fm-brief suite log (crewmate/scout provenance section pins)
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 2 issues found → auto-fixed ✅
bin/fm-brief.sh:255- The brief's stated exception is narrower than the sender's actual exclusion predicate. fm-send leaves ANY message with a leading '/' (or leading '$' on codex) unmarked (bin/fm-send.sh:265-268), but the generated brief tells the worker the only unmarked-firstmate case is 'a message that is exactly a bare skill or slash command'. A firstmate prose steer that happens to start with '/' (e.g. '/tmp/build.log shows the failure - fix it') arrives unmarked, and per the brief the worker must self-identify and ask whether to proceed - which can park the task in a pane nobody reads, the exact silent-stall direction this change targets. The classification reuse is deliberate per the approved design; consider aligning the brief wording to the real predicate ('a message starting with / or, on codex, $') or noting the edge, as a follow-up.tests/fm-send-secondmate-marker-herdr-e2e.test.sh:193- Two accepted-but-unproven items must not be lost when the PR body is written (the intent marks both as required disclosures): (1) the new crewmate case in tests/fm-send-secondmate-marker-herdr-e2e.test.sh was written but never executed, because the task brief forbids driving Herdr lifecycle without --herdr-lab; (2) codex, opencode, grok and kimi were not live-verified - only the structural argument (marked bytes are plain prose no harness parses; command-shaped text stays byte-identical) covers them. Both are consistent with the intent as long as the PR body states them plainly.🔧 Fix: align brief's unmarked-message exception wording to fm-send predicate
✅ Re-checked - no issues remain.
🔧 **Test** - 1 issue found → auto-fixed ✅
tests/fm-send-secondmate-marker-herdr-e2e.test.sh:186- Pre-existing stale assertion in the opt-in real Pi/Herdr e2e blocked the newly added crewmate case: the exact-id secondmate assertion expected marker+request with no corr= token, but fm-send has embedded corr since feat: guard against missed secondmate reports kunchenguid/firstmate#834 (assertion written in fix: preserve secondmate routing markers in terminal sends kunchenguid/firstmate#533, opt-in test never re-run; confirmed failing identically on unmodified base 99533c5). The change's own updated header claims the exact-id case proves 'marker plus correlation token plus parent pending-reply record', contradicting the assertion. Fixed the assertion to expect marker+corr+request exactly once and to require the pending-reply record; the full e2e then passed live against real Pi/Herdr, executing the crewmate case the author had flagged as written-but-unproven.bin/fm-test-run.sh tests/fm-send-marker.test.sh tests/fm-brief.test.sh(12 + 18 assertions, all pass)bin/fm-test-run.sh tests/fm-send-strict.test.sh tests/fm-gate-refuse.test.sh tests/fm-backend-orca.test.sh tests/fm-pending-reply.test.sh(all pass, failed=0)bin/fm-test-run.sh --check-coverage(ok after the fm-send-marker.test.sh rename)Manual wire-level transcript: real fm-send.sh against a tmux stub at the send-keys boundary for 9 scenarios (crewmate/scout/kind-less marked;/no-mistakesand codex$no-mistakesunmarked; non-codex$5/monthmarked; secondmate marker+corr kept for command-shaped; explicit target and --key unmarked)Baseline byte-identity: same sends through the pre-change fm-send from base 99533c5 — all unmarked paths byte-identicalGenerated real ship and scout briefs viabin/fm-brief.sh; verified the '# Who is speaking to you' section renders with the concrete task id and the secondmate charter keeps its own consequenceFM_SEND_MARKER_HERDR_E2E=1 bin/fm-test-run.sh tests/fm-send-secondmate-marker-herdr-e2e.test.sh— real Pi via Herdr lab: exact-id marker+corr, crewmate marker-only, direct input unmarked (passes after fixing a stale pre-existing assertion; confirmed the failure reproduced on unmodified base first)🔧 Fix: fix stale corr-less assertion in Pi/Herdr marker e2e
✅ Re-checked - no issues remain.
FM_SEND_MARKER_HERDR_E2E=1 bash tests/fm-send-secondmate-marker-herdr-e2e.test.sh— live Pi/Herdr run, rc=0, all three routing cases (secondmate marker+corr, crewmate marker-only, direct input unmarked)bash tests/fm-send-marker.test.sh— full marking policy: crewmate/scout marked, command-shaped exclusion, exclusion not leaking to secondmate, non-codex$still marked, explicit/--key unmarked, idempotency, U+2063 shapebash tests/fm-brief.test.sh— crewmate/scout '# Who is speaking to you' section, secondmate charter keeps its own consequencebash tests/fm-send-strict.test.shbash tests/fm-gate-refuse.test.shbash tests/fm-backend-orca.test.shbash tests/fm-pending-reply.test.sh— marked crewmate send creates no pending-reply expectationManual: scaffolded a real crewmate brief viabin/fm-brief.shand extracted the rendered '# Who is speaking to you' sectionManual: decoded the e2e's captured real-Pi received-hex bytes into human-readable wire forms for all three casesdocs/verification/runtime-backends.md:248- The crewmate marker case added to tests/fm-send-secondmate-marker-herdr-e2e.test.sh was written but not executed (this task forbade Herdr lifecycle), so docs/verification/runtime-backends.md's maintainer-verification section cannot yet cite live evidence for crewmate marker delivery; a follow-up FM_SEND_MARKER_HERDR_E2E=1 herdr-lab run should execute it and record the observed guarantee there.✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.