docs: align tmux and harness guidance - #3
Merged
Merged
Conversation
…watcher env vars, toolbelt, untracked data/)
e-jung
pushed a commit
to e-jung/firstmate
that referenced
this pull request
Jul 1, 2026
--force bypasses teardown's work-not-landed, dirty, scout-report, and secondmate-child safety checks, so it can discard unreviewed work. Per prime directive kunchenguid#3 firstmate must never self-authorize it, but the script had no structural barrier: firstmate could run --force on its own judgment. Add a two-step guard that separates decision from authorization: 1. The captain explicitly OKs discarding THIS task's work. 2. firstmate records that as state/<id>.force-granted. 3. firstmate runs fm-teardown.sh <id> --force. --force takes effect only when the token exists; without it, --force is INERT (FORCE is cleared, normal safety checks run). The token is consumed on every --force invocation (success or refuse), so each force-teardown needs a fresh captain OK and a stale token can never carry over. Every --force invocation is logged to state/.force-audit.log with timestamp, task id, caller pid, and the authorization verdict. By reassigning FORCE based on authorization, all existing --force comparisons behave correctly with no changes to the rest of the teardown logic. Update the force-teardown tests to grant the token (they model captain-authorized discards), and add coverage for the inert-without-token, token-consumed, and audit-log behaviors. Document the two-step model in AGENTS.md (prime directive kunchenguid#3, secondmate teardown, state layout).
e-jung
pushed a commit
to e-jung/firstmate
that referenced
this pull request
Jul 1, 2026
--force bypasses teardown's work-not-landed, dirty, scout-report, and secondmate-child safety checks, so it can discard unreviewed work. Per prime directive kunchenguid#3 firstmate must never self-authorize it, but the script had no structural barrier: firstmate could run --force on its own judgment. Add a two-step guard that separates decision from authorization: 1. The captain explicitly OKs discarding THIS task's work. 2. firstmate records that as state/<id>.force-granted. 3. firstmate runs fm-teardown.sh <id> --force. --force takes effect only when the token exists; without it, --force is INERT (FORCE is cleared, normal safety checks run). The token is consumed on every --force invocation (success or refuse), so each force-teardown needs a fresh captain OK and a stale token can never carry over. Every --force invocation is logged to state/.force-audit.log with timestamp, task id, caller pid, and the authorization verdict. By reassigning FORCE based on authorization, all existing --force comparisons behave correctly with no changes to the rest of the teardown logic. Update the force-teardown tests to grant the token (they model captain-authorized discards), and add coverage for the inert-without-token, token-consumed, and audit-log behaviors. Document the two-step model in AGENTS.md (prime directive kunchenguid#3, secondmate teardown, state layout).
kunchenguid
added a commit
that referenced
this pull request
Jul 4, 2026
* docs: trim AGENTS.md redundancy (diet PR 3/3) Consolidates five duplicated passages to a single owner each, per data/agentsmd-diet-s2/report.md redundancy items c3-c7: - Inheritable-config propagation mechanism: owned by section 3 (where the sweep runs); sections 4 and 7 keep compact references. Section 4 retains its one genuinely unique fact (crew-harness inherit-vs-fallback semantics), just no longer restates the propagation mechanism itself. - Landed-work definition: owned by section 7's ship-teardown detail (PR-containment mechanics, pr= discovery fallback); section 1's hard rule #3 keeps the rule plus a three-case summary and a pointer. - Backend meta-field enumeration: owned by docs/configuration.md ("Runtime backend", already comprehensive including cmux) and each backend's own doc; AGENTS.md keeps only the fields common to every task plus a pointer. - Dropped one redundant restatement of "silence is correct while waiting" in section 8. - Worktree-tangle guard explanation: owned by section 8 (already the fuller, cross-referenced version); section 3's TANGLE bullet keeps the remediation action and points at section 8 for the why. Also adds two captain-requested single-sentence rules: invoke bin/ scripts by absolute $FM_ROOT path after any cd away from the home, and a backend spawn refusal must be surfaced to the captain rather than silently worked around by switching backends. AGENTS.md: 901 -> 889 lines, 112355 -> 108560 bytes. * no-mistakes(review): Clarify post-cd bin invocation guidance * no-mistakes(document): Sync AGENTS trim docs * no-mistakes(lint): Fix Markdown line style
DQ4443
referenced
this pull request
in DQ4443/firstmate
Jul 6, 2026
* docs: trim AGENTS.md redundancy (diet PR 3/3) Consolidates five duplicated passages to a single owner each, per data/agentsmd-diet-s2/report.md redundancy items c3-c7: - Inheritable-config propagation mechanism: owned by section 3 (where the sweep runs); sections 4 and 7 keep compact references. Section 4 retains its one genuinely unique fact (crew-harness inherit-vs-fallback semantics), just no longer restates the propagation mechanism itself. - Landed-work definition: owned by section 7's ship-teardown detail (PR-containment mechanics, pr= discovery fallback); section 1's hard rule #3 keeps the rule plus a three-case summary and a pointer. - Backend meta-field enumeration: owned by docs/configuration.md ("Runtime backend", already comprehensive including cmux) and each backend's own doc; AGENTS.md keeps only the fields common to every task plus a pointer. - Dropped one redundant restatement of "silence is correct while waiting" in section 8. - Worktree-tangle guard explanation: owned by section 8 (already the fuller, cross-referenced version); section 3's TANGLE bullet keeps the remediation action and points at section 8 for the why. Also adds two captain-requested single-sentence rules: invoke bin/ scripts by absolute $FM_ROOT path after any cd away from the home, and a backend spawn refusal must be surfaced to the captain rather than silently worked around by switching backends. AGENTS.md: 901 -> 889 lines, 112355 -> 108560 bytes. * no-mistakes(review): Clarify post-cd bin invocation guidance * no-mistakes(document): Sync AGENTS trim docs * no-mistakes(lint): Fix Markdown line style
DQ4443
referenced
this pull request
in DQ4443/firstmate
Jul 6, 2026
* fix: anchor turn-end Stop hook to project root (kunchenguid#256) * Fix turn-end Stop hook to use CLAUDE_PROJECT_DIR path Claude Code runs hook commands via /bin/sh from the session cwd, so the bare relative bin/fm-turnend-guard.sh path fails when cwd is not the repo root. Anchor the command with "$CLAUDE_PROJECT_DIR"/bin/fm-turnend-guard.sh instead; verified CLAUDE_PROJECT_DIR is set on Stop hooks in Claude Code 2.1.201. Document the cwd caveat and add a settings.json regression test. * no-mistakes(document): Document Stop hook path anchoring * docs: trim firstmate agent guidance duplication (kunchenguid#258) * docs: trim AGENTS.md redundancy (diet PR 3/3) Consolidates five duplicated passages to a single owner each, per data/agentsmd-diet-s2/report.md redundancy items c3-c7: - Inheritable-config propagation mechanism: owned by section 3 (where the sweep runs); sections 4 and 7 keep compact references. Section 4 retains its one genuinely unique fact (crew-harness inherit-vs-fallback semantics), just no longer restates the propagation mechanism itself. - Landed-work definition: owned by section 7's ship-teardown detail (PR-containment mechanics, pr= discovery fallback); section 1's hard rule #3 keeps the rule plus a three-case summary and a pointer. - Backend meta-field enumeration: owned by docs/configuration.md ("Runtime backend", already comprehensive including cmux) and each backend's own doc; AGENTS.md keeps only the fields common to every task plus a pointer. - Dropped one redundant restatement of "silence is correct while waiting" in section 8. - Worktree-tangle guard explanation: owned by section 8 (already the fuller, cross-referenced version); section 3's TANGLE bullet keeps the remediation action and points at section 8 for the why. Also adds two captain-requested single-sentence rules: invoke bin/ scripts by absolute $FM_ROOT path after any cd away from the home, and a backend spawn refusal must be surfaced to the captain rather than silently worked around by switching backends. AGENTS.md: 901 -> 889 lines, 112355 -> 108560 bytes. * no-mistakes(review): Clarify post-cd bin invocation guidance * no-mistakes(document): Sync AGENTS trim docs * no-mistakes(lint): Fix Markdown line style * feat(backends): improve cmux detection and socket-mode guidance (kunchenguid#259) * feat(backends): cmux detection fallbacks and socket-mode matrix Workstream A: cmux's bundled claude wrapper strips every CMUX_* env var on its passthrough path (reproduced live 2026-07-04, cmux 0.64.17), so a claude-harness firstmate inside a cmux tab has no CMUX_WORKSPACE_ID. fm_backend_detect now falls back - macOS-only, only when the primary marker is absent - to __CFBundleIdentifier=com.cmuxterm.app and then a process ancestry walk resolved by bundle id (lsappinfo) plus a bundle-shaped ps comm match. Innermost-first ordering is unchanged and absorbs the tmux-inside-cmux bundle-id false positive; the auto-detect NOTICE names the winning fallback signal. Workstream B: the five socketControlMode values were traced through cmux source (commit 9c91710e3f58): off/cmuxOnly can never admit an external CLI, automation admits same-user clients with no secret (0600 socket only), password needs the auth handshake, allowAll opens the socket to every local user (0666). Automation mode is now the documented recommendation; the adapter's refusals name every viable mode, classify Invalid password as unauth, and the launch-timeout message names the off-mode possibility. Docs carry the wrapper-strip empirical record, the fallback contract and authority split, and the full mode matrix with rationale; tests cover the new detection paths, the nested false positive, and the refusal wording. * no-mistakes(review): Document cmux fallback detection * no-mistakes(review): Update cmux architecture docs * no-mistakes(document): Align cmux backend docs * fix(backends): scope zellij tabs by firstmate home (kunchenguid#252) * fix(backends): home-scope zellij tab titles to close cross-home collision gap Zellij's one shared "firstmate" session has no per-home split and enforces no tab-name uniqueness, so two firstmate homes with colliding task ids could send/peek/close each other's tabs - the same gap a no-mistakes review gate caught for cmux (docs/cmux-backend.md). Ports that fix: every new tab is created with a home-scoped title (fm-<home-label>-<id>), and every list/find/recover/kill path scopes matches to this home's own tag. A tab spawned before this change still matches via its old untagged bare title, but only when unambiguous - two live tabs sharing a bare title refuse rather than guessing which one is ours. Factors the home-label/hash derivation shared with cmux into bin/fm-backend-hometag-lib.sh so the two adapters can't drift. * no-mistakes(review): Fix zellij child teardown home tag * no-mistakes(review): Fix zellij teardown and selector scoping * no-mistakes(document): Sync zellij home-scope docs * Rewrite operating manual for the dynamic-workflow paradigm Replace the crewmate/tmux-window dispatch model with in-session dynamic workflows. Remove harness adapters, crew spawn/teardown/supervision, pane peeking, stale-crew detection, and multi-backend machinery. Promote the board-v2 interface and the design/red-team/merge gates to first-class sections. Preserve firstmate persistence in tmux, the board pollers and watcher for board input, delivery modes and the no-mistakes gate, secondmates (flagged for David's confirmation), captain.md-at-startup, bootstrap/recovery, backlog, self-update, and X mode. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Finalize AGENTS.md for the dynamic-workflow paradigm Reconcile the d3d3c7a rewrite down to the final workflow-era manual: the finalA draft as the base, folding in the load-bearing board contract from d3d3c7a (section semantics as whose-turn, one item one row, MVP-tracker naming, scan-all-threads-before-seen) and applying the synthesis amendments (Kronos tickets plus cite-repo-files-by-path in section 3, the built-in verify then project-verify then no-mistakes done stack in section 5, the transfer-file maintenance discipline in section 10, the reconciled escape-hatch and skill dispositions in section 12). Secondmates are dropped outright; the tmux-crewmate and watcher stack moves to the documented escape hatch. CLAUDE.md is a symlink to this file. * Add workflow-paradigm machinery: poller, write fence, board check-in fm-poll.sh is the launchd-supervised (com.firstmate.poller, KeepAlive) board/merge/stall poller that replaces the agent-armed watcher for daily duty: it loops state/*.check.sh on the check contract, delivers wakes through the durable queue in fm-wake-lib.sh, keeps its own state/.last-poller-beat beacon and a home-scoped singleton guard, and injects a synthetic startup check event so a wake confirms the delivery path end to end. fm-write-fence.sh is the broadened PreToolUse hook: it blocks writes under ~/dev/work except the agent-worktree allowlist (~/dev/work/*/.claude/worktrees and ~/.treehouse), failing open on unreadable payloads. fm-board-checkin.sh writes a real per-item stamp into state/board-checkins.json atomically at each phase boundary. launchd/com.firstmate.poller.plist ships UNLOADED with a header comment: loading it is the human-verified cutover step, with __FM_ROOT__/__FM_HOME__ placeholders substituted then. * Deprecation headers on retired scripts (delete nothing) Mark the retire-marked scripts from the disposition table with a DEPRECATED header: the watcher/guard stack (fm-guard, fm-tangle-lib), the crewmate scaffolding (fm-brief, fm-promote), the afk daemon (fm-supervise-daemon), and the secondmate stack (fm-home-seed, fm-backlog-handoff, fm-config-push, fm-config-inherit-lib, fm-marker-lib). Each stays functional on disk and is removed only in the cleanup PR after the Opus 4.8 transition settles, so rollback stays real. * Regroup docs/scripts.md by workflow-paradigm disposition Restructure the toolbelt reference into New, Kept, Escape hatch, Retired, and Inert sections so each script's disposition is explicit, add the three new scripts plus the launchd plist and the generated stall check, and note that nothing is deleted until the cleanup PR. * Salvage kronos-watch PR/CI awareness into section 5 * Write fence: cover projects/ clones and resolve new paths through symlinks The projects/<name> entries are firstmate's own real clones, not symlinks into ~/dev/work, so the ~/dev/work rule never covered them. Add an explicit block on <FM_ROOT>/projects/** (allowing only projects/<name>/.claude/worktrees/**), and make resolve() walk up to the nearest existing ancestor so a new file created under a symlinked directory still resolves into the real fenced tree instead of slipping past as an unresolved literal. Add colocated behavior tests. * Poller: identity-check the singleton pidfile and rate-limit the startup wake A bare kill -0 on the pidfile trusted a recycled pid: after a kill -9 left a stale pidfile whose pid got reused, the launchd instance read it as "already running" and exited, and KeepAlive + the 10s throttle wedged the poller in a silent respawn loop that never polled the board. Record the process identity (start time + command) alongside the pid and honor the pidfile only when the identity still matches, so a reused pid is treated as dead. Also rate-limit the synthetic startup wake so a crash-respawn loop cannot flood the durable wake queue; a genuine restart still emits it. * Teardown: implement --worktree <path> mode for meta-less workflow worktrees AGENTS.md sections 4 and 6 name bin/fm-teardown.sh --worktree <path> as the sanctioned disposal for a changed worktree, but only the task-id path existed; a workflow worktree has no state/<id>.meta, so the documented command errored. Add a --worktree mode that runs the same landed check keyed on the worktree path (refusing dirty or unlanded work, refusing a main checkout, removing only on a pass or with --force), independent of any meta record. Add colocated tests. * Docs: reconcile manual with the fence, poller, and cutover reality - Rule 2 and section 3: projects/<name> are firstmate's own clones (real dirs), not symlinks into ~/dev/work; describe the write fence as wired at the poller cutover and best-effort behind isolation (fails open), not present-tense sole enforcement. Document the fence-wiring as a hard step of that cutover in the plist header, kept out of tracked settings for this shared-template repo. - Rule 1: the tracker-sync merge authorization is gated on David's per-run okay of the change list; keep the standing merge authority the skill grants. - Budgets and stall detection: mark data/budgets.md and state/workflow-runs.check.sh as built at the cutover, with an interim path. - docs/scripts.md: fence covers projects/ clones; poller pidfile identity check and rate-limited startup wake. * test: pass explicit identity to fm_git_identity (SC2119) --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
mielyemitchell
referenced
this pull request
in mielyemitchell/firstmate
Jul 6, 2026
* feat(dispatch): add dynamic crew profiles (#154)
* feat(dispatch): add dynamic crew profiles
* no-mistakes(review): Captain, document dispatch profile inheritance
* no-mistakes(review): Captain, guard stale dispatch inheritance
* no-mistakes(document): Sync dispatch profile docs
* no-mistakes: apply CI fixes
* fix: harden crew dispatch profile enforcement (#159)
* Harden crew dispatch profile enforcement
* no-mistakes(document): Captain, synced crew dispatch docs
* feat: add live secondmate config push (#161)
* feat(config): add live secondmate config push
* no-mistakes(document): Document config push behavior
* no-mistakes(lint): Clean changed shell lint
* no-mistakes: apply CI fixes
* feat: support image attachments in X replies (#162)
* feat(x): add image attachments to reply helpers
* no-mistakes(review): Stream X image replies safely
* no-mistakes(review): Captain, clean X reply temp tracking
* no-mistakes(document): Document X reply image support
* fix(teardown): make landed PR detection robust (#167)
* fix(teardown): make landed-check robust when no pr= was ever recorded
fm-teardown.sh's squash-merge landed-check already falls back to
discovering a merged PR by branch name when state/<id>.meta has no
recorded pr=, but nothing guaranteed pr=/pr_head= actually got
recorded on a yolo-authorized merge - the "checks green" trigger that
normally runs fm-pr-check.sh never fires on repos with no PR CI, so a
merge done via a bare `gh-axi pr merge` silently skips it.
Add bin/fm-pr-merge.sh as the one path for merging a task's PR: it
always runs fm-pr-check.sh first, so pr=/pr_head= land in meta as part
of the merge itself regardless of any CI signal. Document both the
existing branch-name discovery fallback and the new merge path in
AGENTS.md, and add regression coverage for the no-pr=-recorded landed
scenario and for fm-pr-merge.sh's record-then-merge behavior.
* no-mistakes(review): Guard PR merges on task metadata
* no-mistakes(document): Document PR merge wrapper
* no-mistakes: apply CI fixes
* fix: parse PR merge URLs for gh-axi (#168)
* Fix fm-pr-merge.sh to parse PR URLs for gh-axi
gh-axi pr merge expects a PR number and --repo, not a full GitHub URL.
Parse the URL, default to --squash when no merge method is passed, and
fail fast on malformed URLs. Tests cover parsing, defaults, and refusal.
* no-mistakes(review): Harden PR merge validation
* no-mistakes(review): Harden PR merge URL guards
* no-mistakes(document): Document PR merge URL handling
* no-mistakes(lint): Clean shell lint
* feat(bin): pin secondmate model and effort (#180)
* feat: pin secondmate model/effort in config/secondmate-harness
Extend config/secondmate-harness's format to an optional
"<harness> [<model>] [<effort>]" line so a secondmate can be durably
locked to a concrete model/effort in the same file, without adding a
new config file. A bare harness-only file behaves exactly as before.
fm-harness.sh gains secondmate-model/secondmate-effort accessors;
fm-spawn.sh populates MODEL/EFFORT from them on every secondmate spawn
(including respawns) unless the caller passed an explicit --model/--effort.
* no-mistakes(review): Fix secondmate override pin precedence
* no-mistakes(document): Document secondmate harness pins
* feat(bin): add runtime backend interface (#183)
* feat(bin): extract tmux runtime behind a backend interface (P1)
Add bin/fm-backend.sh (selection, meta helpers, selector resolution,
dispatch) and bin/backends/tmux.sh (the tmux adapter), then route
fm-send.sh, fm-peek.sh, fm-watch.sh, fm-spawn.sh, and fm-teardown.sh
through them. Every default tmux command sequence, meta shape, and
printed output stays byte-identical: missing backend= still means
tmux, and a default spawn never writes backend=tmux.
Adds a --backend flag (tmux-only for now) and FM_BACKEND/config/backend
selection, refusing any unimplemented backend loudly. Names the
watcher's poll loop as the default event-source implementation over the
backend's pull primitives, per the herdr-addendum's events-as-the-core-
abstraction direction, without changing its behavior.
Verification: fake-tmux/treehouse old-vs-new command-log conformance
tests for send/peek/spawn/teardown, a real-tmux smoke test for the
adapter, and the full existing suite passing unmodified (bar two
fixture-only additions in fm-gotmp.test.sh for the new sibling
scripts).
* no-mistakes(review): Captain, harden backend baseline resolution
* no-mistakes(review): Captain, ignore and document backend config
* no-mistakes(review): Captain, make backend tests executable
* no-mistakes(document): Sync runtime backend documentation
* feat(bin): add experimental Herdr runtime backend (#186)
* feat(bin): add experimental herdr runtime backend (P2)
Implements bin/backends/herdr.sh (session-provider adapter, D3: treehouse
stays the worktree provider) wired through fm-backend.sh's dispatch, with
--backend herdr / FM_BACKEND=herdr / config/backend selection, a
version/protocol gate at spawn, semantic busy-state detection via herdr's
agent.get (fm-watch.sh and fm-crew-state.sh consult it before falling back to
the existing tmux pane-regex path), and label-based recovery discovery.
Container shape (D4) decided empirically: tab-per-task in one "firstmate"
workspace, mirroring tmux's one-session-many-windows model.
Found and fixed two real herdr v0.7.1 bugs during verification: `pane read
--lines N` returns empty for small N (worked around by over-fetching and
trimming locally), and `pane get`'s cwd field is frozen at pane-creation time
(fixed to read foreground_cwd instead, needed for fm-spawn's worktree-
discovery poll after `treehouse get`). Also fixed a pre-existing bug in
tests/fm-backend.test.sh's old-vs-new fixture that was silently missing
fm-backend.sh/bin/backends/ from the old bin/ shim.
Full empirical verification, the D4 decision evidence, and a real end-to-end
run (spawn/steer/peek/done/merge-local/teardown, including confirming
teardown refuses before the merge) are recorded in docs/herdr-backend.md.
The entire existing tmux conformance suite stays green.
* no-mistakes(review): Fix Herdr supervision recovery gaps
* no-mistakes(review): Captain, fix Herdr stale recovery gaps
* no-mistakes(review): Document Herdr composer primitive candidate
* no-mistakes(review): Captain, harden Herdr stale recovery and tests
* no-mistakes(document): Sync herdr backend docs
* no-mistakes: apply CI fixes
* feat(bin): auto-detect runtime backend (#188)
* feat(bin): auto-detect runtime backend from HERDR_ENV/TMUX markers
fm_backend_name now falls through to runtime auto-detection between
config/backend and the hard tmux default: a firstmate running natively
inside herdr (HERDR_ENV=1) now spawns crewmates into herdr by default,
mirroring how harness detection already works in fm-harness.sh. Nesting
resolves innermost-first (tmux wins over a nested herdr pane). Explicit
--backend/FM_BACKEND/config/backend settings always win over detection.
Selecting herdr via auto-detect prints a loud stderr notice; auto-detecting
tmux stays silent so the unconfigured default path is unchanged.
* no-mistakes(review): Captain, pin tmux tests and backend docs
* no-mistakes(document): Sync backend autodetect docs
* no-mistakes: apply CI fixes
* feat(stow): add operational memory capture (#197)
* feat(stow): add operational-memory learnings convention and /stow skill
Add data/learnings.md as the fleet-local operational-learnings home,
a knowledge-routing table in AGENTS.md, and a user-invocable /stow
skill that sweeps a session for uncaptured durable knowledge and
files it to the right disk home before a reset.
* no-mistakes(review): Fix stow backlog note command
* no-mistakes(document): Document stow memory routing
* fix(tests): protect herdr smoke cleanup from default sessions (#199)
* fix(tests): stop real-herdr smoke tests from ever killing the default session
Both fm-backend-herdr-smoke.test.sh and fm-backend-autodetect-smoke.test.sh
tore down their isolated throwaway HERDR_SESSION via a bare/inline-prefixed
`herdr server stop`, which is unscoped and resolves ambiently. On this herdr
client, that ambient resolution silently falls back to whatever server is
already running instead of the requested session - it killed the captain's
live default herdr server twice in production (2026-07-02), once from each
smoke test's cleanup trap.
Add tests/herdr-test-safety.sh with herdr_safe_stop_and_delete: it uses the
explicit-by-name `herdr session stop/delete <name>` form (never the ambient
`server stop`) and, before that, a read-only hard guard
(herdr_refuse_if_default) that re-queries `herdr session list --json` and
refuses outright if the target is literally "default", not found, or flagged
default:true. Fails closed on any ambiguity. Verified empirically against a
real isolated session: refuses on default/nonexistent/empty names without
ever calling stop, and correctly tears down a genuine isolated session while
leaving the default session's workspace state byte-identical before and
after.
* no-mistakes(review): guard herdr delete with fresh check
* no-mistakes(document): Document herdr smoke cleanup safety
* feat(bin): route herdr secondmates into per-home workspaces (#200)
* feat(bin): give each secondmate its own labeled herdr workspace
Give each secondmate its own labeled herdr workspace, and land crewmates
spawned from a secondmate home in that secondmate's own space, instead of
every firstmate home (primary and all secondmates) sharing one "firstmate"
workspace.
bin/backends/herdr.sh: replace the constant FM_BACKEND_HERDR_WORKSPACE_LABEL
with fm_backend_herdr_workspace_label(), resolved fresh from FM_HOME on every
call. The primary (no .fm-secondmate-home marker) still resolves to
"firstmate" - byte-identical to every pre-existing task's recorded label, no
forced migration. A secondmate home resolves to "firstmate-<secondmate-id>".
Every workspace-scoped path (find/ensure, tab create + duplicate check,
list-live recovery, pane-for-tab) uses this same resolution, so recovery and
duplicate checks stay scoped to each home's own space. Workspace and tab
create now pass --no-focus unconditionally (verified: neither focuses by
default once a workspace exists; --no-focus is defense in depth against the
one bootstrap edge case where the very first workspace in a session auto-
focuses).
Also fixes a session-targeting bug found while verifying this empirically:
HERDR_SESSION (env var, exported or inline-prefixed) is not reliably honored
by herdr 0.7.1 CLI subcommands once another herdr server is already running -
it silently falls back to whatever server IS running. fm_backend_herdr_cli
wraps every herdr invocation with both HERDR_SESSION and a trailing
--session <name> flag (verified to route correctly in every case tried),
fixing this for the whole adapter, not just the new label-scoped calls.
bin/fm-spawn.sh: a --secondmate spawn is launched BY the primary's own
process, whose FM_HOME still names the primary at that point. The herdr case
arm now shadows FM_HOME to the secondmate's own home (PROJ_ABS) for just the
two calls that resolve/create the workspace and tab, restored automatically
afterward (bash's temporary-assignment-before-a-command form works for shell
functions too). A crewmate/scout spawned FROM a secondmate's own fm-spawn.sh
process needs no such glue - its own FM_HOME already names it.
Tests: extended tests/fm-backend-herdr.test.sh (per-home label resolution,
--no-focus, --session flag, workspace-find/list-live scoping) and
tests/fm-backend-herdr-smoke.test.sh (a secondmate-shaped home's workspace
label, list-live scoping, restart stability in the multi-workspace shape).
Added tests/fm-backend-herdr-workspace-per-home-e2e.test.sh: the mandatory
isolated E2E, driving real bin/fm-spawn.sh/fm-teardown.sh - a primary-shaped
home into "firstmate", a --secondmate spawn into its own labeled space, a
crewmate spawned FROM that secondmate-shaped home landing in the same space
(this exact path had never run before), teardown closing only the right tab,
and list-live recovery seeing only each home's own tabs. All ten assertions
passed on the real binary; the default herdr session's own workspace state
was confirmed byte-identical before and after every real-herdr test run in
this change.
docs/herdr-backend.md: rewrote "Task container shape" for the workspace-per-
home design (label derivation, the --secondmate FM_HOME-shadow wrinkle, focus
behavior, label-collision/adopt-don't-duplicate semantics, no-forced-
migration), added "Session targeting: the --session flag, not HERDR_SESSION
alone", extended "ID stability" to the multi-workspace shape, and documented
the new E2E test.
* no-mistakes(review): Clarify herdr focus docs
* no-mistakes(review): Captain, clarify herdr server session docs
* no-mistakes(document): Document herdr per-home spaces
* fix(backends): rename Herdr secondmate workspace labels (#203)
* Rename herdr secondmate workspace prefix to 2ndmate-
The primary home keeps the firstmate label; secondmate homes now
resolve to 2ndmate-<id> so the herdr spaces sidebar is unambiguous.
Tests and docs updated; pre-rename workspaces can be aligned with
herdr workspace rename.
* no-mistakes(review): Clarify herdr workspace migration behavior
* no-mistakes(document): Herdr docs label alignment
* feat(bin): add unified session start digest (#201)
* feat(bin): collapse session start into one command
Add bin/fm-session-start.sh, composing fm-lock.sh, fm-bootstrap.sh, and
fm-wake-drain.sh into one ordered digest (lock, bootstrap diagnostics,
wake queue, context files, fleet state) instead of six-plus separate
turns. Lock now runs before bootstrap's mutating sweeps, closing a race
where a second concurrent session could mutate shared state before
discovering the lock was held. A lock refusal prints a loud read-only
banner, skips every mutating step via a new opt-in
FM_BOOTSTRAP_DETECT_ONLY flag on fm-bootstrap.sh, and still completes
the read-only-safe digest.
Add fm_backend_target_exists to fm-backend.sh as a shared, read-only,
never-side-effecting per-task endpoint-liveness primitive for both the
tmux and herdr backends.
Rewrite AGENTS.md sections 3 and 5 around the single command and add
tests/fm-session-start.test.sh.
* no-mistakes(review): Harden session-start read-only guidance
* no-mistakes(review): Suppress read-only tangle repair guidance
* no-mistakes(review): Include orphan status logs
* no-mistakes(review): Captain: make session-start test executable
* no-mistakes(review): Captain: clarify status tail guidance
* no-mistakes(document): Sync session-start docs
* no-mistakes(lint): Shell lint clean
* fix(tests): avoid shellcheck boolean chain
* fix(bin): corroborate herdr idle crew state (#207)
* fix(bin): corroborate herdr idle agent_status with the pane's own text
crew_pane_is_busy trusted a bare `idle` verdict from herdr's agent.get
outright, skipping the tail-regex corroboration unknown already gets.
agent.get reports generation state only (working while the model streams
a turn), so it reads idle for a crew blocked on its own long foreground
no-mistakes run - even though the pane still shows the busy banner the
whole time. Combined with the no-mistakes CLI's 10-run attribution cap,
this made a genuinely working herdr crew read as not provably working,
triggering an immediate stale wake instead of absorb-then-escalate.
* no-mistakes(document): Align herdr busy-state docs
* fix(backends): reuse the herdr firstmate workspace instead of leaking one per spawn (#202)
* fix(backends): reuse the herdr firstmate workspace instead of leaking one per spawn
Every herdr-backed crewmate left an orphaned `firstmate`-labelled workspace
behind, one per task, because `fm_backend_herdr_workspace_find` never matched
the existing workspace: its jq filter used `--arg label ... $label`, and
`label` is a reserved keyword in jq (label/break), so the filter was a compile
error. The error was swallowed by `2>/dev/null`, the find returned empty on
every call, and `workspace_ensure` took the create path each spawn, minting a
fresh workspace. The same collision silently disabled the create-task
duplicate-label check and the bare-selector tab lookup.
Rename the jq variable to `$want` in all three affected filters so reuse,
duplicate detection, and bare-selector lookup work. With reuse restored the
single `firstmate` workspace is persistent (like tmux's session) and teardown
correctly leaves it in place, closing only the task's pane/tab.
Also prune the default tab (label "1") herdr auto-creates inside a freshly
created workspace, best-effort, so the workspace holds only real task tabs.
Add stateful-fake-CLI tests that replay repeated spawn/teardown cycles and
assert one reused workspace, zero orphans, the default tab pruned, and
`workspace create` invoked exactly once. Verified against the real herdr binary
too: the pre-fix code failed the smoke idempotency check (minted wE then wF in
an isolated session); the fix passes it.
Document the workspace lifecycle, the jq-keyword pitfall, the default-tab
prune, and the project-labelled-workspace anomaly (not adapter-created) in
docs/herdr-backend.md.
* docs(herdr): trim workspace-lifecycle addition to current-state facts
The docs/herdr-backend.md convention documents current behavior, not
history - narrative belongs in the PR/commit message. Tightened the
workspace-leak and default-tab-prune write-up down to the operative
facts (the jq reserved-keyword guard, when pruning is safe, and the
persistence caveat), and corrected the CLI-facts table rows to match
the corrected prune timing.
* fix(backends): defer herdr default-tab prune until a real task tab exists
Closing a workspace's LAST tab deletes the whole workspace on real
herdr (verified). Pruning the auto-created default tab right after
workspace create closed the workspace's only tab at that point,
destroying the just-created workspace on every single spawn instead of
reusing it - the fake-CLI unit tests didn't model this real-herdr
behavior, so they passed while the real-herdr smoke test failed with
"container_ensure is not idempotent".
Move the prune into fm_backend_herdr_create_task, right after the
first real task tab is added to a freshly created workspace, when
closing the default tab alongside it is safe. Update the fake-CLI unit
test to match the corrected timing.
Also fix a smoke-test-only bug this surfaced: the test's second
create_task call reused a $CONTAINER captured before the first task
was killed, rather than re-running container_ensure like real
fm-spawn.sh always does immediately before every create_task call - so
once the workspace (correctly) disappeared after its last tab closed,
the stale reference no longer named a live workspace.
* test(backends): guard against jq --arg names colliding with jq keywords
Regression guard for the workspace-leak bug this PR fixes: a jq
--arg/--argjson variable named after a jq reserved keyword (e.g.
label) is a compile error on jq <= 1.6, and this adapter's
2>/dev/null silently turns that into an empty result instead of a
visible failure. Greps bin/ for the pattern so a future violation
fails loudly here instead of silently misbehaving on an older jq.
* docs(herdr): fix per-home staleness and drop contributor-specific example
The workspace-lifecycle write-up hardcoded "the firstmate workspace"
as if the label were always the fixed constant, stale against the
per-home labeling documented earlier in this file (primary: firstmate,
secondmate: 2ndmate-<id>). Rephrased per-home throughout, and pointed
the "workspace this adapter did not derive" case at the existing
Label-derivation section instead of a separate anomaly writeup.
Dropped the "Anomaly: a workspace labelled with a project name"
section - the python-teslemetry-stream example was a contributor's own
environment, not current adapter fact, and it repeated a now-incorrect
FM_BACKEND_HERDR_WORKSPACE_LABEL constant claim. Replaced with one
generic sentence already covered by the corrected wording above.
Also fixed the jq-reserved-keyword guard test's file reference, which
named tests/fm-backend.test.sh when the test actually lives in
tests/fm-backend-herdr.test.sh.
---------
Co-authored-by: kunchenguid <kun@kunchenguid.com>
* fix(backends): make Herdr default-tab pruning safe (#215)
* fix(backends): make the herdr default-tab prune provably safe
The default-tab prune could close a pane holding a LIVE agent: an
ADOPTED workspace (found pre-existing by label match) was pruned using
the same tab-count/label heuristic as a freshly created one, and herdr
derives a workspace's displayed label from its cwd basename when no
explicit --label is given. A captain launching herdr directly inside a
directory named "firstmate" produces a workspace that looks identical,
by label alone, to firstmate's own container - so the very next spawn
adopted the captain's own live workspace and closed their live pane
27ms after creating its task tab (2026-07-02 incident).
The fix is structural: fm_backend_herdr_workspace_ensure now captures
the seeded default tab's id straight from its own `workspace create`
response, only when it just created the workspace. That id threads
through fm_backend_herdr_container_ensure to fm_backend_herdr_create_task,
which is the only function allowed to prune it - an adopted workspace's
caller always passes an empty seeded-tab-id, so create_task never
re-derives "prunable" from a tab's label or count. Defense in depth:
the prune also refuses a tab whose pane reports a working agent.
Covered by new unit tests (adopted-never-prunes, created-prunes-exactly,
the exact label-collision incident shape) and a new isolated real-herdr
E2E test that reproduces the incident against the pre-fix code and shows
it fixed, plus the normal happy path.
* no-mistakes(document): Sync herdr prune docs
* no-mistakes: apply CI fixes
* fix(brief): remove apostrophe breaking bash -n and guard bash 3.2 set -u in spawn (#173)
* fix(brief): remove apostrophe that broke bash -n on fm-brief.sh
The no-mistakes DOD heredoc, built via VAR=$(cat <<EOF ... EOF), had an
unescaped apostrophe in "no-mistakes' own guidance". Nesting a heredoc
inside $(...) makes bash track quote state through the body, so the lone
apostrophe broke parsing of the rest of the script (bin/fm-brief.sh:211),
making the default no-mistakes ship path fail outright. Audited the other
two $(cat <<EOF...EOF) blocks (direct-PR, local-only) for the same class
of bug; none found. Added tests/fm-brief.test.sh as a regression guard.
* no-mistakes(test): fix(spawn): guard empty shared_args under bash 3.2 set -u
* no-mistakes(document): docs(contributing): list tests/fm-brief.test.sh in the test suite inventory
* fix(test): silence shellcheck SC2034/SC2100 in fm-brief.test.sh
Drop the unused out= capture (redirect to /dev/null instead) and quote
the id= assignment so shellcheck stops reading the hyphenated id value
as an arithmetic expression.
* feat: add experimental zellij runtime backend (#217)
* feat(backends): add experimental zellij runtime backend (P3)
Implements bin/backends/zellij.sh on the P1 dispatcher + P2 herdr precedent:
one zellij session, one tab per task, treehouse stays the worktree provider.
Wired through fm-backend.sh/fm-spawn.sh so fm-send/fm-peek/fm-watch/
fm-crew-state/fm-teardown work generically with zero changes to those scripts.
Empirically verified against real zellij 0.44.0: every "gaps to verify" item
from the design report, plus real findings the report missed - new-tab always
steals focus (mitigated with a restore call), zellij action always exits 0
even against a dead target, every pane op needs an explicit --pane-id, and
pane_cwd never tracks a subshell's own cd (treehouse get's exact shape) so
worktree-path discovery uses an active pwd-probe instead of passive JSON
polling. Findings and the full real-CLI evidence log are in
docs/zellij-backend.md.
Full real E2E cycle passed: spawn a real claude crewmate, accept its trust
dialog, steer it, receive done, confirm teardown refuses before merge, merge
local-only, confirm teardown then succeeds and the zellij tab is gone - all
in a scratch FM_HOME against a uniquely-named isolated zellij session, never
touching the real "firstmate" session or the live fleet.
Existing tmux and herdr conformance suites stay green; the two P1-era tests
asserting zellij was unimplemented now assert that of orca instead.
* no-mistakes(review): captain, guard zellij paste payloads
* no-mistakes(review): Captain, guard zellij pane readiness
* no-mistakes(review): Captain, harden zellij dead-target handling
* no-mistakes(review): Captain, harden zellij teardown and tests
* no-mistakes(review): Captain, harden zellij target validation
* no-mistakes(document): Document zellij backend
* docs: document Orca backend adapter contract (#209)
* docs: specify Orca backend adapter contract
* docs: require Orca window target alias
* no-mistakes(test): Captain: handle empty arrays under nounset
* no-mistakes(document): Document Orca backend proposal
---------
Co-authored-by: Stephen Brouhard <vesta@stephens-macbook-air.tail2122af.ts.net>
* feat(backends): add Orca primitive backend support (#210)
* feat(backends): add Orca adapter primitives
* no-mistakes(review): Gate Orca from task spawning
* no-mistakes(document): Document Orca backend limits
* fix(backends): stop mapping Orca Escape to interrupt
* no-mistakes(document): Document Orca primitive key support
* no-mistakes: apply CI fixes
* fix: drop CI-gate rewrite and normalize shared array-guard hunks
---------
Co-authored-by: Stephen Brouhard <vesta@stephens-macbook-air.tail2122af.ts.net>
* fix(backends): submit herdr slash commands reliably (#223)
* fix(backends): herdr slash-command submit verification false-positives on popup autocomplete
Two grok/herdr crewmates left /no-mistakes fully typed but unsubmitted for
minutes while fm-send exited 0. Live-reproduced against real grok 0.2.82:
the herdr adapter's submit verification declared success on ANY pane content
change after Enter, but an argument-taking slash command's first Enter only
closes the popup and expands the composer into an argument-hint placeholder
(/compact -> /compact compaction instructions) rather than submitting - a
real, visible change that isn't a submission. A second Enter is required.
fm_backend_herdr_composer_state replaces the delta check with a structural
read of the composer's own row (located by border-glyph shape, since herdr
exposes no cursor-row primitive), mirroring what cursor_y gives the tmux
adapter. A popup-close-with-placeholder-fill still reads pending, so the
retry loop now correctly sends the needed second Enter instead of stopping
early. The tmux backend was unaffected (its cursor-row read already handled
this correctly, verified side by side against the same live repro).
* no-mistakes(document): Sync herdr submit docs
* feat(skills): publish public stow skill with internal-skill hiding (#221)
* feat(skills): hide agent-only skills from installer discovery, add public stow
Mark every .agents/skills/* skill metadata.internal: true so the
skills.sh installer (npx skills add) hides them from discovery - all
assume a live firstmate home and are meaningless elsewhere. This is
inert to firstmate's own harness skill loader.
Add a new, fully standalone skills/stow for non-firstmate users: sweep
a conversation for durable knowledge and file it into whatever notes
convention the host project/user already has, asking once and
remembering the answer when ambiguous. No shared code with the
internal stow by design.
Document the two-tier layout in README and CONTRIBUTING.
* no-mistakes(review): Tighten public stow tracker routing
* no-mistakes(document): Sync skill docs
* fix(skills): make public stow's undone-next-steps routing local-first
Supersede the earlier ask-before-tracker-write tightening: the
standalone stow no longer treats an issue tracker as a routing option
at all based on inference (git remote, .github/ presence, etc).
Undone next steps always land in a local file by default - an
existing TODO/BACKLOG/NOTES file, or a freshly created local scratch
file otherwise. A tracker (or any other external system) is only ever
used when the user has explicitly said so, this session or as a
previously recorded standing preference.
* no-mistakes(document): Sync skill documentation
* feat(skills): clarify public stow resume and fallback routing (#225)
* feat(skills): resume pointer, routing tiers, default notes file for public stow
Adds a copy-pasteable resume pointer to the safe-to-end verdict so a
new session can pick the work back up cold, states the explicit three-tier
routing priority (explicit instruction > existing local convention > default
NOTES.md fallback) instead of leaving it implicit, and names NOTES.md as the
top-level discoverable default instead of letting each agent improvise a
location. Also removes a leftover internal-tooling word from the maintainer
comment so the public file carries zero internal vocabulary.
* fix(skills): make the public stow default fallback private and gitignored
Supersedes the earlier NOTES.md/ask-once split: the tier-3 default fallback
(no existing local convention fits) is now .stow-notes.md, gitignored so it
never lands unprompted in a shared/committed file. Because it's private, it's
safe for every finding-kind including user preferences, so the separate
ask-once carve-out for personal material is no longer needed there. Tier 2
(an already-established tracked convention) is unchanged and is the only
tier that still writes into a shared file. Step 7's resume pointer now flags
when notes landed in the private fallback and that they can be promoted into
a shared file later.
* fix(skills): split the public stow private fallback by scope, use git exclude
Supersedes the single .stow-notes.md fallback: user preferences (cross-project
by nature) now default to a host-local ~/.stow/notes.md instead of being
siloed into one project's repo. Project-scoped findings still default to
.stow-notes.md at the project root, but it's now kept out of git via the
local-only .git/info/exclude instead of the tracked .gitignore, so the
fallback is truly zero-shared-footprint: nothing lands in a tracked file and
nothing is left for the user to review or commit.
* fix(skills): keep the public stow private fallback sandbox-safe (current dir only)
Supersedes the home-file split: a home-directory path fails for agents
sandboxed to their current working directory, so the tier-3 default now
stays a single .stow-notes.md at the project root for every finding-kind,
including user preferences. Tier 2's user-level memory file is now framed
as a bonus when accessible, never assumed or required. Step 7 gains a
caveat when a preference lands in the project-local fallback: it applies to
this project only, and the user can copy it into their own global memory
file if they want it to follow them everywhere.
* fix(skills): use a current-directory .gitignore for the public stow fallback
Supersedes .git/info/exclude: that mechanism resolves outside the working
directory in a linked worktree, breaking the sandbox-safety guarantee for
exactly the setup this fleet uses everywhere. Switch to an ordinary
.gitignore file in the current directory instead - always in-directory
regardless of worktree layout - creating or appending a .stow-notes.md
line, left uncommitted for the user. If that write itself fails, the skill
still creates .stow-notes.md and tells the user to ignore it manually
rather than blocking. Also scopes the "never writes outside the current
directory" guarantee precisely to tier 3: tier 2 is exempted since it only
targets a destination the user's own existing convention already
established, which can legitimately be a user-level file outside the
project.
* no-mistakes(review): Clarify stow routing precedence
* no-mistakes(review): Clarify stow fallback metadata boundary
* no-mistakes(review): Guard tracked stow fallback
* no-mistakes(review): Clarify stow fallback verdict
* no-mistakes(document): Sync stow skill docs
* no-mistakes(lint): Public skill lint cleanup
* docs: clarify note hygiene guidance (#226)
* docs: add note-hygiene rule to backlog format section
Backlog and task notes accumulate volatile specifics that drift and
mislead; capture the general principle so every firstmate user avoids
trusting a stale note over the authoritative source.
* no-mistakes(review): Clarify note hygiene schema exemptions
* no-mistakes(document): Clarify note-hygiene docs
* revert: drop out-of-scope stow/architecture doc edits
Keep this PR's diff scoped to the AGENTS.md note-hygiene addition only;
skills/stow/SKILL.md, .agents/skills/stow/SKILL.md, and docs/architecture.md
are under separate close review and must not change out of band here.
This reverts commit ed2d73205b01e24ba8ffd40c523a24993f6eb1a5.
* feat(backends): add Orca task lifecycle support (#228)
* feat(backends): add Orca task lifecycle support
* fix(backends): harden Orca spawn lifecycle
* no-mistakes(review): Fix Orca lifecycle cleanup gaps
* no-mistakes(review): Release Orca worktrees without paths
* no-mistakes(review): Guard Orca spawn abort cleanup
* no-mistakes(review): Allow partial Orca child cleanup
* no-mistakes(review): Fix Orca selector and cleanup leaks
* no-mistakes(review): Preserve pathless Orca cleanup metadata
* no-mistakes(review): Harden Orca spawn and teardown lifecycle
* no-mistakes(review): Enforce Orca scout report gate
* no-mistakes(review): Harden Orca teardown path validation
* no-mistakes(review): Harden Orca capture errors
* no-mistakes(review): Harden Orca JSON cleanup validation
* no-mistakes(test): Fix zellij scout teardown fixture
* no-mistakes(document): Document Orca lifecycle support
* Harden Orca runtime and submit verification
* no-mistakes(review): Captain, preserve Orca current-tail verification
* no-mistakes(document): Sync Orca lifecycle docs
* no-mistakes(lint): Captain, silence deliberate ShellCheck
---------
Co-authored-by: Stephen Brouhard <vesta@stephens-macbook-air.tail2122af.ts.net>
* fix: use PR head for review diffs (#229)
* Fix fm-review-diff to compare PR head when pr= is recorded
After no-mistakes fix rounds push to the open PR, the crewmate worktree
branch can lag the authoritative PR head. When meta records pr=, resolve
the compare ref from reachable pr_head= or refs/pull/<n>/head before
diffing against the fetched authoritative base; fall back to the local
branch with a loud warning when the PR head cannot be resolved.
Add behavior tests for pr_head resolution, fetch, unchanged no-pr path,
and unreachable-PR fallback.
* no-mistakes(review): Make review-diff test executable
* no-mistakes(document): Document PR-head review diffs
* no-mistakes: apply CI fixes
* fix: make backend matching shell-portable (#230)
* Make fm-backend.sh backend-name matching shell-portable for zsh
Replace word-split-dependent for-loops in fm_backend_is_known() and
fm_backend_validate_spawn() with case-based membership tests so sourcing
the library from zsh no longer falsely rejects known backends.
Add zsh/bash regression coverage in tests/fm-backend.test.sh.
* no-mistakes(review): Fix zsh backend loading and validation
* no-mistakes(document): Document backend membership portability
* fix(backends): make herdr respawn idempotent (#231)
* fix(backends): make herdr respawn idempotent against restored-layout husks
herdr persists and restores its session layout (workspaces/tabs/panes)
across a server restart, so a restored fm-<id> task tab comes back a
husk - a dead pane, or a plain agent-less shell - which fm-spawn.sh's
duplicate-tab guard refused unconditionally, forcing manual pane closes
after every restart.
fm_backend_herdr_create_task now classifies an existing same-labeled
tab's pane conservatively (dead/no-agent/live/unknown) and
closes-and-replaces only a confirmed husk, always creating the
replacement tab before closing the old one so a husk that is a
workspace's only tab is never at risk of taking the whole workspace
down with it. A genuinely live agent, or anything not confidently
classifiable, still refuses exactly as before.
* no-mistakes(review): Harden herdr duplicate respawn guard
* no-mistakes(review): Enforce herdr husk cleanup postcondition
* no-mistakes(document): sync herdr respawn docs
* no-mistakes: apply CI fixes
* fix: absorb stale wakes during active validation (#233)
* fix(watcher): stop stale_is_terminal from ignoring an active run-step
A crewmate's status log gets no new entry once firstmate hands it to a
no-mistakes validation (the sparse status-reporting contract), so the
log's last line can stay a pre-validation "done:" (or needs-decision/
blocked) leftover for the run's entire duration. fm-watch.sh's
stale_is_terminal only reads that raw last line - it has no run-step
awareness - so it kept surfacing a stale pane as immediately terminal
every time it went quiet for two polls, no matter how actively the
pipeline was validating (confirmed live against fm-herdr-respawn-idem,
whose status log's last line is literally "done: ..." while its
no-mistakes run-step reads "validating (running)"). crew_is_provably_working
now gets a chance to override a stale captain-relevant log line on a
new stale hash, exactly as it already did for a non-captain-relevant one.
Also fixed a separate, independently-confirmed dead code path in
fm-crew-state.sh: its cross-branch run-attribution fallback shelled out
to `no-mistakes axi` (bare) expecting a runs[N]{...} TOON table that the
real CLI (v1.32.2) never emits - verified the axi surface exposes only
abort/logs/respond/run/status. Replaced it with the real top-level
`no-mistakes runs` listing.
* no-mistakes(document): Sync watcher stale docs
* no-mistakes(lint): Fix stale fake run-list variables
* docs: clarify no-mistakes evidence commit handling (#232)
* docs: no-mistakes evidence commits in crew branches are intentional
* no-mistakes(review): Scope evidence guidance to project repos
* no-mistakes(document): Document evidence commit policy
* fix: tighten Orca backend parsing (#237)
* fix: tighten orca parser coverage
* no-mistakes(document): Sync Orca backend docs
* no-mistakes(lint): Captain, lint clean
---------
Co-authored-by: Stephen Brouhard <vesta@stephens-macbook-air.tail2122af.ts.net>
* docs: add backend setup guides and slim README (#238)
* docs: make README pointer-first, add per-backend setup guides
Trim the Quick Start and How It Works walls of prose down to overview plus
pointers, relocating every removed sentence's content into docs/architecture.md,
docs/configuration.md, or the relevant backend doc. Add docs/tmux-backend.md as
the reference-backend setup guide, and add a Setup section to each experimental
backend doc (herdr, zellij, Orca) covering prerequisites, selection, first run,
watching/attaching, verification, and limitations. Record the README convention
in CONTRIBUTING.md.
* no-mistakes(review): Clarify backend setup docs
* no-mistakes(review): Clarify tmux secondmate support
* no-mistakes(document): Align backend documentation
* docs: stop telling users to run fm-spawn.sh for backend selection
fm-spawn.sh is firstmate-internal; a user never runs it directly. Rephrase
every user-facing backend-selection sentence across the tmux/herdr/zellij/orca
guides and docs/configuration.md to present the actual user mechanisms: a
local config/backend file, FM_BACKEND at launch, or telling the first mate in
chat. Internal-reference mentions of the --backend flag (fm-spawn.sh usage
notes, test coverage lists) are left as mechanics, not user instructions.
* Document herdr dual license in docs and install hint (#239)
Add AGPL-3.0-or-later/commercial licensing note to herdr-backend Setup
and the missing-binary error in bin/backends/herdr.sh.
* feat: support multiple X-mode follow-ups (#241)
* feat(x-mode): raise X follow-up cap to 3 within a 7-day window
Matches the relay's parallel contract change: fm-x-link.sh now records a
follow-up counter (with --carry-count to preserve it across a re-link onto
a successor task), fm-x-followup.sh posts up to three follow-ups per
mention within a 7-day window instead of one within 24h, clearing the
link on --final, cap exhaustion, window lapse, or a distinguishable relay
rejection (fm-x-reply.sh HTTP 409 -> exit 9) rather than treating that
rejection as a retryable failure. AGENTS.md and the fmx-respond skill are
updated to keep usage disciplined: spend follow-ups only on genuine
milestones, always finish with --final.
* no-mistakes(review): Update follow-up dry-run docs
* no-mistakes(review): Captain: refresh X follow-up docs
* no-mistakes(review): Captain: harden X follow-up relinks
* no-mistakes(review): Captain: harden follow-up state persistence
* no-mistakes(document): Document X follow-up carryover
* feat(backends): add experimental cmux runtime backend (#246)
* feat(backends): add cmux runtime backend (experimental)
Session-provider-only adapter for cmux (bin/backends/cmux.sh), mirroring
zellij/herdr structurally, wired into fm-backend.sh and fm-spawn.sh with
--secondmate refused for now. Verified against the real cmux 0.64.17 app:
send does not auto-submit, cwd is creation-time-frozen (zellij-shape,
pwd-marker-probe workaround), close-surface refuses on a workspace's last
surface (falls back to close-workspace), workspace ids do not survive a
relaunch, and the control socket defaults to cmuxOnly access (requires a
one-time password-mode setup, documented in docs/cmux-backend.md). Also
found and fixed a live bug during development: read-screen fails on a
surface that has never been written to, so liveness now uses list-panes
instead. Fake-CLI unit suite (40 tests), a real-binary smoke test, and a
full spawn/steer/peek/done/merge/teardown E2E pass against a real claude
crewmate all pass, including the popup/second-Enter regression class.
* no-mistakes(review): Harden cmux recovery and password parsing
* no-mistakes(review): Harden cmux capture failure handling
* no-mistakes(review): Mark cmux test scripts executable
* no-mistakes(review): Scope cmux workspaces and teardown
* no-mistakes(review): Captain, honor cmux password config override
* no-mistakes(review): Captain, hash cmux home labels
* no-mistakes(document): Sync cmux backend docs
* feat(agents): add firstmate coding guidelines skill (#248)
* Add firstmate-coding-guidelines skill (AGENTS.md diet PR 0)
Encodes the knowledge-placement decision tree, one-owner rule, and
inline-stub pattern from the diet analysis so future contributions stop
adding conditional detail inline. AGENTS.md gets one section-13 trigger
line; fm-brief.sh's REPO argument has no reliable signal for "this is
firstmate's own repo", so the load instruction goes in CONTRIBUTING.md's
Development section instead of the scaffold.
* no-mistakes(review): Captain, align tracked-material trigger scope
* no-mistakes(document): Sync coding guidelines docs
* no-mistakes(lint): Fix Markdown style issues
* fix: add turn-end supervision guard (#249)
* feat: structural Stop-hook backstop for primary turn-end supervision
fm-guard.sh is pull-based: it only warns when some other supervision
script happens to run, so a primary session that ends a turn without
re-arming the watcher and then runs no further fleet-touching command
can sit blind for hours (the 2026-07-04 incident this fixes).
Add bin/fm-turnend-guard.sh, a Claude Code Stop hook registered in the
tracked .claude/settings.json, that fires on every primary turn end and
blocks (exit 2, verified empirically to force continuation) when work
is in flight with no fresh watcher beacon. It never blocks more than
once per turn, using Claude Code's own stop_hook_active loop-guard
field, and scopes itself to the actual primary checkout only (inert in
crewmate/scout worktrees and secondmate homes).
Factor the shared "in-flight but no live watcher" predicate out of
fm-guard.sh into bin/fm-supervision-lib.sh so the pull-based banner and
the push-based hook can never drift on what "unhealthy" means.
Document the verified Stop-hook mechanism and scoping in
docs/turnend-guard.md, add a harness-adapters note, and cover the
predicate and hook with tests/fm-turnend-guard.test.sh.
* no-mistakes(review): Respect active home in turnend guard
* no-mistakes(review): Require live watcher for turn-end guard
* no-mistakes(review): Captain: portable turn-end timing
* no-mistakes(document): Sync turn-end guard documentation
* feat(backends): auto-detect cmux runtime (#250)
* feat(backends): auto-detect cmux runtime from CMUX_WORKSPACE_ID
Wires cmux into fm_backend_detect the same way herdr already is: a
firstmate process running inside a cmux-spawned terminal now spawns
new tasks into cmux by default, no config needed. Verified from cmux's
own shipped source that CMUX_WORKSPACE_ID/CMUX_SURFACE_ID/CMUX_SOCKET_PATH
are unconditionally, non-overridably injected into every terminal
surface it spawns, and that cmux's own CLI treats CMUX_WORKSPACE_ID as
its own ambient-target fallback - the same role $TMUX/HERDR_ENV play for
their backends. CMUX_WORKSPACE_ID is checked last (after $TMUX and
HERDR_ENV=1) since cmux is a terminal application, not a nestable
multiplexer. Socket auth (config/cmux-socket-password) stays required
regardless of how the backend was selected; the existing spawn refusal
now also names the config/backend=tmux / --backend tmux opt-out for a
caller who never explicitly chose cmux.
A live env dump inside a real cmux terminal was not obtained safely on
the shared dev machine (documented in docs/cmux-backend.md); this rests
on the source read instead, mirroring this doc's existing
verified-from-source precedent.
* no-mistakes(review): Fix cmux autodetect docs and tests
* no-mistakes(document): Document cmux auto-detection
* fix(afk): support herdr away-mode injection (#251)
* fix(afk): make the away-mode daemon backend-aware for herdr
bin/fm-supervise-daemon.sh discovered its supervisor pane and injected
via raw tmux calls only, so /afk failed outright on a herdr-based
fleet (TMUX_PANE unset, firstmate:0 fallback unresolvable).
Discovery now resolves backend (tmux|herdr) and target independently,
mirroring fm-backend.sh's own runtime auto-detection, with an explicit
FM_SUPERVISOR_BACKEND override alongside the existing FM_SUPERVISOR_TARGET.
zellij/orca refuse loudly at startup instead of misapplying tmux
primitives. Injection (pane-exists probe, busy-guard, composer-guard,
verified submit) now dispatches through bin/fm-backend.sh's generic
primitives, adding a new fm_backend_composer_state dispatcher; the
tmux path is byte-identical to before. Also fixes a pre-existing bug
in fm_backend_target_exists's herdr arm (missing --session, so it
silently misrouted once more than one herdr server was running) found
while verifying this end to end against a real isolated herdr session.
Classification, batching, max-defer, the marker contract, locks, and
wake-queue handling are unchanged - this is a transport-layer fix.
* no-mistakes(review): Corroborate Herdr idle busy state
* no-mistakes(review): Stabilize Herdr daemon startup wait
* no-mistakes(review): Captain, route cmux composer and update AFK docs
* no-mistakes(document): Document AFK supervisor backend support
* docs(agents): move X-mode procedures out of AGENTS (#253)
* docs(agents): collapse X-mode section 14 into fmx-respond/docs pointers
AGENTS.md diet PR 1 of 3 (agentsmd-diet-s2 report, move-plan items 1-2).
Replaces section 14's "Answering"/"Completion follow-up"/"Conversations"/
"Length and threads"/"Preview / dry-run" blocks (54 lines) and the
"Mechanism" narrative (6 lines) with two short pointers: fmx-respond
(section 13) for the procedure, docs/configuration.md "X mode (.env)"
for the wire protocol. Net -55 lines in AGENTS.md.
Destination edits landed first, deletions second (q4 discipline):
- docs/configuration.md: added the "purely additive, watcher untouched"
guarantee that AGENTS.md's Mechanism block stated but configuration.md
did not.
- fmx-respond/SKILL.md: added the x-mode-error wake boundary (report as
a blocker, do not load this skill), the --image flag for replies and
follow-ups, the "images are for real artifacts, not prose" rule, and
the dry-run compact-image-marker behavior - none of these were
previously in the skill even though AGENTS.md described them, so they
were genuine gaps, not pre-existing duplication. Also made the skill's
own "Completion follow-up" section the sole, full owner of that
procedure instead of deferring to AGENTS.md section 14 for substance
that no longer lives there (two internal cross-references updated to
point at section 8's terminal-wake trigger and the skill's own section
instead).
Mechanical line-by-line audit of every removed AGENTS.md line:
Mechanism block (6 lines removed):
- bootstrap artifact-writing description -> already owned by
docs/configuration.md "X mode (.env)" (locked-bootstrap paragraph)
- check-shim/poll mechanism description -> already owned by
docs/configuration.md same section
- missing-deps/x-mode-error diagnostic description -> already owned by
docs/configuration.md ("Relay auth or config problems...") plus
bin/fm-x-poll.sh's own header comment for the missing-curl/jq mechanics
- opt-out artifact removal description -> already owned by
docs/configuration.md same section
- "purely additive, no edit to fm-watch.sh/fm-watch-arm.sh/fm-wake-lib.sh/
afk daemon" guarantee -> MOVED to docs/configuration.md (added in this
PR; this fact had no other home before)
Answering/Completion follow-up/Conversations/Length and threads/
Preview-dry-run blocks (54 lines removed):
- x-mention wake -> load fmx-respond: already owned by section 13's
existing trigger line (unchanged) and restated in the new pointer
- x-mode-error wake -> report as blocker, don't load fmx-respond: MOVED
to fmx-respond/SKILL.md (added in this PR)
- inbox-draining, classification, acting, reply composition, submission,
cleanup-on-success/failure: already owned by fmx-respond/SKILL.md
"Procedure" section (unchanged, pre-existing)
- owner-only routing / captain-as-asker framing: already owned by
fmx-respond/SKILL.md "The asker is your own captain" section
- standing X-mode authorization / autonomous posting / dry-run as only
non-posting path: already owned by fmx-respond/SKILL.md same section
- acknowledge-first -> act -> follow-up shape, three-case classification:
already owned by fmx-respond/SKILL.md "A request to act on" section
- destructive/irreversible/security-sensitive escalation guardrail:
already owned by fmx-respond/SKILL.md "Public channel..." section and
Procedure step 2c
- dismiss-instead-of-reply for pure acknowledgments, relay re-offer
prevention, dry-run honoring: already owned by fmx-respond/SKILL.md
Procedure steps 2b/2c/2e-skip and docs/configuration.md
- public-safety bar (no task ids/internals/captain-private/secrets):
already owned by fmx-respond/SKILL.md "The reply is public" section
- never-inline-into-shell-command / --text-file or stdin: already owned
by fmx-respond/SKILL.md Procedure step 2e and Notes
- --image flag for replies (formats, base64, no-inline guarantee): MOVED
to fmx-respond/SKILL.md Procedure step 2e (added in this PR - this was
not previously in the skill)
- fm-x-link field names (x_request=, x_request_ts=, x_followups=):
already owned by AGENTS.md section 2's state/<id>.meta field list
(untouched, out of scope for this PR) and fmx-respond/SKILL.md
- carry-count/carry-ts relink behavior, three-follow-up budget, milestone
sparingness, --check/--text-file posting, connector/followup wire
detail, --final clearing, cap/window graceful degradation: already
owned by fmx-respond/SKILL.md "Completion follow-up" section (now sole
owner) and docs/configuration.md wire-protocol paragraphs
- --image flag for follow-ups: MOVED to fmx-respond/SKILL.md "Completion
follow-up" section (added in this PR - genuine gap)
- "failed task still gets an honest final follow-up": already owned by
fmx-respond/SKILL.md "Completion follow-up" section
- FMX_DRY_RUN whole-loop previewability: already owned by
fmx-respond/SKILL.md "Dry-run / preview mode" section
- in_reply_to conversation continuity, untrusted-thread handling,
follow-up worthiness judgment, relay-owned self-reply guard/cap:
already owned by fmx-respond/SKILL.md "The direct ask is the captain's"
section and Notes (one bullet is a verbatim match)
- concise-by-default / no hand-numbered threads: already owned by
fmx-respond/SKILL.md "Voice" section
- auto-split behavior, char/tweet caps, premium-independence, wire shape
({text}/{text,texts}): behavior already owned by fmx-respond/SKILL.md
Voice section; exact defaults and wire shape already owned by
docs/configuration.md; "premium-independent" mechanics already owned
by bin/fm-x-reply.sh's own header comment
- "images are for real artifacts, not prose": MOVED to fmx-respond/
SKILL.md "Voice" section (added in this PR - genuine gap)
- image-on-thread wire behavior: already owned by docs/configuration.md;
reinforced in fmx-respond/SKILL.md's new --image note
- dry-run POST-body shape, endpoint marker, truthy-value definition,
jq-only dependency, end-to-end testability, x-outbox inspection:
already owned by fmx-respond/SKILL.md "Dry-run / preview mode" section
(several near-verbatim matches) and docs/configuration.md wire detail
- dry-run compact image marker: MOVED to fmx-respond/SKILL.md "Dry-run /
preview mode" section (added in this PR - genuine gap)
Section 8's terminal-wake completion-follow-up trigger (the one fact
required to survive inline) is untouched and already present; the new
section 14 pointer references it instead of restating it.
Nothing outside section 14 (plus the two destination files) is touched.
Full test suite green, including all 74 fm-x-mode.test.sh checks.
* no-mistakes(review): Preserve X-linked follow-up triggers
* no-mistakes(review): Fix x-mode error trigger
* no-mistakes(document): Docs cross-reference synchronized
* no-mistakes(lint): Clean Markdown lint pass
* docs: trim duplicated harness guidance (#255)
* docs(agents): trim section 4 harness/secondmate duplication
AGENTS.md diet PR 2 of 3 (data/agentsmd-diet-s2/report.md, move-plan
items 3-4; redundancy item 2 folded into item 3).
Removed the five claude/codex/grok/pi/opencode model/effort-flag
bullets from section 4 - byte-for-byte duplicated by
harness-adapters' "Launch profile axes" table (which is already a
superset: it carries verified CLI versions per adapter that the
AGENTS.md bullets lacked). Replaced with a one-line pointer; the
skill is already loaded before every spawn per section 4's own
closing trigger, so no new trigger was needed.
Moved the config/secondmate-harness model/effort pin-format detail
(the `<harness> [<model>] [<effort>]` line format, the
secondmate-model/secondmate-effort accessors, back-compat, and the
durability-across-respawn behavior) into secondmate-provisioning,
which is already a mandatory load at every secondmate lifecycle
touchpoint. Added the destination content to the skill first, then
replaced the AGENTS.md paragraph with a 3-line pointer.
Mechanical audit - every removed line's new home:
- 5 harness bullets (claude/codex/grok/pi/opencode model+effort
flags, per-harness max-omission rationale) -> already present in
harness-adapters SKILL.md's "Launch profile axes" table (lines
53-59), confirmed fact-by-fact before deleting.
- "config/secondmate-harness may also pin..." paragraph (pin format,
bare-harness back-compat, secondmate-model/secondmate-effort
accessors, per-spawn override precedence, respawn durability,
secondmate-only scope) -> secondmate-provisioning SKILL.md's
"Charter and seed" section, added verbatim before this trim.
- The following paragraph (inheritable config: crew-dispatch.json,
crew-harness, backlog-backend) is untouched - out of scope for
this PR, still inline.
- The bootstrap CREW_DISPATCH effort-mismatch diagnostic sentence is
untouched - not part of the five-bullet duplication, stays inline.
No script changes. Section 4 shrinks from 104 to 87 lines
(958 -> 901 total AGENTS.md lines) with zero facts lost: every fact
is reachable through harness-adapters or secondmate-provisioning,
both already mandatory loads at the relevant lifecycle points.
* no-mistakes(document): Align secondmate skill triggers
* no-mistakes(lint): Markdown style clean
* fix: anchor turn-end Stop hook to project root (#256)
* Fix turn-end Stop hook to use CLAUDE_PROJECT_DIR path
Claude Code runs hook commands via /bin/sh from the session cwd, so the
bare relative bin/fm-turnend-guard.sh path fails when cwd is not the repo
root. Anchor the command with "$CLAUDE_PROJECT_DIR"/bin/fm-turnend-guard.sh
instead; verified CLAUDE_PROJECT_DIR is set on Stop hooks in Claude Code
2.1.201. Document the cwd caveat and add a settings.json regression test.
* no-mistakes(document): Document Stop hook path anchoring
* docs: trim firstmate agent guidance duplication (#258)
* docs: trim AGENTS.md redundancy (diet PR 3/3)
Consolidates five duplicated passages to a single owner each, per
data/agentsmd-diet-s2/report.md redundancy items c3-c7:
- Inheritable-config propagation mechanism: owned by section 3 (where
the sweep runs); sections 4 and 7 keep compact references. Section 4
retains its one genuinely unique fact (crew-harness inherit-vs-fallback
semantics), just no longer restates the propagation mechanism itself.
- Landed-work definition: owned by section 7's ship-teardown detail
(PR-containment mechanics, pr= discovery fallback); section 1's hard
rule #3 keeps the rule plus a three-case summary and a pointer.
- Backend meta-field enumeration: owned by docs/configuration.md
("Runtime backend", already comprehensive including cmux) and each
backend's own doc; AGENTS.md keeps only the fields common to every
task plus a pointer.
- Dropped one redundant restatement of "silence is correct while
waiting" in section 8.
- Worktree-tangle guard explanation: owned by section 8 (already the
fuller, cross-referenced version); section 3's TANGLE bullet keeps
the remediation action and points at section 8 for the why.
Also adds two captain-requested single-sentence rules: invoke bin/
scripts by absolute $FM_ROOT path after any cd away from the home, and
a backend spawn refusal must be surfaced to the captain rather than
silently worked around by switching backends.
AGENTS.md: 901 -> 889 lines, 112355 -> 108560 bytes.
* no-mistakes(review): Clarify post-cd bin invocation guidance
* no-mistakes(document): Sync AGENTS trim docs
* no-mistakes(lint): Fix Markdown line style
* feat(backends): improve cmux detection and socket-mode guidance (#259)
* feat(backends): cmux detection fallbacks and socket-mode matrix
Workstream A: cmux's bundled claude wrapper strips every CMUX_* env var on
its passthrough path (reproduced live 2026-07-04, cmux 0.64.17), so a
claude-harness firstmate inside a cmux tab has no CMUX_WORKSPACE_ID.
fm_backend_detect now falls back - macOS-only, only when the primary marker
is absent - to __CFBundleIdentifier=com.cmuxterm.app and then a process
ancestry walk resolved by bundle id (lsappinfo) plus a bundle-shaped ps comm
match. Innermost-first ordering is unchanged and absorbs the
tmux-inside-cmux bundle-id false positive; the auto-detect NOTICE names the
winning fallback signal.
Workstream B: the five socketControlMode values were traced through cmux
source (commit 9c91710e3f58): off/cmuxOnly can never admit an external CLI,
automation admits same-user clients with no secret (0600 socket only),
password needs the auth handshake, allowAll opens the socket to every local
user (0666). Automation mode is now the documented recommendation; the
adapter's refusals name every viable mode, classify Invalid password as
unauth, and the launch-timeout message names the off-mode possibility.
Docs carry the wrapper-strip empirical record, the fallback contract and
authority split, and the full mode matrix with rationale; tests cover the
new detection paths, the nested false positive, and the refusal wording.
* no-mistakes(review): Document cmux fallback detection
* no-mistakes(review): Update cmux architecture docs
* no-mistakes(document): Align cmux backend docs
* fix(backends): scope zellij tabs by firstmate home (#252)
* fix(backends): home-scope zellij tab titles to close cross-home collision gap
Zellij's one shared "firstmate" session has no per-home split and enforces
no tab-name uniqueness, so two firstmate homes with colliding task ids could
send/peek/close each other's tabs - the same gap a no-mistakes review gate
caught for cmux (docs/cmux-backend.md). Ports that fix: every new tab is
created with a home-scoped title (fm-<home-label>-<id>), and every
list/find/recover/kill path scopes matches to this home's own tag. A tab
spawned before this change still matches via its old untagged bare title,
but only when unambiguous - two live tabs sharing a bare title refuse rather
than guessing which one is ours.
Factors the home-label/hash derivation shared with cmux into
bin/fm-backend-hometag-lib.sh so the two adapters can't drift.
* no-mistakes(review): Fix zellij child teardown home tag
* no-mistakes(review): Fix zellij teardown and selector scoping
* no-mistakes(document): Sync zellij home-scope docs
* fix: sync project clones after merged PR wakes (#293)
* fix(fleet-sync): auto-sync on merged-PR wake, accept project name
fm-fleet-sync.sh's single-project form failed on a bare project name
("not a directory"), forcing hand-typed full paths (4 manual runs in
one incident). It now resolves a bare name or projects/<name> against
the home's projects dir.
AGENTS.md now encodes the trigger: a wake whose status reports a
merged PR for a project cloned in this home runs fleet-sync for that
project as part of handling the wake, so a secondmate-reported merge
does not leave the primary's clone stale until the next session start
or teardown.
* no-mistakes(review): Fix fleet-sync project name shadowing
* no-mistakes(document): sync fleet-sync docs
* fix: canonicalize spawn worktree path checks (#294)
* fix(spawn): canonicalize worktree-isolation guard against symlinked project prefixes
fm-spawn.sh compared a logical PROJ_ABS against the physically-resolved
pane cwd every backend reports, so a project reached through a symlinked
prefix (e.g. macOS's /tmp -> /private/tmp) could trip the isolation
guard's false refusal before treehouse ever moved the pane. Canonicalize
once into PROJ_ABS_REAL and compare against that everywhere instead.
* no-mistakes(review): Canonicalize spawn cwd comparisons
* no-mistakes(document): Refresh symlinked spawn docs
* docs: add Orca operator skill (#276)
* docs: add Orca operator skill
* no-mistakes(document): Document Orca checklist
---------
Co-authored-by: Stephen Brouhard <vesta@stephens-macbook-air.tail2122af.ts.net>
* fix: surface green PRs during CI monitoring (#297)
* fix(crew-state): detect green-PR CI monitoring, escalate repeat wedges
fm-crew-state.sh's ci step never distinguishes "still waiting on checks"
from "checks green, waiting on merge" via axi status alone, since a repo
that defers merge to the captain keeps the ci step at status=running for
the whole monitor phase. Read the ci step's own log tail (axi logs) for
the checks-passed marker and surface done instead of a false "validating
(running)" - verified against the real PR #252 run's ci.log.
The watcher's wedge timer can re-escalate the same stale pane forever
without ever signaling that it is a repeat; track a per-pane consecutive
escalation count and add a demand-deep-inspection marker to the wake
payload once it crosses a threshold, so the supervisor can no longer
dismiss each one as an isolated, still-validating pane.
Also clarify the ship-brief's checks-green line: it is owed at the
CI-ready return point, not after the background monitor-until-merge
loop finishes.
* no-mistakes(review): Captain, distinguish pending no-checks CI marker
* no-mistakes(review): Harden CI relapse handling
* no-mistakes(review): Block stale done during fixing
* no-mistakes(review): Captain, tighten CI status gating
* no-mistakes(review): Captain, harden stale CI green handling
* no-mistakes(review): Captain, recognize ranged CI rearm markers
* no-mistakes(document): Sync crew-state supervision docs
* fix(teardown): recover provably stale git index locks (#296)
* fix(teardown): recover from a stale worktree git index.lock
A crew process killed mid-git-operation can leave a stale
.git/worktrees/<wt>/index.lock behind, making fm-teardown.sh's
`treehouse return --force` fail closed. On that failure, retry once
after a short wait (the owning process may be exiting), then remove
the lock and retry once more only when it is provably stale: old
enough by mtime and lsof shows no live holder on the lock or the
worktree itself. A lock that isn't provably stale is left in place and
the original…
kostas-nt
referenced
this pull request
in kostas-nt/firstmate
Jul 8, 2026
* feat: add grok crewmate harness support (#143)
* feat(harness): add grok (Grok Build) as a verified crewmate adapter
Empirically verified against grok 0.2.73 and encoded across the machinery:
- fm-harness.sh: detect grok via GROK_AGENT=1 env marker (grok does not set
CLAUDECODE) and `grok` command-name ancestry.
- fm-spawn.sh: grok launch template (`grok --always-approve "$(cat BRIEF)"`,
fully autonomous, no permission gate) and a turn-end Stop hook. grok only
loads project hooks after a manual folder-trust grant, so the hook is a
single firstmate-owned global hook (~/.grok/hooks/fm-turn-end.json, always
trusted) that is a guarded no-op unless the workspace holds a per-task
.fm-grok-turnend pointer; fm-spawn drops that gitignored pointer naming
state/<id>.turn-ended. Hook stays outside the worktree, needs no trust grant.
- fm-watch.sh + fm-tmux-lib.sh: grok busy signature `Ctrl+c:cancel` (the
mid-turn cancel hint; ASCII, present iff a turn runs).
- harness-adapters skill: grok facts section (busy, exit=Ctrl+Q x2,
interrupt=Ctrl+C, skill invocation /<skill>, resume) and /no-mistakes form.
Gating question confirmed: grok invokes /no-mistakes and drives a real
no-mistakes axi run, so grok is usable for no-mistakes-mode tasks. End-to-end
verified through fm-spawn: autonomous launch past the dir picker into the
worktree, brief processed, busy->idle and turn-end signal detected, fm-send
steer lands, clean Ctrl+Q exit and teardown. config/crew-harness is left
unchanged; this only makes grok available as a verified option.
* no-mistakes(review): Captain, harden Grok hook lifecycle
* no-mistakes(review): Captain, make Grok harness test executable
* no-mistakes(review): Captain, bound Grok pointer reads
* no-mistakes(test): Captain, harden crew-state and watcher-lock timing
* no-mistakes(document): Document Grok harness support
* feat(harness): split secondmate harness configuration (#144)
* feat(harness): split secondmate harness and inherit primary config into secondmate homes
Add config/secondmate-harness so secondmates can run on a different adapter
than crewmates. fm-harness.sh gains a `secondmate` mode resolving the chain
config/secondmate-harness -> config/crew-harness -> own; `crew` mode is
unchanged. fm-spawn resolves a --secondmate launch through that mode (durable:
every respawn re-resolves), while an explicit per-spawn harness arg still wins
and the unverified-adapter guard still holds.
Add a generic, extensible inheritable-config mechanism (fm-config-inherit-lib.sh)
that pushes the primary's declared LOCAL config into each secondmate home's
config/ at secondmate spawn and on the bootstrap secondmate sweep. Exactly one
item is wired today: config/crew-harness, so a secondmate's own crewmates use
the primary's setting. Primary-authoritative (re-pushed every convergence,
mirrors absence); config/secondmate-harness is deliberately not inherited since
secondmates never spawn secondmates. config/ is gitignored, so this is a copy
separate from the tracked-files fast-forward.
Update AGENTS.md (layout, bootstrap, harness, spawn), the harness-adapters
skill, docs/scripts.md, and .gitignore. New tests cover secondmate resolution
and fallback, spawn/respawn honoring config/secondmate-harness, config
propagation on spawn and sweep, the unverified-adapter guard, and backward
compatibility.
* no-mistakes(review): Surface inherited config propagation failures
* no-mistakes(review): Harden inherited config propagation
* no-mistakes(review): Document literal harness inheritance requirement
* no-mistakes(document): Document secondmate harness config
* feat(backlog): default backlog operations to tasks-axi (#145)
* feat(backlog): default to tasks-axi backend
* no-mistakes(document): Sync backlog backend docs
* fix(spawn): set per-task GOTMPDIR so interrupted Go builds don't leak /tmp (#36)
* fix(spawn): set per-task GOTMPDIR so interrupted Go builds don't leak /tmp
Go's GOTMPDIR is unset, so every go build/test creates numbered /tmp/go-build*
dirs. Go cleans them on a clean exit but LEAVES THEM when interrupted (signal,
timeout, OOM, full disk), accumulating and filling the disk over time.
Give each task its own temp root at /tmp/fm-<id>/ with Go's build temp nested at
gotmp/. fm-spawn creates the dir (Go won't mkdir GOTMPDIR), exports GOTMPDIR into
the crewmate pane so the agent and child processes inherit it, and records
tasktmp= in meta. fm-teardown reads tasktmp= and removes the whole root on
cleanup, deterministically.
GOTMPDIR (not TMPDIR) is the targeted knob: TMPDIR is too broad (affects every
program's temp). The nested root is extensible: other per-task temp can live
under /tmp/fm-<id>/ later.
Backward compat: tasks spawned before this change have no tasktmp= in meta;
teardown tolerates the empty value as a no-op. The daily fm-disk-cleanup.sh cron
remains a safety net for any pre-fix stray dirs.
* fix(tests): silence SC2016 for literal grep -F patterns in fm-gotmp test
The structural grep -F assertions deliberately match literal $TASK_TMP in the
fm-spawn source; add per-line shellcheck disable=SC2016 (the codebase's existing
pattern, e.g. bin/fm-spawn.sh) so CI lint passes.
* no-mistakes(document): docs: document tasktmp= meta field for per-task GOTMPDIR
---------
Co-authored-by: e-jung <8334081+e-jung@users.noreply.github.com>
* fix: accept landed squash-merged PR heads (#149)
* fix(teardown): accept landed squash-merge PR heads
* no-mistakes(document): Document teardown landing behavior
* no-mistakes: apply CI fixes
* fix(test): pass explicit teardown git identity
* feat(dispatch): add dynamic crew profiles (#154)
* feat(dispatch): add dynamic crew profiles
* no-mistakes(review): Captain, document dispatch profile inheritance
* no-mistakes(review): Captain, guard stale dispatch inheritance
* no-mistakes(document): Sync dispatch profile docs
* no-mistakes: apply CI fixes
* fix: harden crew dispatch profile enforcement (#159)
* Harden crew dispatch profile enforcement
* no-mistakes(document): Captain, synced crew dispatch docs
* feat: add live secondmate config push (#161)
* feat(config): add live secondmate config push
* no-mistakes(document): Document config push behavior
* no-mistakes(lint): Clean changed shell lint
* no-mistakes: apply CI fixes
* feat: support image attachments in X replies (#162)
* feat(x): add image attachments to reply helpers
* no-mistakes(review): Stream X image replies safely
* no-mistakes(review): Captain, clean X reply temp tracking
* no-mistakes(document): Document X reply image support
* fix(teardown): make landed PR detection robust (#167)
* fix(teardown): make landed-check robust when no pr= was ever recorded
fm-teardown.sh's squash-merge landed-check already falls back to
discovering a merged PR by branch name when state/<id>.meta has no
recorded pr=, but nothing guaranteed pr=/pr_head= actually got
recorded on a yolo-authorized merge - the "checks green" trigger that
normally runs fm-pr-check.sh never fires on repos with no PR CI, so a
merge done via a bare `gh-axi pr merge` silently skips it.
Add bin/fm-pr-merge.sh as the one path for merging a task's PR: it
always runs fm-pr-check.sh first, so pr=/pr_head= land in meta as part
of the merge itself regardless of any CI signal. Document both the
existing branch-name discovery fallback and the new merge path in
AGENTS.md, and add regression coverage for the no-pr=-recorded landed
scenario and for fm-pr-merge.sh's record-then-merge behavior.
* no-mistakes(review): Guard PR merges on task metadata
* no-mistakes(document): Document PR merge wrapper
* no-mistakes: apply CI fixes
* fix: parse PR merge URLs for gh-axi (#168)
* Fix fm-pr-merge.sh to parse PR URLs for gh-axi
gh-axi pr merge expects a PR number and --repo, not a full GitHub URL.
Parse the URL, default to --squash when no merge method is passed, and
fail fast on malformed URLs. Tests cover parsing, defaults, and refusal.
* no-mistakes(review): Harden PR merge validation
* no-mistakes(review): Harden PR merge URL guards
* no-mistakes(document): Document PR merge URL handling
* no-mistakes(lint): Clean shell lint
* feat(bin): pin secondmate model and effort (#180)
* feat: pin secondmate model/effort in config/secondmate-harness
Extend config/secondmate-harness's format to an optional
"<harness> [<model>] [<effort>]" line so a secondmate can be durably
locked to a concrete model/effort in the same file, without adding a
new config file. A bare harness-only file behaves exactly as before.
fm-harness.sh gains secondmate-model/secondmate-effort accessors;
fm-spawn.sh populates MODEL/EFFORT from them on every secondmate spawn
(including respawns) unless the caller passed an explicit --model/--effort.
* no-mistakes(review): Fix secondmate override pin precedence
* no-mistakes(document): Document secondmate harness pins
* feat(bin): add runtime backend interface (#183)
* feat(bin): extract tmux runtime behind a backend interface (P1)
Add bin/fm-backend.sh (selection, meta helpers, selector resolution,
dispatch) and bin/backends/tmux.sh (the tmux adapter), then route
fm-send.sh, fm-peek.sh, fm-watch.sh, fm-spawn.sh, and fm-teardown.sh
through them. Every default tmux command sequence, meta shape, and
printed output stays byte-identical: missing backend= still means
tmux, and a default spawn never writes backend=tmux.
Adds a --backend flag (tmux-only for now) and FM_BACKEND/config/backend
selection, refusing any unimplemented backend loudly. Names the
watcher's poll loop as the default event-source implementation over the
backend's pull primitives, per the herdr-addendum's events-as-the-core-
abstraction direction, without changing its behavior.
Verification: fake-tmux/treehouse old-vs-new command-log conformance
tests for send/peek/spawn/teardown, a real-tmux smoke test for the
adapter, and the full existing suite passing unmodified (bar two
fixture-only additions in fm-gotmp.test.sh for the new sibling
scripts).
* no-mistakes(review): Captain, harden backend baseline resolution
* no-mistakes(review): Captain, ignore and document backend config
* no-mistakes(review): Captain, make backend tests executable
* no-mistakes(document): Sync runtime backend documentation
* feat(bin): add experimental Herdr runtime backend (#186)
* feat(bin): add experimental herdr runtime backend (P2)
Implements bin/backends/herdr.sh (session-provider adapter, D3: treehouse
stays the worktree provider) wired through fm-backend.sh's dispatch, with
--backend herdr / FM_BACKEND=herdr / config/backend selection, a
version/protocol gate at spawn, semantic busy-state detection via herdr's
agent.get (fm-watch.sh and fm-crew-state.sh consult it before falling back to
the existing tmux pane-regex path), and label-based recovery discovery.
Container shape (D4) decided empirically: tab-per-task in one "firstmate"
workspace, mirroring tmux's one-session-many-windows model.
Found and fixed two real herdr v0.7.1 bugs during verification: `pane read
--lines N` returns empty for small N (worked around by over-fetching and
trimming locally), and `pane get`'s cwd field is frozen at pane-creation time
(fixed to read foreground_cwd instead, needed for fm-spawn's worktree-
discovery poll after `treehouse get`). Also fixed a pre-existing bug in
tests/fm-backend.test.sh's old-vs-new fixture that was silently missing
fm-backend.sh/bin/backends/ from the old bin/ shim.
Full empirical verification, the D4 decision evidence, and a real end-to-end
run (spawn/steer/peek/done/merge-local/teardown, including confirming
teardown refuses before the merge) are recorded in docs/herdr-backend.md.
The entire existing tmux conformance suite stays green.
* no-mistakes(review): Fix Herdr supervision recovery gaps
* no-mistakes(review): Captain, fix Herdr stale recovery gaps
* no-mistakes(review): Document Herdr composer primitive candidate
* no-mistakes(review): Captain, harden Herdr stale recovery and tests
* no-mistakes(document): Sync herdr backend docs
* no-mistakes: apply CI fixes
* feat(bin): auto-detect runtime backend (#188)
* feat(bin): auto-detect runtime backend from HERDR_ENV/TMUX markers
fm_backend_name now falls through to runtime auto-detection between
config/backend and the hard tmux default: a firstmate running natively
inside herdr (HERDR_ENV=1) now spawns crewmates into herdr by default,
mirroring how harness detection already works in fm-harness.sh. Nesting
resolves innermost-first (tmux wins over a nested herdr pane). Explicit
--backend/FM_BACKEND/config/backend settings always win over detection.
Selecting herdr via auto-detect prints a loud stderr notice; auto-detecting
tmux stays silent so the unconfigured default path is unchanged.
* no-mistakes(review): Captain, pin tmux tests and backend docs
* no-mistakes(document): Sync backend autodetect docs
* no-mistakes: apply CI fixes
* feat(stow): add operational memory capture (#197)
* feat(stow): add operational-memory learnings convention and /stow skill
Add data/learnings.md as the fleet-local operational-learnings home,
a knowledge-routing table in AGENTS.md, and a user-invocable /stow
skill that sweeps a session for uncaptured durable knowledge and
files it to the right disk home before a reset.
* no-mistakes(review): Fix stow backlog note command
* no-mistakes(document): Document stow memory routing
* fix(tests): protect herdr smoke cleanup from default sessions (#199)
* fix(tests): stop real-herdr smoke tests from ever killing the default session
Both fm-backend-herdr-smoke.test.sh and fm-backend-autodetect-smoke.test.sh
tore down their isolated throwaway HERDR_SESSION via a bare/inline-prefixed
`herdr server stop`, which is unscoped and resolves ambiently. On this herdr
client, that ambient resolution silently falls back to whatever server is
already running instead of the requested session - it killed the captain's
live default herdr server twice in production (2026-07-02), once from each
smoke test's cleanup trap.
Add tests/herdr-test-safety.sh with herdr_safe_stop_and_delete: it uses the
explicit-by-name `herdr session stop/delete <name>` form (never the ambient
`server stop`) and, before that, a read-only hard guard
(herdr_refuse_if_default) that re-queries `herdr session list --json` and
refuses outright if the target is literally "default", not found, or flagged
default:true. Fails closed on any ambiguity. Verified empirically against a
real isolated session: refuses on default/nonexistent/empty names without
ever calling stop, and correctly tears down a genuine isolated session while
leaving the default session's workspace state byte-identical before and
after.
* no-mistakes(review): guard herdr delete with fresh check
* no-mistakes(document): Document herdr smoke cleanup safety
* feat(bin): route herdr secondmates into per-home workspaces (#200)
* feat(bin): give each secondmate its own labeled herdr workspace
Give each secondmate its own labeled herdr workspace, and land crewmates
spawned from a secondmate home in that secondmate's own space, instead of
every firstmate home (primary and all secondmates) sharing one "firstmate"
workspace.
bin/backends/herdr.sh: replace the constant FM_BACKEND_HERDR_WORKSPACE_LABEL
with fm_backend_herdr_workspace_label(), resolved fresh from FM_HOME on every
call. The primary (no .fm-secondmate-home marker) still resolves to
"firstmate" - byte-identical to every pre-existing task's recorded label, no
forced migration. A secondmate home resolves to "firstmate-<secondmate-id>".
Every workspace-scoped path (find/ensure, tab create + duplicate check,
list-live recovery, pane-for-tab) uses this same resolution, so recovery and
duplicate checks stay scoped to each home's own space. Workspace and tab
create now pass --no-focus unconditionally (verified: neither focuses by
default once a workspace exists; --no-focus is defense in depth against the
one bootstrap edge case where the very first workspace in a session auto-
focuses).
Also fixes a session-targeting bug found while verifying this empirically:
HERDR_SESSION (env var, exported or inline-prefixed) is not reliably honored
by herdr 0.7.1 CLI subcommands once another herdr server is already running -
it silently falls back to whatever server IS running. fm_backend_herdr_cli
wraps every herdr invocation with both HERDR_SESSION and a trailing
--session <name> flag (verified to route correctly in every case tried),
fixing this for the whole adapter, not just the new label-scoped calls.
bin/fm-spawn.sh: a --secondmate spawn is launched BY the primary's own
process, whose FM_HOME still names the primary at that point. The herdr case
arm now shadows FM_HOME to the secondmate's own home (PROJ_ABS) for just the
two calls that resolve/create the workspace and tab, restored automatically
afterward (bash's temporary-assignment-before-a-command form works for shell
functions too). A crewmate/scout spawned FROM a secondmate's own fm-spawn.sh
process needs no such glue - its own FM_HOME already names it.
Tests: extended tests/fm-backend-herdr.test.sh (per-home label resolution,
--no-focus, --session flag, workspace-find/list-live scoping) and
tests/fm-backend-herdr-smoke.test.sh (a secondmate-shaped home's workspace
label, list-live scoping, restart stability in the multi-workspace shape).
Added tests/fm-backend-herdr-workspace-per-home-e2e.test.sh: the mandatory
isolated E2E, driving real bin/fm-spawn.sh/fm-teardown.sh - a primary-shaped
home into "firstmate", a --secondmate spawn into its own labeled space, a
crewmate spawned FROM that secondmate-shaped home landing in the same space
(this exact path had never run before), teardown closing only the right tab,
and list-live recovery seeing only each home's own tabs. All ten assertions
passed on the real binary; the default herdr session's own workspace state
was confirmed byte-identical before and after every real-herdr test run in
this change.
docs/herdr-backend.md: rewrote "Task container shape" for the workspace-per-
home design (label derivation, the --secondmate FM_HOME-shadow wrinkle, focus
behavior, label-collision/adopt-don't-duplicate semantics, no-forced-
migration), added "Session targeting: the --session flag, not HERDR_SESSION
alone", extended "ID stability" to the multi-workspace shape, and documented
the new E2E test.
* no-mistakes(review): Clarify herdr focus docs
* no-mistakes(review): Captain, clarify herdr server session docs
* no-mistakes(document): Document herdr per-home spaces
* fix(backends): rename Herdr secondmate workspace labels (#203)
* Rename herdr secondmate workspace prefix to 2ndmate-
The primary home keeps the firstmate label; secondmate homes now
resolve to 2ndmate-<id> so the herdr spaces sidebar is unambiguous.
Tests and docs updated; pre-rename workspaces can be aligned with
herdr workspace rename.
* no-mistakes(review): Clarify herdr workspace migration behavior
* no-mistakes(document): Herdr docs label alignment
* feat(bin): add unified session start digest (#201)
* feat(bin): collapse session start into one command
Add bin/fm-session-start.sh, composing fm-lock.sh, fm-bootstrap.sh, and
fm-wake-drain.sh into one ordered digest (lock, bootstrap diagnostics,
wake queue, context files, fleet state) instead of six-plus separate
turns. Lock now runs before bootstrap's mutating sweeps, closing a race
where a second concurrent session could mutate shared state before
discovering the lock was held. A lock refusal prints a loud read-only
banner, skips every mutating step via a new opt-in
FM_BOOTSTRAP_DETECT_ONLY flag on fm-bootstrap.sh, and still completes
the read-only-safe digest.
Add fm_backend_target_exists to fm-backend.sh as a shared, read-only,
never-side-effecting per-task endpoint-liveness primitive for both the
tmux and herdr backends.
Rewrite AGENTS.md sections 3 and 5 around the single command and add
tests/fm-session-start.test.sh.
* no-mistakes(review): Harden session-start read-only guidance
* no-mistakes(review): Suppress read-only tangle repair guidance
* no-mistakes(review): Include orphan status logs
* no-mistakes(review): Captain: make session-start test executable
* no-mistakes(review): Captain: clarify status tail guidance
* no-mistakes(document): Sync session-start docs
* no-mistakes(lint): Shell lint clean
* fix(tests): avoid shellcheck boolean chain
* fix(bin): corroborate herdr idle crew state (#207)
* fix(bin): corroborate herdr idle agent_status with the pane's own text
crew_pane_is_busy trusted a bare `idle` verdict from herdr's agent.get
outright, skipping the tail-regex corroboration unknown already gets.
agent.get reports generation state only (working while the model streams
a turn), so it reads idle for a crew blocked on its own long foreground
no-mistakes run - even though the pane still shows the busy banner the
whole time. Combined with the no-mistakes CLI's 10-run attribution cap,
this made a genuinely working herdr crew read as not provably working,
triggering an immediate stale wake instead of absorb-then-escalate.
* no-mistakes(document): Align herdr busy-state docs
* fix(backends): reuse the herdr firstmate workspace instead of leaking one per spawn (#202)
* fix(backends): reuse the herdr firstmate workspace instead of leaking one per spawn
Every herdr-backed crewmate left an orphaned `firstmate`-labelled workspace
behind, one per task, because `fm_backend_herdr_workspace_find` never matched
the existing workspace: its jq filter used `--arg label ... $label`, and
`label` is a reserved keyword in jq (label/break), so the filter was a compile
error. The error was swallowed by `2>/dev/null`, the find returned empty on
every call, and `workspace_ensure` took the create path each spawn, minting a
fresh workspace. The same collision silently disabled the create-task
duplicate-label check and the bare-selector tab lookup.
Rename the jq variable to `$want` in all three affected filters so reuse,
duplicate detection, and bare-selector lookup work. With reuse restored the
single `firstmate` workspace is persistent (like tmux's session) and teardown
correctly leaves it in place, closing only the task's pane/tab.
Also prune the default tab (label "1") herdr auto-creates inside a freshly
created workspace, best-effort, so the workspace holds only real task tabs.
Add stateful-fake-CLI tests that replay repeated spawn/teardown cycles and
assert one reused workspace, zero orphans, the default tab pruned, and
`workspace create` invoked exactly once. Verified against the real herdr binary
too: the pre-fix code failed the smoke idempotency check (minted wE then wF in
an isolated session); the fix passes it.
Document the workspace lifecycle, the jq-keyword pitfall, the default-tab
prune, and the project-labelled-workspace anomaly (not adapter-created) in
docs/herdr-backend.md.
* docs(herdr): trim workspace-lifecycle addition to current-state facts
The docs/herdr-backend.md convention documents current behavior, not
history - narrative belongs in the PR/commit message. Tightened the
workspace-leak and default-tab-prune write-up down to the operative
facts (the jq reserved-keyword guard, when pruning is safe, and the
persistence caveat), and corrected the CLI-facts table rows to match
the corrected prune timing.
* fix(backends): defer herdr default-tab prune until a real task tab exists
Closing a workspace's LAST tab deletes the whole workspace on real
herdr (verified). Pruning the auto-created default tab right after
workspace create closed the workspace's only tab at that point,
destroying the just-created workspace on every single spawn instead of
reusing it - the fake-CLI unit tests didn't model this real-herdr
behavior, so they passed while the real-herdr smoke test failed with
"container_ensure is not idempotent".
Move the prune into fm_backend_herdr_create_task, right after the
first real task tab is added to a freshly created workspace, when
closing the default tab alongside it is safe. Update the fake-CLI unit
test to match the corrected timing.
Also fix a smoke-test-only bug this surfaced: the test's second
create_task call reused a $CONTAINER captured before the first task
was killed, rather than re-running container_ensure like real
fm-spawn.sh always does immediately before every create_task call - so
once the workspace (correctly) disappeared after its last tab closed,
the stale reference no longer named a live workspace.
* test(backends): guard against jq --arg names colliding with jq keywords
Regression guard for the workspace-leak bug this PR fixes: a jq
--arg/--argjson variable named after a jq reserved keyword (e.g.
label) is a compile error on jq <= 1.6, and this adapter's
2>/dev/null silently turns that into an empty result instead of a
visible failure. Greps bin/ for the pattern so a future violation
fails loudly here instead of silently misbehaving on an older jq.
* docs(herdr): fix per-home staleness and drop contributor-specific example
The workspace-lifecycle write-up hardcoded "the firstmate workspace"
as if the label were always the fixed constant, stale against the
per-home labeling documented earlier in this file (primary: firstmate,
secondmate: 2ndmate-<id>). Rephrased per-home throughout, and pointed
the "workspace this adapter did not derive" case at the existing
Label-derivation section instead of a separate anomaly writeup.
Dropped the "Anomaly: a workspace labelled with a project name"
section - the python-teslemetry-stream example was a contributor's own
environment, not current adapter fact, and it repeated a now-incorrect
FM_BACKEND_HERDR_WORKSPACE_LABEL constant claim. Replaced with one
generic sentence already covered by the corrected wording above.
Also fixed the jq-reserved-keyword guard test's file reference, which
named tests/fm-backend.test.sh when the test actually lives in
tests/fm-backend-herdr.test.sh.
---------
Co-authored-by: kunchenguid <kun@kunchenguid.com>
* fix(backends): make Herdr default-tab pruning safe (#215)
* fix(backends): make the herdr default-tab prune provably safe
The default-tab prune could close a pane holding a LIVE agent: an
ADOPTED workspace (found pre-existing by label match) was pruned using
the same tab-count/label heuristic as a freshly created one, and herdr
derives a workspace's displayed label from its cwd basename when no
explicit --label is given. A captain launching herdr directly inside a
directory named "firstmate" produces a workspace that looks identical,
by label alone, to firstmate's own container - so the very next spawn
adopted the captain's own live workspace and closed their live pane
27ms after creating its task tab (2026-07-02 incident).
The fix is structural: fm_backend_herdr_workspace_ensure now captures
the seeded default tab's id straight from its own `workspace create`
response, only when it just created the workspace. That id threads
through fm_backend_herdr_container_ensure to fm_backend_herdr_create_task,
which is the only function allowed to prune it - an adopted workspace's
caller always passes an empty seeded-tab-id, so create_task never
re-derives "prunable" from a tab's label or count. Defense in depth:
the prune also refuses a tab whose pane reports a working agent.
Covered by new unit tests (adopted-never-prunes, created-prunes-exactly,
the exact label-collision incident shape) and a new isolated real-herdr
E2E test that reproduces the incident against the pre-fix code and shows
it fixed, plus the normal happy path.
* no-mistakes(document): Sync herdr prune docs
* no-mistakes: apply CI fixes
* fix(brief): remove apostrophe breaking bash -n and guard bash 3.2 set -u in spawn (#173)
* fix(brief): remove apostrophe that broke bash -n on fm-brief.sh
The no-mistakes DOD heredoc, built via VAR=$(cat <<EOF ... EOF), had an
unescaped apostrophe in "no-mistakes' own guidance". Nesting a heredoc
inside $(...) makes bash track quote state through the body, so the lone
apostrophe broke parsing of the rest of the script (bin/fm-brief.sh:211),
making the default no-mistakes ship path fail outright. Audited the other
two $(cat <<EOF...EOF) blocks (direct-PR, local-only) for the same class
of bug; none found. Added tests/fm-brief.test.sh as a regression guard.
* no-mistakes(test): fix(spawn): guard empty shared_args under bash 3.2 set -u
* no-mistakes(document): docs(contributing): list tests/fm-brief.test.sh in the test suite inventory
* fix(test): silence shellcheck SC2034/SC2100 in fm-brief.test.sh
Drop the unused out= capture (redirect to /dev/null instead) and quote
the id= assignment so shellcheck stops reading the hyphenated id value
as an arithmetic expression.
* feat: add experimental zellij runtime backend (#217)
* feat(backends): add experimental zellij runtime backend (P3)
Implements bin/backends/zellij.sh on the P1 dispatcher + P2 herdr precedent:
one zellij session, one tab per task, treehouse stays the worktree provider.
Wired through fm-backend.sh/fm-spawn.sh so fm-send/fm-peek/fm-watch/
fm-crew-state/fm-teardown work generically with zero changes to those scripts.
Empirically verified against real zellij 0.44.0: every "gaps to verify" item
from the design report, plus real findings the report missed - new-tab always
steals focus (mitigated with a restore call), zellij action always exits 0
even against a dead target, every pane op needs an explicit --pane-id, and
pane_cwd never tracks a subshell's own cd (treehouse get's exact shape) so
worktree-path discovery uses an active pwd-probe instead of passive JSON
polling. Findings and the full real-CLI evidence log are in
docs/zellij-backend.md.
Full real E2E cycle passed: spawn a real claude crewmate, accept its trust
dialog, steer it, receive done, confirm teardown refuses before merge, merge
local-only, confirm teardown then succeeds and the zellij tab is gone - all
in a scratch FM_HOME against a uniquely-named isolated zellij session, never
touching the real "firstmate" session or the live fleet.
Existing tmux and herdr conformance suites stay green; the two P1-era tests
asserting zellij was unimplemented now assert that of orca instead.
* no-mistakes(review): captain, guard zellij paste payloads
* no-mistakes(review): Captain, guard zellij pane readiness
* no-mistakes(review): Captain, harden zellij dead-target handling
* no-mistakes(review): Captain, harden zellij teardown and tests
* no-mistakes(review): Captain, harden zellij target validation
* no-mistakes(document): Document zellij backend
* docs: document Orca backend adapter contract (#209)
* docs: specify Orca backend adapter contract
* docs: require Orca window target alias
* no-mistakes(test): Captain: handle empty arrays under nounset
* no-mistakes(document): Document Orca backend proposal
---------
Co-authored-by: Stephen Brouhard <vesta@stephens-macbook-air.tail2122af.ts.net>
* feat(backends): add Orca primitive backend support (#210)
* feat(backends): add Orca adapter primitives
* no-mistakes(review): Gate Orca from task spawning
* no-mistakes(document): Document Orca backend limits
* fix(backends): stop mapping Orca Escape to interrupt
* no-mistakes(document): Document Orca primitive key support
* no-mistakes: apply CI fixes
* fix: drop CI-gate rewrite and normalize shared array-guard hunks
---------
Co-authored-by: Stephen Brouhard <vesta@stephens-macbook-air.tail2122af.ts.net>
* fix(backends): submit herdr slash commands reliably (#223)
* fix(backends): herdr slash-command submit verification false-positives on popup autocomplete
Two grok/herdr crewmates left /no-mistakes fully typed but unsubmitted for
minutes while fm-send exited 0. Live-reproduced against real grok 0.2.82:
the herdr adapter's submit verification declared success on ANY pane content
change after Enter, but an argument-taking slash command's first Enter only
closes the popup and expands the composer into an argument-hint placeholder
(/compact -> /compact compaction instructions) rather than submitting - a
real, visible change that isn't a submission. A second Enter is required.
fm_backend_herdr_composer_state replaces the delta check with a structural
read of the composer's own row (located by border-glyph shape, since herdr
exposes no cursor-row primitive), mirroring what cursor_y gives the tmux
adapter. A popup-close-with-placeholder-fill still reads pending, so the
retry loop now correctly sends the needed second Enter instead of stopping
early. The tmux backend was unaffected (its cursor-row read already handled
this correctly, verified side by side against the same live repro).
* no-mistakes(document): Sync herdr submit docs
* feat(skills): publish public stow skill with internal-skill hiding (#221)
* feat(skills): hide agent-only skills from installer discovery, add public stow
Mark every .agents/skills/* skill metadata.internal: true so the
skills.sh installer (npx skills add) hides them from discovery - all
assume a live firstmate home and are meaningless elsewhere. This is
inert to firstmate's own harness skill loader.
Add a new, fully standalone skills/stow for non-firstmate users: sweep
a conversation for durable knowledge and file it into whatever notes
convention the host project/user already has, asking once and
remembering the answer when ambiguous. No shared code with the
internal stow by design.
Document the two-tier layout in README and CONTRIBUTING.
* no-mistakes(review): Tighten public stow tracker routing
* no-mistakes(document): Sync skill docs
* fix(skills): make public stow's undone-next-steps routing local-first
Supersede the earlier ask-before-tracker-write tightening: the
standalone stow no longer treats an issue tracker as a routing option
at all based on inference (git remote, .github/ presence, etc).
Undone next steps always land in a local file by default - an
existing TODO/BACKLOG/NOTES file, or a freshly created local scratch
file otherwise. A tracker (or any other external system) is only ever
used when the user has explicitly said so, this session or as a
previously recorded standing preference.
* no-mistakes(document): Sync skill documentation
* feat(skills): clarify public stow resume and fallback routing (#225)
* feat(skills): resume pointer, routing tiers, default notes file for public stow
Adds a copy-pasteable resume pointer to the safe-to-end verdict so a
new session can pick the work back up cold, states the explicit three-tier
routing priority (explicit instruction > existing local convention > default
NOTES.md fallback) instead of leaving it implicit, and names NOTES.md as the
top-level discoverable default instead of letting each agent improvise a
location. Also removes a leftover internal-tooling word from the maintainer
comment so the public file carries zero internal vocabulary.
* fix(skills): make the public stow default fallback private and gitignored
Supersedes the earlier NOTES.md/ask-once split: the tier-3 default fallback
(no existing local convention fits) is now .stow-notes.md, gitignored so it
never lands unprompted in a shared/committed file. Because it's private, it's
safe for every finding-kind including user preferences, so the separate
ask-once carve-out for personal material is no longer needed there. Tier 2
(an already-established tracked convention) is unchanged and is the only
tier that still writes into a shared file. Step 7's resume pointer now flags
when notes landed in the private fallback and that they can be promoted into
a shared file later.
* fix(skills): split the public stow private fallback by scope, use git exclude
Supersedes the single .stow-notes.md fallback: user preferences (cross-project
by nature) now default to a host-local ~/.stow/notes.md instead of being
siloed into one project's repo. Project-scoped findings still default to
.stow-notes.md at the project root, but it's now kept out of git via the
local-only .git/info/exclude instead of the tracked .gitignore, so the
fallback is truly zero-shared-footprint: nothing lands in a tracked file and
nothing is left for the user to review or commit.
* fix(skills): keep the public stow private fallback sandbox-safe (current dir only)
Supersedes the home-file split: a home-directory path fails for agents
sandboxed to their current working directory, so the tier-3 default now
stays a single .stow-notes.md at the project root for every finding-kind,
including user preferences. Tier 2's user-level memory file is now framed
as a bonus when accessible, never assumed or required. Step 7 gains a
caveat when a preference lands in the project-local fallback: it applies to
this project only, and the user can copy it into their own global memory
file if they want it to follow them everywhere.
* fix(skills): use a current-directory .gitignore for the public stow fallback
Supersedes .git/info/exclude: that mechanism resolves outside the working
directory in a linked worktree, breaking the sandbox-safety guarantee for
exactly the setup this fleet uses everywhere. Switch to an ordinary
.gitignore file in the current directory instead - always in-directory
regardless of worktree layout - creating or appending a .stow-notes.md
line, left uncommitted for the user. If that write itself fails, the skill
still creates .stow-notes.md and tells the user to ignore it manually
rather than blocking. Also scopes the "never writes outside the current
directory" guarantee precisely to tier 3: tier 2 is exempted since it only
targets a destination the user's own existing convention already
established, which can legitimately be a user-level file outside the
project.
* no-mistakes(review): Clarify stow routing precedence
* no-mistakes(review): Clarify stow fallback metadata boundary
* no-mistakes(review): Guard tracked stow fallback
* no-mistakes(review): Clarify stow fallback verdict
* no-mistakes(document): Sync stow skill docs
* no-mistakes(lint): Public skill lint cleanup
* docs: clarify note hygiene guidance (#226)
* docs: add note-hygiene rule to backlog format section
Backlog and task notes accumulate volatile specifics that drift and
mislead; capture the general principle so every firstmate user avoids
trusting a stale note over the authoritative source.
* no-mistakes(review): Clarify note hygiene schema exemptions
* no-mistakes(document): Clarify note-hygiene docs
* revert: drop out-of-scope stow/architecture doc edits
Keep this PR's diff scoped to the AGENTS.md note-hygiene addition only;
skills/stow/SKILL.md, .agents/skills/stow/SKILL.md, and docs/architecture.md
are under separate close review and must not change out of band here.
This reverts commit ed2d73205b01e24ba8ffd40c523a24993f6eb1a5.
* feat(backends): add Orca task lifecycle support (#228)
* feat(backends): add Orca task lifecycle support
* fix(backends): harden Orca spawn lifecycle
* no-mistakes(review): Fix Orca lifecycle cleanup gaps
* no-mistakes(review): Release Orca worktrees without paths
* no-mistakes(review): Guard Orca spawn abort cleanup
* no-mistakes(review): Allow partial Orca child cleanup
* no-mistakes(review): Fix Orca selector and cleanup leaks
* no-mistakes(review): Preserve pathless Orca cleanup metadata
* no-mistakes(review): Harden Orca spawn and teardown lifecycle
* no-mistakes(review): Enforce Orca scout report gate
* no-mistakes(review): Harden Orca teardown path validation
* no-mistakes(review): Harden Orca capture errors
* no-mistakes(review): Harden Orca JSON cleanup validation
* no-mistakes(test): Fix zellij scout teardown fixture
* no-mistakes(document): Document Orca lifecycle support
* Harden Orca runtime and submit verification
* no-mistakes(review): Captain, preserve Orca current-tail verification
* no-mistakes(document): Sync Orca lifecycle docs
* no-mistakes(lint): Captain, silence deliberate ShellCheck
---------
Co-authored-by: Stephen Brouhard <vesta@stephens-macbook-air.tail2122af.ts.net>
* fix: use PR head for review diffs (#229)
* Fix fm-review-diff to compare PR head when pr= is recorded
After no-mistakes fix rounds push to the open PR, the crewmate worktree
branch can lag the authoritative PR head. When meta records pr=, resolve
the compare ref from reachable pr_head= or refs/pull/<n>/head before
diffing against the fetched authoritative base; fall back to the local
branch with a loud warning when the PR head cannot be resolved.
Add behavior tests for pr_head resolution, fetch, unchanged no-pr path,
and unreachable-PR fallback.
* no-mistakes(review): Make review-diff test executable
* no-mistakes(document): Document PR-head review diffs
* no-mistakes: apply CI fixes
* fix: make backend matching shell-portable (#230)
* Make fm-backend.sh backend-name matching shell-portable for zsh
Replace word-split-dependent for-loops in fm_backend_is_known() and
fm_backend_validate_spawn() with case-based membership tests so sourcing
the library from zsh no longer falsely rejects known backends.
Add zsh/bash regression coverage in tests/fm-backend.test.sh.
* no-mistakes(review): Fix zsh backend loading and validation
* no-mistakes(document): Document backend membership portability
* fix(backends): make herdr respawn idempotent (#231)
* fix(backends): make herdr respawn idempotent against restored-layout husks
herdr persists and restores its session layout (workspaces/tabs/panes)
across a server restart, so a restored fm-<id> task tab comes back a
husk - a dead pane, or a plain agent-less shell - which fm-spawn.sh's
duplicate-tab guard refused unconditionally, forcing manual pane closes
after every restart.
fm_backend_herdr_create_task now classifies an existing same-labeled
tab's pane conservatively (dead/no-agent/live/unknown) and
closes-and-replaces only a confirmed husk, always creating the
replacement tab before closing the old one so a husk that is a
workspace's only tab is never at risk of taking the whole workspace
down with it. A genuinely live agent, or anything not confidently
classifiable, still refuses exactly as before.
* no-mistakes(review): Harden herdr duplicate respawn guard
* no-mistakes(review): Enforce herdr husk cleanup postcondition
* no-mistakes(document): sync herdr respawn docs
* no-mistakes: apply CI fixes
* fix: absorb stale wakes during active validation (#233)
* fix(watcher): stop stale_is_terminal from ignoring an active run-step
A crewmate's status log gets no new entry once firstmate hands it to a
no-mistakes validation (the sparse status-reporting contract), so the
log's last line can stay a pre-validation "done:" (or needs-decision/
blocked) leftover for the run's entire duration. fm-watch.sh's
stale_is_terminal only reads that raw last line - it has no run-step
awareness - so it kept surfacing a stale pane as immediately terminal
every time it went quiet for two polls, no matter how actively the
pipeline was validating (confirmed live against fm-herdr-respawn-idem,
whose status log's last line is literally "done: ..." while its
no-mistakes run-step reads "validating (running)"). crew_is_provably_working
now gets a chance to override a stale captain-relevant log line on a
new stale hash, exactly as it already did for a non-captain-relevant one.
Also fixed a separate, independently-confirmed dead code path in
fm-crew-state.sh: its cross-branch run-attribution fallback shelled out
to `no-mistakes axi` (bare) expecting a runs[N]{...} TOON table that the
real CLI (v1.32.2) never emits - verified the axi surface exposes only
abort/logs/respond/run/status. Replaced it with the real top-level
`no-mistakes runs` listing.
* no-mistakes(document): Sync watcher stale docs
* no-mistakes(lint): Fix stale fake run-list variables
* docs: clarify no-mistakes evidence commit handling (#232)
* docs: no-mistakes evidence commits in crew branches are intentional
* no-mistakes(review): Scope evidence guidance to project repos
* no-mistakes(document): Document evidence commit policy
* fix: tighten Orca backend parsing (#237)
* fix: tighten orca parser coverage
* no-mistakes(document): Sync Orca backend docs
* no-mistakes(lint): Captain, lint clean
---------
Co-authored-by: Stephen Brouhard <vesta@stephens-macbook-air.tail2122af.ts.net>
* docs: add backend setup guides and slim README (#238)
* docs: make README pointer-first, add per-backend setup guides
Trim the Quick Start and How It Works walls of prose down to overview plus
pointers, relocating every removed sentence's content into docs/architecture.md,
docs/configuration.md, or the relevant backend doc. Add docs/tmux-backend.md as
the reference-backend setup guide, and add a Setup section to each experimental
backend doc (herdr, zellij, Orca) covering prerequisites, selection, first run,
watching/attaching, verification, and limitations. Record the README convention
in CONTRIBUTING.md.
* no-mistakes(review): Clarify backend setup docs
* no-mistakes(review): Clarify tmux secondmate support
* no-mistakes(document): Align backend documentation
* docs: stop telling users to run fm-spawn.sh for backend selection
fm-spawn.sh is firstmate-internal; a user never runs it directly. Rephrase
every user-facing backend-selection sentence across the tmux/herdr/zellij/orca
guides and docs/configuration.md to present the actual user mechanisms: a
local config/backend file, FM_BACKEND at launch, or telling the first mate in
chat. Internal-reference mentions of the --backend flag (fm-spawn.sh usage
notes, test coverage lists) are left as mechanics, not user instructions.
* Document herdr dual license in docs and install hint (#239)
Add AGPL-3.0-or-later/commercial licensing note to herdr-backend Setup
and the missing-binary error in bin/backends/herdr.sh.
* feat: support multiple X-mode follow-ups (#241)
* feat(x-mode): raise X follow-up cap to 3 within a 7-day window
Matches the relay's parallel contract change: fm-x-link.sh now records a
follow-up counter (with --carry-count to preserve it across a re-link onto
a successor task), fm-x-followup.sh posts up to three follow-ups per
mention within a 7-day window instead of one within 24h, clearing the
link on --final, cap exhaustion, window lapse, or a distinguishable relay
rejection (fm-x-reply.sh HTTP 409 -> exit 9) rather than treating that
rejection as a retryable failure. AGENTS.md and the fmx-respond skill are
updated to keep usage disciplined: spend follow-ups only on genuine
milestones, always finish with --final.
* no-mistakes(review): Update follow-up dry-run docs
* no-mistakes(review): Captain: refresh X follow-up docs
* no-mistakes(review): Captain: harden X follow-up relinks
* no-mistakes(review): Captain: harden follow-up state persistence
* no-mistakes(document): Document X follow-up carryover
* feat(backends): add experimental cmux runtime backend (#246)
* feat(backends): add cmux runtime backend (experimental)
Session-provider-only adapter for cmux (bin/backends/cmux.sh), mirroring
zellij/herdr structurally, wired into fm-backend.sh and fm-spawn.sh with
--secondmate refused for now. Verified against the real cmux 0.64.17 app:
send does not auto-submit, cwd is creation-time-frozen (zellij-shape,
pwd-marker-probe workaround), close-surface refuses on a workspace's last
surface (falls back to close-workspace), workspace ids do not survive a
relaunch, and the control socket defaults to cmuxOnly access (requires a
one-time password-mode setup, documented in docs/cmux-backend.md). Also
found and fixed a live bug during development: read-screen fails on a
surface that has never been written to, so liveness now uses list-panes
instead. Fake-CLI unit suite (40 tests), a real-binary smoke test, and a
full spawn/steer/peek/done/merge/teardown E2E pass against a real claude
crewmate all pass, including the popup/second-Enter regression class.
* no-mistakes(review): Harden cmux recovery and password parsing
* no-mistakes(review): Harden cmux capture failure handling
* no-mistakes(review): Mark cmux test scripts executable
* no-mistakes(review): Scope cmux workspaces and teardown
* no-mistakes(review): Captain, honor cmux password config override
* no-mistakes(review): Captain, hash cmux home labels
* no-mistakes(document): Sync cmux backend docs
* feat(agents): add firstmate coding guidelines skill (#248)
* Add firstmate-coding-guidelines skill (AGENTS.md diet PR 0)
Encodes the knowledge-placement decision tree, one-owner rule, and
inline-stub pattern from the diet analysis so future contributions stop
adding conditional detail inline. AGENTS.md gets one section-13 trigger
line; fm-brief.sh's REPO argument has no reliable signal for "this is
firstmate's own repo", so the load instruction goes in CONTRIBUTING.md's
Development section instead of the scaffold.
* no-mistakes(review): Captain, align tracked-material trigger scope
* no-mistakes(document): Sync coding guidelines docs
* no-mistakes(lint): Fix Markdown style issues
* fix: add turn-end supervision guard (#249)
* feat: structural Stop-hook backstop for primary turn-end supervision
fm-guard.sh is pull-based: it only warns when some other supervision
script happens to run, so a primary session that ends a turn without
re-arming the watcher and then runs no further fleet-touching command
can sit blind for hours (the 2026-07-04 incident this fixes).
Add bin/fm-turnend-guard.sh, a Claude Code Stop hook registered in the
tracked .claude/settings.json, that fires on every primary turn end and
blocks (exit 2, verified empirically to force continuation) when work
is in flight with no fresh watcher beacon. It never blocks more than
once per turn, using Claude Code's own stop_hook_active loop-guard
field, and scopes itself to the actual primary checkout only (inert in
crewmate/scout worktrees and secondmate homes).
Factor the shared "in-flight but no live watcher" predicate out of
fm-guard.sh into bin/fm-supervision-lib.sh so the pull-based banner and
the push-based hook can never drift on what "unhealthy" means.
Document the verified Stop-hook mechanism and scoping in
docs/turnend-guard.md, add a harness-adapters note, and cover the
predicate and hook with tests/fm-turnend-guard.test.sh.
* no-mistakes(review): Respect active home in turnend guard
* no-mistakes(review): Require live watcher for turn-end guard
* no-mistakes(review): Captain: portable turn-end timing
* no-mistakes(document): Sync turn-end guard documentation
* feat(backends): auto-detect cmux runtime (#250)
* feat(backends): auto-detect cmux runtime from CMUX_WORKSPACE_ID
Wires cmux into fm_backend_detect the same way herdr already is: a
firstmate process running inside a cmux-spawned terminal now spawns
new tasks into cmux by default, no config needed. Verified from cmux's
own shipped source that CMUX_WORKSPACE_ID/CMUX_SURFACE_ID/CMUX_SOCKET_PATH
are unconditionally, non-overridably injected into every terminal
surface it spawns, and that cmux's own CLI treats CMUX_WORKSPACE_ID as
its own ambient-target fallback - the same role $TMUX/HERDR_ENV play for
their backends. CMUX_WORKSPACE_ID is checked last (after $TMUX and
HERDR_ENV=1) since cmux is a terminal application, not a nestable
multiplexer. Socket auth (config/cmux-socket-password) stays required
regardless of how the backend was selected; the existing spawn refusal
now also names the config/backend=tmux / --backend tmux opt-out for a
caller who never explicitly chose cmux.
A live env dump inside a real cmux terminal was not obtained safely on
the shared dev machine (documented in docs/cmux-backend.md); this rests
on the source read instead, mirroring this doc's existing
verified-from-source precedent.
* no-mistakes(review): Fix cmux autodetect docs and tests
* no-mistakes(document): Document cmux auto-detection
* fix(afk): support herdr away-mode injection (#251)
* fix(afk): make the away-mode daemon backend-aware for herdr
bin/fm-supervise-daemon.sh discovered its supervisor pane and injected
via raw tmux calls only, so /afk failed outright on a herdr-based
fleet (TMUX_PANE unset, firstmate:0 fallback unresolvable).
Discovery now resolves backend (tmux|herdr) and target independently,
mirroring fm-backend.sh's own runtime auto-detection, with an explicit
FM_SUPERVISOR_BACKEND override alongside the existing FM_SUPERVISOR_TARGET.
zellij/orca refuse loudly at startup instead of misapplying tmux
primitives. Injection (pane-exists probe, busy-guard, composer-guard,
verified submit) now dispatches through bin/fm-backend.sh's generic
primitives, adding a new fm_backend_composer_state dispatcher; the
tmux path is byte-identical to before. Also fixes a pre-existing bug
in fm_backend_target_exists's herdr arm (missing --session, so it
silently misrouted once more than one herdr server was running) found
while verifying this end to end against a real isolated herdr session.
Classification, batching, max-defer, the marker contract, locks, and
wake-queue handling are unchanged - this is a transport-layer fix.
* no-mistakes(review): Corroborate Herdr idle busy state
* no-mistakes(review): Stabilize Herdr daemon startup wait
* no-mistakes(review): Captain, route cmux composer and update AFK docs
* no-mistakes(document): Document AFK supervisor backend support
* docs(agents): move X-mode procedures out of AGENTS (#253)
* docs(agents): collapse X-mode section 14 into fmx-respond/docs pointers
AGENTS.md diet PR 1 of 3 (agentsmd-diet-s2 report, move-plan items 1-2).
Replaces section 14's "Answering"/"Completion follow-up"/"Conversations"/
"Length and threads"/"Preview / dry-run" blocks (54 lines) and the
"Mechanism" narrative (6 lines) with two short pointers: fmx-respond
(section 13) for the procedure, docs/configuration.md "X mode (.env)"
for the wire protocol. Net -55 lines in AGENTS.md.
Destination edits landed first, deletions second (q4 discipline):
- docs/configuration.md: added the "purely additive, watcher untouched"
guarantee that AGENTS.md's Mechanism block stated but configuration.md
did not.
- fmx-respond/SKILL.md: added the x-mode-error wake boundary (report as
a blocker, do not load this skill), the --image flag for replies and
follow-ups, the "images are for real artifacts, not prose" rule, and
the dry-run compact-image-marker behavior - none of these were
previously in the skill even though AGENTS.md described them, so they
were genuine gaps, not pre-existing duplication. Also made the skill's
own "Completion follow-up" section the sole, full owner of that
procedure instead of deferring to AGENTS.md section 14 for substance
that no longer lives there (two internal cross-references updated to
point at section 8's terminal-wake trigger and the skill's own section
instead).
Mechanical line-by-line audit of every removed AGENTS.md line:
Mechanism block (6 lines removed):
- bootstrap artifact-writing description -> already owned by
docs/configuration.md "X mode (.env)" (locked-bootstrap paragraph)
- check-shim/poll mechanism description -> already owned by
docs/configuration.md same section
- missing-deps/x-mode-error diagnostic description -> already owned by
docs/configuration.md ("Relay auth or config problems...") plus
bin/fm-x-poll.sh's own header comment for the missing-curl/jq mechanics
- opt-out artifact removal description -> already owned by
docs/configuration.md same section
- "purely additive, no edit to fm-watch.sh/fm-watch-arm.sh/fm-wake-lib.sh/
afk daemon" guarantee -> MOVED to docs/configuration.md (added in this
PR; this fact had no other home before)
Answering/Completion follow-up/Conversations/Length and threads/
Preview-dry-run blocks (54 lines removed):
- x-mention wake -> load fmx-respond: already owned by section 13's
existing trigger line (unchanged) and restated in the new pointer
- x-mode-error wake -> report as blocker, don't load fmx-respond: MOVED
to fmx-respond/SKILL.md (added in this PR)
- inbox-draining, classification, acting, reply composition, submission,
cleanup-on-success/failure: already owned by fmx-respond/SKILL.md
"Procedure" section (unchanged, pre-existing)
- owner-only routing / captain-as-asker framing: already owned by
fmx-respond/SKILL.md "The asker is your own captain" section
- standing X-mode authorization / autonomous posting / dry-run as only
non-posting path: already owned by fmx-respond/SKILL.md same section
- acknowledge-first -> act -> follow-up shape, three-case classification:
already owned by fmx-respond/SKILL.md "A request to act on" section
- destructive/irreversible/security-sensitive escalation guardrail:
already owned by fmx-respond/SKILL.md "Public channel..." section and
Procedure step 2c
- dismiss-instead-of-reply for pure acknowledgments, relay re-offer
prevention, dry-run honoring: already owned by fmx-respond/SKILL.md
Procedure steps 2b/2c/2e-skip and docs/configuration.md
- public-safety bar (no task ids/internals/captain-private/secrets):
already owned by fmx-respond/SKILL.md "The reply is public" section
- never-inline-into-shell-command / --text-file or stdin: already owned
by fmx-respond/SKILL.md Procedure step 2e and Notes
- --image flag for replies (formats, base64, no-inline guarantee): MOVED
to fmx-respond/SKILL.md Procedure step 2e (added in this PR - this was
not previously in the skill)
- fm-x-link field names (x_request=, x_request_ts=, x_followups=):
already owned by AGENTS.md section 2's state/<id>.meta field list
(untouched, out of scope for this PR) and fmx-respond/SKILL.md
- carry-count/carry-ts relink behavior, three-follow-up budget, milestone
sparingness, --check/--text-file posting, connector/followup wire
detail, --final clearing, cap/window graceful degradation: already
owned by fmx-respond/SKILL.md "Completion follow-up" section (now sole
owner) and docs/configuration.md wire-protocol paragraphs
- --image flag for follow-ups: MOVED to fmx-respond/SKILL.md "Completion
follow-up" section (added in this PR - genuine gap)
- "failed task still gets an honest final follow-up": already owned by
fmx-respond/SKILL.md "Completion follow-up" section
- FMX_DRY_RUN whole-loop previewability: already owned by
fmx-respond/SKILL.md "Dry-run / preview mode" section
- in_reply_to conversation continuity, untrusted-thread handling,
follow-up worthiness judgment, relay-owned self-reply guard/cap:
already owned by fmx-respond/SKILL.md "The direct ask is the captain's"
section and Notes (one bullet is a verbatim match)
- concise-by-default / no hand-numbered threads: already owned by
fmx-respond/SKILL.md "Voice" section
- auto-split behavior, char/tweet caps, premium-independence, wire shape
({text}/{text,texts}): behavior already owned by fmx-respond/SKILL.md
Voice section; exact defaults and wire shape already owned by
docs/configuration.md; "premium-independent" mechanics already owned
by bin/fm-x-reply.sh's own header comment
- "images are for real artifacts, not prose": MOVED to fmx-respond/
SKILL.md "Voice" section (added in this PR - genuine gap)
- image-on-thread wire behavior: already owned by docs/configuration.md;
reinforced in fmx-respond/SKILL.md's new --image note
- dry-run POST-body shape, endpoint marker, truthy-value definition,
jq-only dependency, end-to-end testability, x-outbox inspection:
already owned by fmx-respond/SKILL.md "Dry-run / preview mode" section
(several near-verbatim matches) and docs/configuration.md wire detail
- dry-run compact image marker: MOVED to fmx-respond/SKILL.md "Dry-run /
preview mode" section (added in this PR - genuine gap)
Section 8's terminal-wake completion-follow-up trigger (the one fact
required to survive inline) is untouched and already present; the new
section 14 pointer references it instead of restating it.
Nothing outside section 14 (plus the two destination files) is touched.
Full test suite green, including all 74 fm-x-mode.test.sh checks.
* no-mistakes(review): Preserve X-linked follow-up triggers
* no-mistakes(review): Fix x-mode error trigger
* no-mistakes(document): Docs cross-reference synchronized
* no-mistakes(lint): Clean Markdown lint pass
* docs: trim duplicated harness guidance (#255)
* docs(agents): trim section 4 harness/secondmate duplication
AGENTS.md diet PR 2 of 3 (data/agentsmd-diet-s2/report.md, move-plan
items 3-4; redundancy item 2 folded into item 3).
Removed the five claude/codex/grok/pi/opencode model/effort-flag
bullets from section 4 - byte-for-byte duplicated by
harness-adapters' "Launch profile axes" table (which is already a
superset: it carries verified CLI versions per adapter that the
AGENTS.md bullets lacked). Replaced with a one-line pointer; the
skill is already loaded before every spawn per section 4's own
closing trigger, so no new trigger was needed.
Moved the config/secondmate-harness model/effort pin-format detail
(the `<harness> [<model>] [<effort>]` line format, the
secondmate-model/secondmate-effort accessors, back-compat, and the
durability-across-respawn behavior) into secondmate-provisioning,
which is already a mandatory load at every secondmate lifecycle
touchpoint. Added the destination content to the skill first, then
replaced the AGENTS.md paragraph with a 3-line pointer.
Mechanical audit - every removed line's new home:
- 5 harness bullets (claude/codex/grok/pi/opencode model+effort
flags, per-harness max-omission rationale) -> already present in
harness-adapters SKILL.md's "Launch profile axes" table (lines
53-59), confirmed fact-by-fact before deleting.
- "config/secondmate-harness may also pin..." paragraph (pin format,
bare-harness back-compat, secondmate-model/secondmate-effort
accessors, per-spawn override precedence, respawn durability,
secondmate-only scope) -> secondmate-provisioning SKILL.md's
"Charter and seed" section, added verbatim before this trim.
- The following paragraph (inheritable config: crew-dispatch.json,
crew-harness, backlog-backend) is untouched - out of scope for
this PR, still inline.
- The bootstrap CREW_DISPATCH effort-mismatch diagnostic sentence is
untouched - not part of the five-bullet duplication, stays inline.
No script changes. Section 4 shrinks from 104 to 87 lines
(958 -> 901 total AGENTS.md lines) with zero facts lost: every fact
is reachable through harness-adapters or secondmate-provisioning,
both already mandatory loads at the relevant lifecycle points.
* no-mistakes(document): Align secondmate skill triggers
* no-mistakes(lint): Markdown style clean
* fix: anchor turn-end Stop hook to project root (#256)
* Fix turn-end Stop hook to use CLAUDE_PROJECT_DIR path
Claude Code runs hook commands via /bin/sh from the session cwd, so the
bare relative bin/fm-turnend-guard.sh path fails when cwd is not the repo
root. Anchor the command with "$CLAUDE_PROJECT_DIR"/bin/fm-turnend-guard.sh
instead; verified CLAUDE_PROJECT_DIR is set on Stop hooks in Claude Code
2.1.201. Document the cwd caveat and add a settings.json regression test.
* no-mistakes(document): Document Stop hook path anchoring
* docs: trim firstmate agent guidance duplication (#258)
* docs: trim AGENTS.md redundancy (diet PR 3/3)
Consolidates five duplicated passages to a single owner each, per
data/agentsmd-diet-s2/report.md redundancy items c3-c7:
- Inheritable-config propagation mechanism: owned by section 3 (where
the sweep runs); sections 4 and 7 keep compact references. Section 4
retains its one genuinely unique fact (crew-harness inherit-vs-fallback
semantics), just no longer restates the propagation mechanism itself.
- Landed-work definition: owned by section 7's ship-teardown detail
(PR-containment mechanics, pr= discovery fallback); section 1's hard
rule #3 keeps the rule plus a three-case summary and a pointer.
- Backend meta-field enumeration: owned by docs/configuration.md
("Runtime backend", already comprehensive including cmux) and each
backend's own doc; AGENTS.md keeps only the fields common to every
task plus a pointer.
- Dropped one redundant restatement of "silence is correct while
waiting" in section 8.
- Worktree-tangle guard explanation: owned by section 8 (already the
fuller, cross-referenced version); section 3's TANGLE bullet keeps
the remediation action and points at section 8 for the why.
Also adds two captain-requested single-sentence rules: invoke bin/
scripts by absolute $FM_ROOT path after any cd away from the home, and
a backend spawn refusal must be surfaced to the captain rather than
silently worked around by switching backends.
AGENTS.md: 901 -> 889 lines, 112355 -> 108560 bytes.
* no-mistakes(review): Clarify post-cd bin invocation guidance
* no-mistakes(document): Sync AGENTS trim docs
* no-mistakes(lint): Fix Markdown line style
* feat(backends): improve cmux detection and socket-mode guidance (#259)
* feat(backends): cmux detection fallbacks and socket-mode matrix
Workstream A: cmux's bundled claude wrapper strips every CMUX_* env var on
its passthrough path (reproduced live 2026-07-04, cmux 0.64.17), so a
claude-harness firstmate inside a cmux tab has no CMUX_WORKSPACE_ID.
fm_backend_detect now falls back - macOS-only, only when the primary marker
is absent - to __CFBundleIdentifier=com.cmuxterm.app and then a process
ancestry walk resolved by bundle id (lsappinfo) plus a bundle-shaped ps comm
match. Innermost-first ordering is unchanged and absorbs the
tmux-inside-cmux bundle-id false positive; the auto-detect NOTICE names the
winning fallback signal.
Workstream B…
This was referenced Jul 13, 2026
cdonovan-abtex
referenced
this pull request
in cdonovan-abtex/firstmate
Jul 16, 2026
…t of failure) The anti-loss machinery (activity-log guard/manifest/watchdog/recovery + dish-#3 completeness check on Codex + digest/backfill tooling + hooks + plists) lived only in gitignored data/ on one machine. Move the source-of-truth into tracked officers-mess/ so a push backs it up off-box; data (logs/flags/drafts) stays local. install.sh redeploys. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jul 21, 2026
prodempsey
added a commit
to prodempsey/firstmate
that referenced
this pull request
Jul 23, 2026
A treehouse lease is released only by an explicit `treehouse return`, which bin/fm-teardown.sh runs on the normal path. A crewmate whose agent dies, is killed, or is abandoned never reaches teardown, so its pool slot stays leased forever; `treehouse prune` will not take it back either, because prune skips anything with an owner reservation. The pool silently fills with slots held by tasks that no longer exist until it hits exhaustion and blocks all new dispatch. Add the abnormal path: bin/fm-lease-lib.sh owns the safety model and bin/fm-lease-reclaim.sh sweeps the pools, wired into session start as a locked mutating sweep (after fleet sync, so the landed check reads a freshly fast-forwarded default branch). The rule is asymmetric on purpose: a false park costs a report line, a false reclaim destroys unlanded work. A lease is freed only when its owner is provably gone - no live agent process AND no task meta in this home or any secondmate home - and its worktree provably holds nothing (clean, and landed on a remote or in the default branch). Anything else is PARKED, never returned, and re-reported every run until the captain rules on it (prime directive kunchenguid#3).
This was referenced Sep 16, 2026
This was referenced Sep 16, 2026
davestanyer
pushed a commit
to davestanyer/firstmate
that referenced
this pull request
Sep 17, 2026
Catch main up to upstream (14 commits)
LorenzoMinghini
added a commit
to LorenzoMinghini/firstmate
that referenced
this pull request
Sep 17, 2026
…er (v13, Greptile review of v12) Greptile's v12 review flagged three P1s that all shared the same root cause: the v12 cap-fire path wrote the per-hash marker and the window-scoped marker BEFORE fm_wake_append, with an incomplete rollback on fm_wake_append failure. P1 #1 'Marker precedes durable wake': if the watcher dies between the per-hash marker write and fm_wake_append, the marker silences retries even though no terminal wake was queued. P1 kunchenguid#2 'Window marker survives append failure': the v12 rollback only removed the per-hash marker on fm_wake_append failure; the window-scoped marker remained and suppressed every hash for FM_CAP_HORIZON_SECS even though no wake was queued. P1 kunchenguid#3 'Failed window marker permits repeats': on window-scoped marker write failure v12 logged the error but continued, so a hash-churning busy pane would rebuild the escalation counter and re-fire. v13 fix: invert the ordering - queue the durable wake FIRST, then write both markers. On fm_wake_append failure: NO markers written, exit 1 (equivalent to the v3 behavior). On per-hash marker write failure: log and exit 1 (durable wake is visible; next poll can re-escalate). On window-scoped marker write failure: rm the per-hash marker (which we just wrote) for exact state, log, exit 1. No partial cap state is ever visible to a subsequent poll. Tests: - All 10 pre-existing wedge-cap tests pass on the v13 code. - New: test_wedge_cap_marker_write_after_durable_wake_v13 - drives the cap with FM_WAKE_QUEUE in a read-only directory. Asserts NO marker is written when fm_wake_append fails. - New: test_wedge_cap_failed_window_marker_rolls_back_per_hash_v13 - plants the window-scoped marker path as a non-empty directory so the per-hash marker succeeds and the window-scoped marker fails. Asserts the per-hash marker is rolled back and no partial cap state is visible. Side cleanup: bash's 'Is a directory' diagnostic for now goes through a subshell that stderr-suppresses the wrapper's stderr, so the operator only sees our triage_log message. All 12 wedge-cap tests pass on the rebased + v11 + v12 + v13 code (~12m runtime).
This was referenced Sep 19, 2026
This was referenced Sep 22, 2026
Lab captures of the running screenCaptured with 1 Office2 TasksLab lifecycle and cost
|
This was referenced Sep 25, 2026
NewAiCoder
referenced
this pull request
in NewAiCoder/firstmate
Sep 26, 2026
* fix(bin): let a structural harness ancestor outrank a retained marker bin/fm-harness.sh treated a verified environment marker as unconditionally authoritative, so a Codex session started from an environment that had retained CLAUDECODE=1 detected as claude. Session start then emitted Claude's Stop-owned supervision protocol to a Codex primary, and every turn end was blocked for missing Claude recovery. The defect is the precedence boundary, not any one harness. codex, opencode, kimi, and muse publish no identity marker at all, so with markers winning outright any retained CLAUDECODE renamed them; the Cursor-before-Claude ordering was a point patch on the same class of problem, and the launch-time marker clearing only ever covered sessions fm-spawn started. Markers and ancestry are now separate evidence layers that detect_own arbitrates: - no ancestry match, or no marker: the single available layer answers, unchanged; - same harness family: the marker's finer verdict stands, so a launch-selected pi-signed is not flattened to pi by an ancestry walk that can only see the shared launcher name; - different harness with a structural (command-name) ancestor: ancestry wins, because only ancestry proves who owns the process tree; - different harness with only a bare-interpreter script-path match: the marker wins, since a harness-shaped path in some node process's arguments is weaker evidence than a harness publishing its own identity. The correction is symmetric: a retained CURSOR_AGENT no longer renames a claude worker nested under cursor either. Adds fm-harness.sh ancestry [<pid>], ancestry evidence with no marker layer, so a real harness process can be asked what the walk makes of it. tests/fm-harness-precedence.test.sh is the portable regression, built from real renamed processes with no harness installed. Every case drives the two layers apart and asserts each alone as well as the combination, so no case can pass vacuously; it also pins Codex's real two-process install topology, since the fix depends on the native binary being what a tool subprocess meets first. The opt-in drift guard gains the matching live half: each installed harness's real running process must still be identified by the ancestry walk, and it fails naming the harness and version when a release changes that name. Documentation follows the corrected contract in the script header, the harness-adapters detection section, the codex, opencode, kimi, and cursor references, and a dated verification record. * fix(tests): drop the unused argument pass-through in the shim-topology helper bin/fm-lint.sh refused the branch: run_shim declared a `[ancestry]` argument and forwarded "$@", but every call site that varies the environment or passes the ancestry subcommand invokes the shim entry point directly, so the helper is only ever called with no arguments (ShellCheck SC2120/SC2119). Behavior is unchanged: with no arguments "$@" expanded to nothing. * fix(bin): examine the top of the process chain instead of assuming init harness_ancestry stopped as soon as the next pid was 1, on the assumption that pid 1 is always init and can never be a harness. Inside a PID namespace that assumption inverts: the harness itself is pid 1, so the walk never examined the one process that proves who owns the tree, reported no ancestry at all, and handed the verdict straight back to a retained marker. A real Codex session under `codex sandbox`, holding CLAUDECODE=1 and CLAUDE_CODE_ENTRYPOINT=cli, is exactly that shape: it resolved claude and rendered Claude's Stop-owned supervision protocol even with the marker-vs-ancestry precedence boundary in place. The same probe now resolves codex and renders the Codex foreground checkpoint. A host's real pid 1 (init, systemd, launchd) matches no harness name, so examining it costs one ps call and can introduce no false positive; the walk still stops once that top process has been read, and a non-numeric or zero ppid still ends it. tests/fm-harness-precedence.test.sh pins the namespace shape with a fake ps that reports every process as bash with ppid 1 and pid 1 as the harness. The case asserts the marker still answers alone when pid 1 is host-shaped, so it cannot pass vacuously, and it fails against the previous stop condition. * docs(verification): record the real-Codex retained-marker evidence The existing record proved the precedence boundary with the portable regression and recorded each installed harness's process name behind the ancestry walk, but it had no evidence from a real Codex process actually holding a retained Claude marker, which is the failure the boundary exists for. Adds the dated before/after result from codex-cli 0.152.0 under `codex sandbox`, with the exact command and the decisive verdict and rendered protocol on each side, and records the second boundary that shape exposed: the walk must examine the top of the process chain, because inside a PID namespace the harness is pid 1. Refreshes the portable regression's observed output for the case it gained. * no-mistakes(review): blind ancestry in marker-pinned harness tests * no-mistakes(review): blind ancestry in the Pi guard-routing test * no-mistakes(review): classify precedence suite, dedupe ps stub, soften claims * no-mistakes(review): model the spawn-and-wait Codex shim topology * no-mistakes(document): correct stale muse marker-clearing detection claims * no-mistakes: apply CI fixes * fix(bin): examine the top of the chain in the lock and nudge walks too The pid-1 defect corrected in bin/fm-harness.sh survived unchanged in the two other harness-ancestry walks, on the exact topology the branch verified against a real Codex process. bin/fm-session-lock-lib.sh's fm_harness_ancestry_pids stopped as soon as the next pid was 1, so a firstmate whose harness is pid 1 of its own PID namespace could not find that harness at all and did not recognize its own session lock. bin/fm-sessionstart-nudge.sh carried the same stop plus a blanket rejection of a lock pid of 1, so the same session was told to run session start again on every turn. Both walks now compare the top process before stopping, matching the shape used in bin/fm-harness.sh. For the lock walk this is safe because fm_harness_process_matches rejects a host's real pid 1. For the nudge, `kill -0` still gates the lock pid, and on a host an unprivileged `kill -0 1` fails, so a lock file that wrongly names pid 1 leaves the hook silent rather than acting on init. Each walk gains one regression case. The lock case drives a deterministic process table whose pid 1 is the harness and asserts a host-shaped pid 1 still finds nothing, so it cannot pass vacuously. The nudge case needs a real PID namespace, because the builtin `kill -0` gate cannot be reached through a fake ps, and it first proves the same fixture nudges with no lock present; it skips explicitly where unprivileged namespaces are unavailable. * no-mistakes(review): assert comm-strength detection from subprocess vantage in drift guard * fix(bin): verify the live harness guard at the strength the guarantee needs The marker-versus-ancestry boundary this branch ships is a strength claim: detect_own hands an args-strength verdict straight back to a retained foreign marker, so a harness is only protected where the ancestry walk reaches it at comm strength. The installed-harness drift guard probed the pane process alone. Under an interpreter shim the pane process IS the shim, whose own script path is args strength, while the native binary that carries comm strength is its child. The guard therefore observed args for Codex, passed, and would have kept passing if a release stopped spawning that native child at all, while real sessions silently regressed to the original bug. fm-harness.sh gains `ancestry-subtree`, which asks the walk from the pane process and every descendant of it, the vantage a tool subprocess actually occupies. The guard now requires comm strength somewhere in that set and requires every vantage to name the same harness. This supersedes the preceding commit's in-guard leaf walk, which reached the same vantage but left the logic inside the test file, where CI could not pin it and nothing else could reuse it. A harness-dependent check needs both halves: `tests/fm-harness-precedence.test.sh` now carries a portable case proving the subtree probe reaches a strength the top-of-session probe cannot, mutation checked twice, once against the pre-change script and once by disabling descendant enumeration. The subtree walk also avoids depending on tty and process-group semantics that differ between Linux and macOS. Verified live: codex-cli 0.152.0 reports [args codex;comm codex] and Claude Code 2.1.257 reports [comm claude]. * no-mistakes(review): narrow drift guard to the upward vantage path * no-mistakes(review): judge only comm-strength vantages in drift guard * no-mistakes(document): drop duplicated rationale in detection precedence evidence * no-mistakes(review): fix pid-1 nudge case vacuity and descent no-arg expansion * no-mistakes(document): drop branch-relative phrasing in detection precedence evidence * no-mistakes(review): guard remaining empty positional expansions in fm-harness * no-mistakes(document): scope cursor marker-ordering claim to the marker layer * no-mistakes(review): Prefer comm-strength leaves in equal-depth descent ties * no-mistakes(document): Document comm-strength descent tie-break --------- Co-authored-by: NewAiCoder <170579485+NewAiCoder@users.noreply.github.com>
Grijak
added a commit
to Grijak/firstmate
that referenced
this pull request
Sep 28, 2026
… launch (kunchenguid#3) * feat(bin): check local model availability before every Pi handoff A local model server such as llama.cpp behind llama-swap can be switched off and holds one loaded model at a time. Firstmate used to hand a task to a Pi worker on a local model without asking the server, so a worker could open against an unreachable server and stall, or make the server swap models and interrupt the sessions using the loaded one. bin/fm-local-model.sh reads Pi's own effective configuration (agent directory, models.json with Pi's comment and trailing-comma rules, model-level baseUrl, strict settings.json default) and asks each local server once, read-only and bounded, for its model list and load state. It reports ready, unavailable, busy (a model switch would be needed), unknown, unchecked, or not-local, and never claims spare session capacity the server does not report. - Session start: the deferred network stage prints one informational BOOTSTRAP_INFO line per local server named by a Pi dispatch profile. - Every handoff: quota-array-dispatch reruns the check and, when a local model fits but its server is off, tells the captain before any worker opens; the typed resolver rechecks in code and marks such candidates not eligible. - Every Pi launch: fm-spawn refuses a local model that cannot take the work before any endpoint, worktree, or record exists, and fm-control relaunch asks before stopping the running agent. --local-model-override exists only for the captain's explicit instruction. Cloud candidates are never gated. Tests pin the verdicts against a real local listener, offline and silent servers with real curl timeouts, the spawn and relaunch refusals, the resolver and bootstrap integration, and a default-on live guard proving the real Pi requests the same server and model id the check resolved. tests/lib.sh points the check at a nonexistent Pi directory so fixtures never probe a developer's real local server. * no-mistakes(review): Match llama-swap default aliases and enforce local session limits * no-mistakes(review): Report malformed local session limits once in startup observation * no-mistakes(document): Document malformed localSessions startup observation line * no-mistakes(review): Fix local model relaunch switching, remote records, runner-up checks * no-mistakes(document): Document local model pre-check in relaunch contract
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
README accuracy pass: state tmux as a prerequisite and recommend running firstmate inside tmux so the captain can watch crewmate windows; fix stale claims - all four harness adapters are now verified, watcher env vars match fm-watch.sh (FM_CHECK_INTERVAL replaced FM_CHECK_EVERY, heartbeat backs off exponentially to FM_HEARTBEAT_MAX, new busy-regex default), restart state is local not committed markdown, toolbelt table lists all ten scripts; also includes the captain's own manifesto paragraph edit
What Changed
data/.bin/toolbelt table.Risk Assessment
✅ Low: The change is limited to README documentation updates and aligns with the referenced scripts and AGENTS.md guidance, with no material merge-blocking risks found.
Testing
Reviewed the README diff against the actual scripts, exercised the watcher heartbeat and
FM_CHECK_INTERVALslow-check paths end to end, asserted the README documents tmux guidance, verified harnesses, current watcher env vars, and all 10bin/scripts, captured rendered README evidence, removed transient watcher state, and confirmed the worktree was clean afterward./var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KTX01X6W39ETXRZJR6C61JB6/readme-render.png)Evidence: Rendered README evidence page
Evidence: Watcher heartbeat smoke output
Evidence: Watcher slow-check interval output
Evidence: README accuracy assertion output
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 1 issue found → auto-fixed ✅
README.md:137- The new statement that claude, codex, opencode, and pi are all verified conflicts with the Install prerequisite at line 66, which still says "claude code is the verified one today". Update the prerequisite wording so the README does not give users contradictory harness support guidance.🔧 Fix: Align README harness prerequisites
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
git diff --unified=80 f62d3e4776413043816990a6c11acfa46c8783dc..06dfd26dad0978361c46e44e15fab6603a93cb46 -- README.mdFM_HEARTBEAT=0 FM_HEARTBEAT_MAX=2 FM_POLL=1 FM_CHECK_INTERVAL=999 bin/fm-watch.shprintf ... > state/no-mistakes-readme-test.check.sh && chmod +x state/no-mistakes-readme-test.check.sh && FM_HEARTBEAT=999 FM_POLL=1 FM_CHECK_INTERVAL=0 bin/fm-watch.shnode <<'NODE' ... README accuracy assertions ... NODEchrome-devtools-axi open file:///var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KTX01X6W39ETXRZJR6C61JB6/readme-render.html && chrome-devtools-axi screenshot /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KTX01X6W39ETXRZJR6C61JB6/readme-render.pngrm -rf stategit status --short✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.