Skip to content

sync: integrate canonical upstream advances - #34

Merged
DereKk8 merged 215 commits into
mainfrom
fm/fm-upstream-sync-a1
Aug 2, 2026
Merged

DereKk8 merged 215 commits into
mainfrom
fm/fm-upstream-sync-a1

Conversation

@DereKk8

@DereKk8 DereKk8 commented Aug 1, 2026 •

Copy link
Copy Markdown
Owner

What Changed

  • Integrates 204 commits from canonical upstream/main into the fork's custom line: 191 files, +19345/-5201.
  • Reconciles AGENTS.md in both directions: the fork's slim tiered structure and fork-specific sections are retained, while upstream's contract changes are adopted, including the new ## Captain instruction precedence section and the hard rule 1 project-write exception.
  • Preserves fork-only operational skills and contracts: /cardio, /explain, /reset-window, /claude-remote, bin/fm-dispatch-select.sh, and bin/fm-backlog-handoff.sh.
  • Adopts upstream's newer semantic lifecycle, dispatch eligibility, validation precedence, and supervision implementations where they supersede duplicated or obsolete fork helpers.
  • Brings in upstream's additional verified harness adapters kimi and pi-signed, and the quota-array-dispatch selection contract.
  • Audits external tooling after the merge; all installed tools were already current, and grok remains intentionally uninstalled.

Validation

  • bin/fm-lint.sh and the tmux backend tests pass.
  • No data/ paths are tracked, matching both origin/main and upstream/main.
  • The no-mistakes pipeline was intentionally not run for this integration, by prior arrangement; a focused seam review covers it instead. The pipeline-required PR check is therefore red by design and accepted.

kunchenguid and others added 30 commits July 4, 2026 11:48
* 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
* 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
* 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 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): 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): 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(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 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 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
…henguid#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): 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(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(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

* no-mistakes(document): Document Orca checklist

---------

Co-authored-by: Stephen Brouhard <vesta@stephens-macbook-air.tail2122af.ts.net>
* 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 kunchenguid#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 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 failure still surfaces.

* no-mistakes(review): Harden teardown lock refusal paths

* no-mistakes(review): Harden stale-lock teardown safety rechecks

* no-mistakes(review): Harden stale teardown lock checks

* no-mistakes(document): Document teardown lock recovery
…-governance section (kunchenguid#307)

* Encode project AGENTS authoring bar

* no-mistakes(review): Captain, centralize CLAUDE promotion governance

* no-mistakes(review): make ensure_maintenance_section idempotent-success, drop || true guards

* no-mistakes(review): separate appended maintenance section on newline-less CLAUDE.md promotion

* no-mistakes(review): assert maintenance heading present before separator check in test

* no-mistakes(document): sync docs with AGENTS.md authoring bar and self-governance

---------

Co-authored-by: fmtest <fmtest@example.invalid>
…chenguid#300)

* Add captain-invocable bearings skill

Generates a pick-up-where-I-left-off status report from live fleet
state to data/status-report-<YYYY-MM-DD>.md plus a concise chat
summary. Read-mostly procedure: reads backlog, per-task crew state
via bin/fm-crew-state.sh, open PRs via gh-axi, scout reports,
pending decisions, and date-gated queued work; composes the
exemplar's sections (TL;DR, Check first, Landed, In flight, Plans,
Decisions pending, Date-gated/queued); never tears down, merges, or
mutates task state as a side effect.

* no-mistakes(document): docs: list new /bearings skill in README built-in skills table
* fix(watcher): pin LC_ALL=C in fm_pid_identity for locale-invariant identity

ps's lstart date format follows the caller's LC_TIME/LC_ALL. The watcher records
its process identity under one locale, but arm/guard/turn-end re-read it under the
machine's ambient locale. On a non-C locale (e.g. ko_KR) the two strings differ
only in the date portion, so fm_watcher_lock_matches_pid / fm_watcher_healthy
reject a genuinely live watcher - breaking fm-watch-arm.sh, fm-guard.sh, and
fm-turnend-guard.sh on every non-C-locale machine.

Pin LC_ALL=C on that one ps call so the write and read sides agree regardless of
machine locale, matching the LC_ALL=C determinism the file already uses elsewhere.
Add a colocated regression test asserting fm_pid_identity is locale-invariant
across exported LC_ALL/LC_TIME.

* no-mistakes(document): Document watcher PID identity coverage
* docs: reconcile Codex App backend contract

* no-mistakes(document): Sync backend docs

* docs: clarify Codex Desktop bridge blocker

* no-mistakes(document): Align Codex App backend docs

* no-mistakes(test): Captain, stabilize watcher self-eviction test cadence

* no-mistakes(document): Document Codex App backend contract

* no-mistakes(document): Captain, document blocked codex-app coverage

* docs: make Codex App contract doc authoritative

* no-mistakes(document): Align Codex App backend docs

* docs: redact local Codex App smoke paths

---------

Co-authored-by: Stephen Brouhard <vesta@stephens-macbook-air.tail2122af.ts.net>
* docs: add Codex App coordination skill

* no-mistakes(review): Captain, mark Codex App skill agent-only

* no-mistakes(document): Document Codex App backend boundary

* no-mistakes(document): Captain, document Codex Desktop backend boundary

* no-mistakes(lint): Captain, lint clean

* no-mistakes(document): Document Codex Desktop boundaries

* docs: narrow Codex App skill playbook
* fix(afk): recognize unbordered herdr composer rows to stop escalation redelivery loop

fm_backend_herdr_composer_state only recognized bordered composer rows
(the grok shape). Real claude and codex render their live input row
with no border at all, so once a harness's own startup banner scrolled
out of the capture window the classifier read the composer as unknown
forever. fm_backend_herdr_send_text_submit never confirmed "empty", so
escalate_flush never cleared state/.subsuper-escalations, and the
away-mode daemon retyped and resubmitted the same buffered digest every
housekeeping cycle - reproduced live against a real herdr+claude pane
(5+ identical deliveries in 40s).

The classifier now recognizes an unbordered (bare) composer row led by
a known prompt glyph alongside the existing bordered shape, keeping
whichever match is bottom-most so a stale decorative box never
outranks the live composer.

* no-mistakes(review): Narrow herdr bare prompt matcher

* no-mistakes(document): Sync herdr composer docs
…guid#323)

* fix(herdr): confirm message submit via native agent-state, not composer text

fm_backend_herdr_send_text_submit now confirms a landed submit by polling
herdr's own agent-state (agent get) for the idle->working transition instead
of reading composer content. Composer scraping remains, unchanged, for the
away-mode daemon's pre-injection empty-box guard only.

This fixes the practical effect of the codex idle-tip gap from the
2026-07-07 incident: codex's dynamic idle-composer hint text can no longer
misread as pending and block/mis-confirm a send, since confirmation no
longer looks at composer text at all. Verified empirically against real
claude and codex agents (timing, swallowed-Enter, unreadable-target, and
already-busy-target scenarios), and against the real away-mode daemon
end-to-end after updating its synthetic supervisor-pane test fixture to
register itself as a real herdr agent (herdr's own report-agent primitive)
so it can still exercise the new confirmation path.

* no-mistakes(review): Captain, harden herdr submit confirmation

* no-mistakes(review): Captain, harden herdr submit confirmation

* no-mistakes(document): Sync Herdr submit docs

* no-mistakes: apply CI fixes
* Add quota-balanced dispatch selection

* no-mistakes(document): Document dispatch selector guidance
* fix(session-start): deterministically respawn dead-shell secondmates

A secondmate agent that exits leaves its backend pane alive as a bare
shell. The session-start endpoint check only verified pane presence, so
recovery and the watcher (which exempts secondmates from stale-pane
detection) never noticed - evidence 2026-07-07: every secondmate in one
fleet was found sitting at a dead zsh shell.

Add fm_backend_agent_alive (bin/fm-backend.sh), a deeper per-backend
liveness probe distinct from pane presence: fm_backend_tmux_agent_alive
classifies the pane's live foreground process via tmux's own
pane_current_command, and fm_backend_herdr_agent_alive reuses the
already-verified pane_agent_state husk classifier. Both are conservative:
anything ambiguous reports unknown, never a false dead.

Wire this into a new session-start-only, locked-and-primary-only sweep in
bin/fm-bootstrap.sh that kills and respawns only a confidently dead
secondmate endpoint, leaving alive/unknown readings untouched - idempotent
by construction, so repeated runs converge without duplicating agents.

* no-mistakes(review): Guard raw secondmate liveness respawns

* no-mistakes(review): Fix detect-only bootstrap test

* no-mistakes(test): Pin liveness fixture harness

* no-mistakes(document): Sync secondmate liveness docs

* no-mistakes: apply CI fixes
* Fix NUDGE_SECONDMATES to print stable fm-<id> selectors.

Session-start secondmate sync used to accumulate raw backend window targets
into NUDGE_SECONDMATES, but the liveness sweep in the same bootstrap run can
respawn secondmates onto new endpoints. fm-send with those stale explicit
targets bypasses meta resolution and fails, while fm-<id> resolves correctly.

Accumulate fm-<id> in process_secondmate, update the bootstrap/update contracts
and /updatefirstmate skill, and add a herdr respawn regression test.

* no-mistakes(review): Captain, guard herdr regression jq dependency

* no-mistakes(document): Document stable secondmate nudge selectors

* no-mistakes(lint): Fix shell lint hints
* Make tasks-axi and quota-axi required bootstrap tools

Add both to the normal toolchain checks alongside lavish-axi, keep the
tasks-axi 0.1.1+ compatibility gate, and report quota-axi through the
standard MISSING install-consent flow. TASKS_AXI: available remains a
backlog-backend capability signal only; manual opt-out no longer suppresses
the missing-tool report.

Update bootstrap tests and point docs/configuration.md at the canonical
toolchain contract.

* no-mistakes(review): Clarify manual backlog bootstrap reporting

* no-mistakes(document): Document bootstrap AXI tools
Replace overwrite-in-place wording with explicit delete-then-create
instructions so agents do not modify an existing daily report file.
* Add primary turn-end guards for all harnesses

* no-mistakes(review): Normalize Codex hook cwd resolution

* no-mistakes(review): Fix OpenCode guard worktree anchoring

* no-mistakes(review): Anchor Codex guard outside nested roots

* no-mistakes(review): Anchor Codex guard to hook root

* no-mistakes(review): Avoid Grok permission escalation

* no-mistakes(document): Sync turn-end guard docs
* fix backend selector task id resolution

* no-mistakes(document): Document selector resolution behavior
karotkriss and others added 20 commits July 29, 2026 22:23
…unchenguid#1261)

A name-by-name list of config/ entries silently stops ignoring any new or
home-local file placed there, which makes the working tree read as dirty and
blocks guarded sync paths that refuse to touch a dirty home. AGENTS.md
already documents config/ as captain-private and gitignored as a category;
this makes .gitignore match that contract.
…al coverage (kunchenguid#1304)

The second assertion in fm-gitignore-config.test.sh (added by kunchenguid#1261) greps
.gitignore for a specific spelling of the config/ ignore pattern. It fails
on a semantically equivalent pattern like config/** and does not prove Git
actually ignores anything, per the completed source-content-test audit.

Replace it with a real git check-ignore control test on a generated
unrelated path, and strengthen the existing directory-coverage test with
generated unpredictable direct and nested config/ paths.
)

* Add bounded startup memory curation

* no-mistakes(review): Record reproducible stow verification evidence

* no-mistakes(review): Validate inherited secondmate stow evidence

* no-mistakes(document): Document editable startup-memory budget propagation
* fix(herdr): place workers in the launching agent's exact workspace

Herdr enforces no workspace-label uniqueness, and spawn resolved its
container by taking the FIRST workspace whose label matched the home
label. With two workspaces both labeled "firstmate", a worker launched
from the second one was created in the first, so it appeared in a
different space than the Firstmate the captain was watching.

Reproduced end to end on Herdr 0.7.5 protocol 17 by running the real
bin/fm-spawn.sh inside a launcher pane in the second "firstmate"
workspace: the worker landed in w1 while its launcher was in w2, with an
unrelated third workspace focused throughout, which also rules out any
dependence on the focused workspace.

Placement now binds to the launching process's own Herdr identity. Herdr
injects HERDR_PANE_ID, HERDR_SESSION, and HERDR_SOCKET_PATH into every
process it manages a pane for, and fm_backend_herdr_launcher_identity
resolves that pane's current owning tab and workspace live from Herdr,
cross-checking the pane against its tab and confirming the workspace
exists exactly once in the session. The injected HERDR_TAB_ID and
HERDR_WORKSPACE_ID are creation-time snapshots and are deliberately not
read as current identity. Labels are no longer placement authority.

A claimed parent identity that is unreadable, contradictory, stale, or
from another named session or Herdr server stops the spawn before any
worker endpoint exists, rather than degrading to a label search. A
launcher with no Herdr ancestry has no workspace to inherit and keeps
the per-home labeled container, which must now resolve to exactly one
workspace; two same-labeled candidates refuse instead of adopting
either. A --secondmate launch keeps standing up that home's own
workspace by design.

With presentation spaces enabled, the projected child is created and
bound under that same exact parent and anchors its ordering on it, so a
duplicated home label no longer makes the layout ambiguous. Projection,
focus restoration, restart binding, and quarantine rules are unchanged,
and children are never collapsed into the parent. tmux, Zellij, cmux,
Orca, and the away-mode daemon terminal were each inspected and are not
affected: none resolves a container by searching mutable labels.

tests/fm-backend-herdr-launcher-workspace-e2e.test.sh drives the real
spawn and teardown against an isolated Herdr lab, with its headline case
running fm-spawn.sh inside a real Herdr pane so the identity comes from
Herdr's own injection. The refusal matrix and the ordering anchor are
covered deterministically in tests/fm-backend-herdr.test.sh.

Eight existing real-Herdr suites inherited the developer terminal's own
Herdr pane into their isolated lab sessions, which the new cross-session
check correctly refuses. tests/herdr-test-safety.sh now owns
herdr_forget_inherited_pane and those suites call it, so what they assert
no longer depends on where they were launched from.

Two unrelated fixes found along the way. tests/fm-secondmate-harness.test.sh
had the same class of environment leak through CLAUDECODE, which outranks
PI_CODING_AGENT in bin/fm-harness.sh and made its pi-signed ancestry case
resolve "claude" whenever the suite ran inside Claude Code. And
fm-spawn.sh's usage() printed a fixed line range that had already been
truncating its own help mid-sentence.

* no-mistakes(review): Enforce exact Herdr launcher and projection identity

* no-mistakes(document): Document exact Herdr launcher workspace placement
* feat(calm): replace Pi's working row with an animated ship while Calm is on

While Calm is active and one logical agent run is under way, Calm now hides
Pi's built-in working row and renders a small two-row SSHHIP-derived boat in
its place. When Calm is off, Pi's stock working row is left untouched.

The presentation uses only public Pi extension API: setWorkingVisible(false)
plus a temporary setWidget() component whose render(width) owns the responsive
geometry and whose timer requests a TUI render. Visibility follows agent_start
through agent_settled, so the boat does not flicker between tool calls,
automatic continuations, retries, or compaction inside the same run, and
settle, abort, and failure all reach the same cleanup.

fm-calm.ts stays the sole owner of the presentation choice and the only caller
of setWorkingVisible(); the new lib owns the sprite geometry and widget.

* no-mistakes(review): Guarded Calm-off lifecycle visibility writes; focused tests pass

* no-mistakes(test): Fixed Calm E2E wait to include tmux scrollback

* no-mistakes(document): Document Calm working boat behavior

* no-mistakes: apply CI fixes

* feat(calm): slow the Calm boat, animate blue water, and make the sail directional

The boat now moves one column every 880ms while a bounded fixed-cell water phase
advances every 220ms, so the water ripples several times between boat steps and
the presentation reads as calm. One scheduler drives both clocks and disposing
the widget stops them together; ticks rather than wall-clock timestamps drive
every state change, so tests seek animation time exactly.

Colors are standard ANSI foreground codes instead of theme lookups: blue for
every water cell and yellow for the complete boat, each run closed with a
default-foreground reset so nothing bleeds into padding or later frames. ANSI
bytes never enter geometry, so visible width stays exact.

The mainsail is directional and trails aft of the mast: <| travelling right and
|> travelling left. Direction reverses the moment the boat lands on an endpoint,
so the endpoint frame already shows the new heading and no frame at or after a
bounce shows the previous sail.

* test(calm): wait for the Ctrl+O expansion redraw this block asserts

* docs(calm): record the revised working-presentation verification evidence

* no-mistakes(document): Fix Calm feasibility document EOF whitespace
…henguid#1349)

* fix(dispatch): scope candidate authentication to its own surface

A locally expired timestamp in one credential store was reported to the
captain as a sign-out, including for dispatch candidates that never read
that store. A `harness=pi, model=xai/grok-*` candidate authenticates
through Pi's own xAI credential, but the only Grok quota reading
available was gated on the standalone Grok CLI's separate token, whose
expiry clock drifts independently. The always-loaded intake rule then
turned that unreadable quota into a mandatory captain escalation.

Add `bin/fm-auth-preflight.sh` as the deterministic owner of the parts
that must not depend on agent memory: it resolves a tuple's
authentication surface from quota-axi's own emitted auth sources rather
than from a harness or model name, so another harness's CLI can never
gate a candidate that does not use it. A vendor CLI is launched only
when the tuple's own harness owns the credential store under test and a
non-destructive discovery command is registered for it, which today is
`grok models` alone. That probe runs at most once with stdin closed and
a hard timeout, reads its verdict from the first stdout line because the
command exits 0 either way, treats unrecognized output as indeterminate,
and never invokes login, logout, or the interactive TUI. Quota is read
at most twice, and unknown headroom never makes a candidate ineligible
on its own.

Update the dispatch procedure to match: usable authentication with
unmeasurable headroom stays eligible at lower preference with the
unknown disclosed, and stop-and-report is reserved for unresolved
authentication, an unresolved relationship, or malformed configuration.
Record that Grok's `credits.remaining` is a prepaid balance rather than
window headroom.

Gate quota-axi at 0.1.16 in bootstrap, the first build reporting
per-credential auth sources. A stale install previously passed the
presence check silently, which is why a fix published two days earlier
was still not in effect.

Replace the orphaned quota-array-dispatch fixtures, which encoded a
`provider: "xai"` shape the tool never emits and had no consumer, with
fixtures shaped like real 0.1.16 output that the new suite drives the
script against. The suite asserts the verdict and, separately, which
vendor CLIs were launched, so a Pi/xAI candidate reaching the Grok CLI
fails. Map `tests/fixtures/<dir>` to its consuming suite so a fixture
change selects the right tests instead of refusing.

* refactor(bootstrap): give the quota-axi floor one owner

The floor was stated twice - once in bootstrap's gate and once inline in
the auth preflight - so bumping it needed two edits that could drift.
Move it to bin/fm-quota-axi-lib.sh alongside its rationale, matching the
existing tasks-axi library, and derive the comparison from the constant
so the number appears exactly once. Bootstrap turns a failing check into
the operator diagnostic; the preflight refuses to emit an unscoped
verdict. Map the new library to both consuming suites so a bump re-runs
them, and record that any usable source means the surface authenticates.

* no-mistakes(review): Captain: bound quota checks and removed Python dependency

* no-mistakes(review): Captain: enforce conservative headroom and exact preflight retry

* no-mistakes(review): Captain: preserve OpenCode eligibility without auth-surface guessing

* no-mistakes(review): Captain: reject malformed OpenCode model relationships

* no-mistakes(review): Captain: exempt verified unmodeled tuples from intake escalation

* no-mistakes(document): Updated dispatch authentication documentation

* no-mistakes: apply CI fixes
…nchenguid#1350)

* feat(x-mode): reconcile promised public replies deterministically

A promised final reply in an X or Discord thread was only kept while the
primary remembered it. Compaction or restart erased that memory, so a typed
public-followup obligation could sit at pending-work after its PR merged and
the original thread never got its reply.

Make the promise durable state instead:

- bin/fm-public-followup-emit.sh reports a typed terminal work result (source
  home, work id, generation, outcome, safe deliverables, bounded public-safe
  text) into the owning home's private inbox. The event id is derived from
  that identity tuple, so duplicate reports and restart replay converge with
  no coordination, and nothing ever parses a free-form done: sentence.
- bin/fm-public-followup.sh registers a commitment, reconciles events through
  tasks-axi public-followup, and runs the idempotent delivery sequence
  (begin-delivery with the payload hash, post, record the posted receipt or a
  typed error) against the stored platform and opaque thread binding. A
  delivery interrupted between post and receipt refuses rather than risk a
  second public reply.
- Session start surfaces unresolved commitments from disk, the existing relay
  poll surfaces a new terminal-result set once, and teardown refuses while
  this home still owes a public reply for that exact work.

tasks-axi public-followup remains the only owner of the obligation state
machine, state/x-context/ the only owner of the private request context, and
fm-x-reply.sh the only thing that posts. Its new optional --receipt-file is
the one addition there, so a caller can record how many messages were sent.

A home that never opted into the myfirstmate relay gates out on a single
[ -f "$FM_HOME/.env" ] test: no tasks-axi call, no backlog or context scan,
no output, and no artifact. Evidence in docs/verification/public-followup.md.

* no-mistakes(review): Hardened public-followup reconciliation and ownership guards

* no-mistakes(review): Hardened typed terminal cleanup and receipt reconciliation

* no-mistakes(review): Automated typed-delivery cleanup and strict backlog validation

* no-mistakes(review): Fail-closed parent resolution and registration-safe delivery

* no-mistakes(review): Harden relay gating and validate secondmate bindings

* no-mistakes(review): Use owner-aware single-gate teardown protection

* no-mistakes(document): Correct public-followup documentation drift

* no-mistakes(lint): Quote done literals to fix ShellCheck warnings

* no-mistakes: apply CI fixes
…chenguid#1327)

* feat: add semantic busy-state contract owner and event writer

One owner (bin/fm-busy-lib.sh) for the captain-approved semantic
busy-state redesign: a per-task gen-bound record written only by
bin/fm-busy-event.sh, per-harness trusted-source classification with
explicit source attribution, busy/idle/unknown/dead semantics where
missing, malformed, stale, or untrusted semantic data is unknown -
never idle - and endpoint death is the only process-level override.
The Grok-only rendered-tail fallback and the standalone-Kimi
verification gate live behind the same classifier.

* feat: arm busy-state at spawn and convert Pi to the semantic extension path

fm-spawn arms the busy-state contract for converted adapters and seeds
busy/fm-spawn (the launch brief is a submitted turn). The Pi/pi-signed
per-task extension now reports agent_start -> busy and agent_settled ->
idle confirmed by ctx.isIdle(), covering auto-retries, compaction
retries, tool loops, and queued continuations, while turn_end stays a
wake notification touch. Teardown removes the new record, gen sidecar,
and lock. Live-verified on Pi 0.82.0: seed -> agent-start busy ->
agent-settled idle with the marker still touched.

* feat: convert OpenCode to the semantic session.status plugin path

The per-task plugin (renamed .opencode/plugins/fm-busy-state.js) now
classifies from OpenCode's semantic session.status events - busy and
retry are active, idle is inactive - latched to the worker's own
session so a subagent child session can never clear the worker's busy
state. The session.idle marker touch stays a wake notification.
Teardown removes both the new and the legacy plugin filenames.
Live-verified on OpenCode 1.17.18 in a real TUI pane: seed ->
session-busy -> session-status-idle.

* feat: convert Claude to the full lifecycle hooks path

The per-task settings.local.json now wires UserPromptSubmit -> busy
and Stop, StopFailure, and SessionEnd -> idle, so API-error and
shutdown turn ends can never strand a busy record; Stop keeps the
turn-ended notification touch. A refused (stale-gen) event exits 0 and
stays silent so Claude's own lifecycle is never broken. Live-verified
on Claude Code 2.1.220: UserPromptSubmit fires for the argv launch
prompt, Stop closes each turn, a mid-stream Escape interrupt fires no
closing hook, and the firstmate-controlled idle/fm-interrupt clear
resolves it.

* feat: gate Codex busy state behind verified semantic sources

The approved contract prefers Codex's app-server turn lifecycle with
capability negotiation and sanctions its lifecycle hooks as the
intermediate. Live probes on codex-cli 0.145.0 show neither is usable
for a pane worker: the app-server daemon is unreachable for a TUI
thread and refuses to start outside the managed standalone install,
and firstmate-written project hooks never fired (interactive with
directory trust granted, and exec, both with
--dangerously-bypass-hook-trust) while global hooks fired in the same
runs. Codex therefore classifies unknown codex-unverified behind an
explicit probe rather than falling back to idle or footer text, and
fm-spawn installs no unverified Codex wiring.

* feat: gate standalone Kimi busy state on live verification

Standalone Kimi has no installed binary here, so per the approved
contract its semantic path stays guarded and it classifies unknown
kimi-unverified rather than idle - and never from its locale-sensitive
moon-phase spinner, which the redesign forbids inventing as a state
source. The gate records the preferred source order (Wire prompt
request lifetime, which brackets a turn and reports cancellation, then
the documented hooks including Interrupt because Stop does not fire on
interrupts) and the exact evidence required to open it. Arming without
wiring would seed a busy record nothing could clear, so both land
together behind the same gate.

* feat: route busy consumers through the contract and drop the global OR

The watcher, crew-state reader, and away-mode daemon now decide busy
state through bin/fm-busy-lib.sh: only an exact busy verdict counts as
working, and unknown never becomes working or a silent idle, so a crew
whose semantic state is missing, malformed, stale, or unverified
surfaces instead of being absorbed. Crew-state reports the producing
source in its detail. The watcher's global OR regex default is gone;
Grok keeps its isolated fallback inside the contract. The daemon's
supervisor-pane reader stays rendered-text - that pane is not a
recorded task - but is now scoped to firstmate's own detected harness
instead of every vendor signature. Secondmate pending-reply
observation is deliberately unchanged and documented as a
delivery-confirmation signal, not task state.

* docs: point busy-state documentation at the single contract owner

Adds a maintainer-architecture section naming bin/fm-busy-lib.sh as
the owner of what busy means, with per-adapter sources, the
unknown-never-idle rule, the endpoint-death override, and the two
rendered-text readers that deliberately stay outside the contract.
Replaces the stale regex-first prose in architecture, tmux-backend,
herdr-backend, and configuration; converts the harness-adapters
per-harness rows from UI signatures to the semantic source each
harness uses; and records the live verification evidence, including
why Codex and standalone Kimi stay unknown.

* fix: arm away-launch signal handlers before acquiring the lifecycle lock

fm_afk_launch_main acquired its lock and only then installed the EXIT,
INT, and TERM traps. A signal arriving in that window terminated the
process by default action and left the lock directory behind, which
blocks the next away-mode launch until the stale-owner reclaim path
clears it. The release helper only removes a lock this process owns,
so the handlers are now armed first. The accompanying test also killed
the child whether or not the lock had appeared and sampled cleanup the
instant wait returned; it now requires the lock, then allows a bounded
settle, so it proves the guarantee instead of racing it.

* test: align fleet, Kimi, lifecycle, and detection suites with the contract

The fleet snapshot and wake-daemon lifecycle fixtures now prove a
working crew through its own semantic busy-state record instead of
rendered pane text, which is what those consumers read. The Kimi
watcher test asserts the approved contract directly: a standalone Kimi
task classifies unknown rather than matching its moon-phase spinner,
while Grok's isolated fallback still classifies only Grok. The
pi-signed detection cases clear ambient harness markers, fixing a
pre-existing failure where the running session's own CLAUDECODE
outranked the fixture's marker.

* fix: stop teardown from deleting a project's own Codex hooks file

An intermediate revision wired Codex through a firstmate-written
<worktree>/.codex/hooks.json, and teardown removed it alongside the
other generated wiring. The Codex wiring was dropped when its probes
came back unverified, so that removal now targets a file firstmate
never creates - and a project may legitimately track its own
.codex/hooks.json, which teardown would then delete from a pooled
worktree.

* fix: keep busy-record parsing from disturbing its sourcing caller

The record parser split fields with set -- under a temporary noglob,
which clobbers a sourcing caller's positional parameters and restores
glob expansion even when the caller had disabled it. The watcher, the
daemon, and the crew-state reader all source this library, so it now
reads fields with read -a, which never globs and never touches caller
state.

* docs: state exactly which Claude hook paths were reproduced live

The busy-state record listed all four wired Claude hooks in the source
column, which could read as a claim that every one fired during the
pass. UserPromptSubmit and Stop did; StopFailure and SessionEnd are
wired from hook names confirmed present in the installed binary, but
the abnormal turn ends they cover were not reproduced.

* test: let reset_fakes own the crew-state busy-text fixture lifecycle

The Grok fallback case set FM_FAKE_BUSY_TEXT and cleared it inline, so
the variable's lifetime was owned by one test rather than by the
shared reset that every other fake already uses.

* no-mistakes(review): Fix semantic busy-state lifecycle races

* no-mistakes(review): Make busy-state retirement idempotent

* no-mistakes(review): Enforce semantic state boundaries for status and injection

* no-mistakes(review): Restore harness-scoped away-mode busy guard

* no-mistakes(document): Refresh semantic busy-state documentation

* no-mistakes: apply CI fixes
…d#1356)

* fix(calm): resume working boat from frozen column across runs

Keep one extension-owned boat animation for the Pi session so settling
freezes column and direction, the next working period resumes there
without hidden-time jumps, and only a fresh session resets to the left edge.

* no-mistakes(review): Freeze Calm boat from last rendered state

* no-mistakes(document): Document Calm boat continuity contract
* fix(dispatch): judge candidate provider relations instead of rejecting them

Firstmate deterministically dropped supported Pi candidates in the
openai-codex family. bin/fm-auth-preflight.sh resolved a harness=pi tuple's
credential surface by constructing the source id `pi:<model-prefix>`, so
`pi + openai-codex/gpt-5.6-terra` looked for a `pi:openai-codex` source. That
source does not exist, because Pi's Codex family authenticates through the
Codex store quota-axi already lists as `auth-json`/`cli-rpc`. The tuple
returned `eligible=no reason=surface-unresolved` while the Pi catalog listed
the model and the Codex provider reported fresh, usable credentials with 64
effective percent remaining on its all-model scope.

The prefix construction was only ever valid where Pi holds its own credential
(`pi:xai`, `pi:kimi-coding`), which is why every previously configured Pi tuple
resolved and the defect stayed hidden until a Codex-family Pi model was
configured.

Retire dispatch eligibility from deterministic shell. The dispatching first
mate now establishes model support and provider family from each harness's
authoritative catalog, applies quota at the granularity the vendor supplies,
and shows that reasoning. Provider-level and all-model evidence bounds every
model established in that family; a named-model window bounds only its own
model. Missing model-level quota, a missing auth source, unmeasurable headroom,
and unmodeled authentication are disclosed uncertainty. Only concrete
contradictory evidence blocks a candidate.

Replace the preflight with bin/fm-vendor-auth-probe.sh, which keeps the
captain's approved bounded probe envelope without any routing knowledge: it
takes no harness, model, or provider, reads no quota, renders no verdict, and
holds only a fixed-argv safety allowlist. Its behavior suite proves the absent
identity surface, the untouched quota, the uniform exit status, the fixed argv
with stdin closed, and a real bound even when the configured bound is zero.

Also fixed along the way: a zero FM_*_TIMEOUT silently removed the hard bound,
the pinned Grok version had drifted to 0.2.117, and --changed selection refused
outright on any deleted bin/ script.

AGENTS.md section 4 and quota-array-dispatch own the corrected policy,
harness-adapters gets the catalog-responsibility correction, and
docs/verification/dispatch-auth.md records the 2026-07-30 evidence on
Pi 0.82.0, quota-axi 0.1.16, and grok 0.2.117.

* no-mistakes(review): Reject all-zero vendor probe timeouts
* docs: add captain-authorized inherent red-check merge exception

Keep the default red-PR ban and own one always-loaded exception in the
merge-authority section: captain-explicit PR or bounded batch plus exact
check, only when the failure is inherent to the selected delivery path.
Yolo cannot activate it; final head and the full current check suite must
be verified; other substantive failures remain non-waivable.

* docs: replace narrow red-check exception with captain precedence

Supersede the inherent failing-check merge exception with one always-loaded
Firstmate-local rule: a current explicit concrete captain instruction
overrides a conflicting Firstmate-written standing rule only within exact
scope, never above platform/system/developer instructions. Keep the ordinary
red-PR default and yolo boundary; point section 7 at the section 1 owner.
* fix: give validation-time captain overrides a supersession sequence

The Validate section let a captain instruction that completely
invalidates the work being validated keep the same task and worker, but
never said how: the adjacent rule flatly bans hand-editing, committing,
aborting, or restarting during an active run with no carve-out, so a
worker facing full invalidation had no sanctioned path forward.

Add the missing sequence: cancel through no-mistakes axi's abort
command, confirm the run has stopped through axi status, recover branch
ownership through axi sync's guarded recovery, only then replace the
obsolete work, and validate once against the final head. The existing
ban on hand-editing an active run now cross-references this sequence
instead of contradicting it.

* no-mistakes(review): Make validation custody recovery conditional

* no-mistakes(document): Clarify validation supersession abort exception

* fix: keep obsolete pipeline commits out of the superseded deliverable

The review-applied fix made custody recovery conditional on
branch_sync.next_action.code, but left an open gap: recovering custody
settles who owns the branch, not what content ships. As written, a
worker could recover an obsolete run's branch and build the
replacement on top of its now-irrelevant commits instead of from the
correct pre-invalidation base, carrying obsolete content into the
final deliverable.

Make that explicit: custody recovery settles ownership, not content,
so the worker replaces obsolete work from the correct base and keeps
the obsolete run's commits out of what gets validated and shipped.

* no-mistakes(test): Restore minimal pre-invalidation replacement instruction

* fix: dedupe redundant "replace the obsolete work" restatement

Line 309 already says the worker replaces the obsolete work from the
correct pre-invalidation base, excluding the obsolete commits. The
closing sentence restated "replace the obsolete work" again before
gating the final validation run, layering the same fact twice instead
of stating it once.

Trim the closing sentence to just the ownership gate and the
single-run-against-final-head requirement it uniquely adds.
* fix: bind explicit --backend to exact-task authority

A Herdr-backed second mate carried a prior one-task --backend tmux
exception forward by analogy, so its child landed in tmux and never
appeared under the second mate in Herdr. Runtime detection was correct;
the authority surface was not.

docs/configuration.md now owns that an explicit --backend is authorized
only for that exact task. AGENTS.md and fm-spawn help point there.

* no-mistakes(document): Consolidate backend selection authorization documentation
…unchenguid#1229)

* fix: remove projected workspaces through Herdr's focus-preserving pane-death path

Herdr 0.7.5's explicit close of a workspace-emptying last pane moves the
attached client's focus to a neighbor workspace, flashing the captain's
whole window and routing in-flight keystrokes to the wrong pane until
Firstmate's exact-tab restore masks it 56-197 ms later.

Teardown and cleanup now plan a workspace-emptying close as a focus-safe
removal: verify the close empties the workspace, reposition the doomed
workspace behind the focused one through the verified workspace.move
transport when it sits before a non-last focused workspace, prove the pane
holds one lone idle shell, and end that shell so Herdr removes the emptied
workspace through its focus-preserving pane-death path. Any ambiguity or
failure falls back to the plain close behind the existing restore backstop,
and fm_backend_herdr_kill applies the same plan for non-projected removals.

Two conditions proven on real hardware are encoded in the adapter: BSD ps
reports a login shell's comm as "-zsh", and an idle shell transiently
hosts a prompt helper right after a workspace.move relayout, absorbed by a
bounded strict-sample settle window in the idle-shell proof, now the single
owner shared with session-start cleanup.

An isolated-lab regression reproduces the raw steal on 0.7.5 and proves the
plan removes a doomed workspace with zero wrong-focus samples and no
corrective focus; unit fixtures cover the position, edge, ambiguity, move
and kill failure, escalation, and transient-helper cases. Upstream fixes
(kunchenguid#1877 explicit close, kunchenguid#1912 pane death) are merged but unreleased; once
released the plan degrades to a harmless reorder-then-remove.

* no-mistakes(review): Confirm pane death from structured not-found responses

* no-mistakes(review): Serialize Herdr kills and sample focus continuously

* no-mistakes(review): Synchronize Herdr focus evidence output

* no-mistakes(review): Refuse unlocked Herdr pane closes

* no-mistakes(document): Correct Herdr focus-safety documentation

* no-mistakes: apply CI fixes

* fix: never erase a Herdr task's records while its pane survives a refused close

A transient presentation-lock contention could produce a completed teardown
while the exact Herdr pane stayed alive as an unowned restored shell: the
kill refused the unlocked close (correctly), returned success, the warning
was suppressed, and cleanup erased the task's status, turn-end, and
metadata records after the isolated copy had already been returned.

Teardown now acquires the named-session presentation lock before anything
destructive: a contended lock refuses up front while the isolated copy, the
task branch, every durable record, and the endpoint are all intact for a
plain rerun, and the projected and flat close paths both run under that one
held lock instead of acquiring their own. Durable records are erased only
once the exact pane is confirmed gone through its structured presence; a
refused, skipped, or failed close retains every record with a visible,
retryable error, and after a skipped close (unresolvable lock path) only a
structured pane_not_found counts as gone - unknown never does.

The teardown regression drives a live contending lock holder end to end:
the refusal touches nothing (no worktree return, no branch drop, no close
attempt), and the retry after release returns the copy, closes the pane
under the lock, and removes the records. The unconfirmed projected close
now refuses with records retained, and the structured-presence gate has a
strict/default unit matrix.

* no-mistakes(review): Require structured pane-not-found before Herdr record removal

* no-mistakes(document): Correct Herdr record-retention verification date

* fix: refuse ambiguity, revalidate SIGKILL ownership, and roll back failed removals

Three accepted-contract corrections from the post-CI personal review of the
Herdr keep-spaces focus-flash mitigation.

Ambiguous endpoint identity no longer counts as a confirmed-gone pane: a
missing or malformed target refuses record removal in the structured
presence gate, and teardown treats missing confirmation machinery as a
refusal instead of skipping the gate, so only an exact structured
pane_not_found ever erases durable task records.

The pane-death SIGKILL escalation re-reads the exact pane's process
information and refuses to signal unless the same shell pid still passes
the strict bare-idle ownership proof, so a pid that exited and was reused
by an unrelated process is never signaled; the refused escalation falls
back to the plain close with the unrelated process untouched.

A reposition whose removal is not confirmed no longer outlives the attempt:
the emptying-close plan records the verified pre-move order and original
index whenever it invokes the mover, and both close owners restore the
exact original workspace order through a second verified move, under the
same held session lock, before reporting the close as failed.

Each defect was reproduced first: the unit matrix documented malformed
identity as gone, the PID-reuse regression showed SIGKILL reaching a
disowned pid, and the rollback regression showed a single unrestored move.
Teardown-level regressions cover unparseable presence retention alongside
the strict identity matrix.

* no-mistakes(review): Require confirmed Herdr removal and resolvable teardown locks

* no-mistakes(review): Enforce structured Herdr closes and teardown preflight

* no-mistakes(review): Preflight explicit Herdr close confirmation helper

* no-mistakes(document): Document Herdr rollback failure semantics

* no-mistakes(review): Captain, harden recursive Herdr teardown safety

* no-mistakes(document): Document recursive Herdr teardown evidence

* fix: retain nested secondmate home when a recursive child cleanup fails

Captain-decided Option A correction for nm-askuser-flash-r6, found during
complete-diff rereview of the merged head.

cleanup_firstmate_home_children's recursive secondmate branch called
itself for a nested child's home without checking the result, then
unconditionally removed that home right after. remove_firstmate_home
ends in an unconditional recursive delete with no check for leftover
records, so a nested secondmate whose own Herdr grandchild failed its
confirmed-gone check would have its entire home - retained grandchild
records included - erased by the very next line.

Guard the recursive call the same way every other fallible call in this
function already is: || return 1, skipping remove_firstmate_home and
leaving the nested home and its records for a safe rerun.

Empirically, fm-teardown.sh's set -eu already halted the script on the
prior unguarded call before reaching removal (verified by hand with the
guard reverted, under both this session's bash and stock macOS bash
3.2) - the reachable behavior was already correct. The explicit guard
is still applied exactly as decided: it matches every sibling call site
in the function, and it stops the correctness of this path depending on
errexit's well-known fragility under refactors (a wrapping if/&&, or a
future subshell) rather than on an explicit check.

Adds a teardown-level regression building on the existing direct-child
Herdr fixtures: a top-level secondmate contains a nested secondmate,
whose own Herdr child's close goes unconfirmed. Proves through the
public fm-teardown.sh interface that the nested home, the nested
secondmate's own record, and the grandchild's metadata and status all
survive, and that the top-level secondmate's record survives too.

* no-mistakes(document): Document nested Herdr teardown retention
…d#1431)

* fix(dispatch): prioritize quota completion runway

* no-mistakes(document): Document completion-aware quota runway selection
…uid#1447)

* Preserve task contract in no-mistakes intent

* no-mistakes(review): Preserve complete current task contract in no-mistakes intent
…nguid#1452)

* fix: centralize secondmate registry parsing

* no-mistakes(review): Centralize secondmate registry binding validation

* no-mistakes(review): Harden registry EOF and symlink validation

* no-mistakes(review): Reject unreadable registries before parsing

* no-mistakes(document): Document punctuation-safe secondmate registry validation

* no-mistakes: apply CI fixes
# Conflicts:
#	.agents/skills/afk/SKILL.md
#	.agents/skills/ask-user-authority/SKILL.md
#	.agents/skills/bearings/SKILL.md
#	.agents/skills/bootstrap-diagnostics/SKILL.md
#	.agents/skills/decision-hold-lifecycle/SKILL.md
#	.agents/skills/firstmate-coding-guidelines/SKILL.md
#	.agents/skills/firstmate-orca/SKILL.md
#	.agents/skills/fmx-respond/SKILL.md
#	.agents/skills/harness-adapters/SKILL.md
#	.agents/skills/project-management/SKILL.md
#	.agents/skills/secondmate-provisioning/SKILL.md
#	.agents/skills/stow/SKILL.md
#	.agents/skills/updatefirstmate/SKILL.md
#	.claude/settings.json
#	.github/workflows/ci.yml
#	.gitignore
#	.opencode/plugins/fm-primary-watch-arm.js
#	.pi/extensions/fm-calm.ts
#	.pi/extensions/fm-primary-pi-watch.ts
#	.pi/extensions/lib/fm-calm-assistant-layout.ts
#	.pi/extensions/lib/fm-calm-operational-user-layout.ts
#	AGENTS.md
#	CONTRIBUTING.md
#	README.md
#	bin/backends/cmux.sh
#	bin/backends/herdr.sh
#	bin/fm-afk-launch.sh
#	bin/fm-backend.sh
#	bin/fm-backlog-handoff.sh
#	bin/fm-bootstrap.sh
#	bin/fm-brief.sh
#	bin/fm-composer-lib.sh
#	bin/fm-config-inherit-lib.sh
#	bin/fm-config-push.sh
#	bin/fm-crew-state.sh
#	bin/fm-decision-hold.sh
#	bin/fm-fleet-snapshot.sh
#	bin/fm-guard.sh
#	bin/fm-herdr-session-cleanup.sh
#	bin/fm-lint.sh
#	bin/fm-pending-reply-lib.sh
#	bin/fm-pr-check.sh
#	bin/fm-send.sh
#	bin/fm-session-lock-lib.sh
#	bin/fm-session-start.sh
#	bin/fm-spawn.sh
#	bin/fm-subagent-pretool-check.sh
#	bin/fm-supervise-daemon.sh
#	bin/fm-supervision-instructions.sh
#	bin/fm-supervision-lib.sh
#	bin/fm-teardown.sh
#	bin/fm-test-isolation-proof.sh
#	bin/fm-test-run.sh
#	bin/fm-tmux-lib.sh
#	bin/fm-turnend-guard-grok.sh
#	bin/fm-turnend-guard.sh
#	bin/fm-wake-lib.sh
#	bin/fm-watch-arm.sh
#	bin/fm-watch.sh
#	bin/fm-x-followup.sh
#	bin/fm-x-poll.sh
#	bin/fm-x-reply.sh
#	docs/architecture.md
#	docs/arm-pretool-check.md
#	docs/calm-mode-feasibility.md
#	docs/calm.md
#	docs/cd-guard.md
#	docs/cmux-backend.md
#	docs/configuration.md
#	docs/decision-hold-lifecycle.md
#	docs/documentation-audiences.json
#	docs/examples/crew-dispatch.json
#	docs/fm-test-isolation-proof.json
#	docs/fm-test-isolation-proof.md
#	docs/fm-test-portable-shards.md
#	docs/gitlab-merge-watch.md
#	docs/herdr-backend.md
#	docs/orca-backend.md
#	docs/scripts.md
#	docs/sessionstart-nudge.md
#	docs/subagent-guard.md
#	docs/supervision-protocols/grok.md
#	docs/supervision-protocols/pi.md
#	docs/tmux-backend.md
#	docs/turnend-guard.md
#	docs/verification/runtime-backends.md
#	docs/verification/supervision.md
#	docs/watcher-continuity.md
#	docs/zellij-backend.md
#	tests/fm-afk-inject-herdr-e2e.test.sh
#	tests/fm-afk-launch.test.sh
#	tests/fm-arm-pretool-check.test.sh
#	tests/fm-ask-user-authority.test.sh
#	tests/fm-backend-autodetect-smoke.test.sh
#	tests/fm-backend-cmux.test.sh
#	tests/fm-backend-herdr-eventwait-smoke.test.sh
#	tests/fm-backend-herdr-presentation-e2e.test.sh
#	tests/fm-backend-herdr-prune-safety-e2e.test.sh
#	tests/fm-backend-herdr-respawn-idem-e2e.test.sh
#	tests/fm-backend-herdr-smoke.test.sh
#	tests/fm-backend-herdr-workspace-per-home-e2e.test.sh
#	tests/fm-backend-herdr.test.sh
#	tests/fm-backend-orca.test.sh
#	tests/fm-backend-zellij.test.sh
#	tests/fm-backend.test.sh
#	tests/fm-backlog-handoff.test.sh
#	tests/fm-bearings-snapshot.test.sh
#	tests/fm-bootstrap.test.sh
#	tests/fm-brief.test.sh
#	tests/fm-calm-pi-extension.test.sh
#	tests/fm-cd-pretool-check.test.sh
#	tests/fm-claude-stop-autoarm.test.sh
#	tests/fm-composer-ghost.test.sh
#	tests/fm-crew-state.test.sh
#	tests/fm-daemon.test.sh
#	tests/fm-decision-hold-lifecycle.test.sh
#	tests/fm-documentation-audiences.test.sh
#	tests/fm-fleet-snapshot-view.test.sh
#	tests/fm-gate-refuse.test.sh
#	tests/fm-gotmp.test.sh
#	tests/fm-herdr-lab.test.sh
#	tests/fm-herdr-session-cleanup-e2e.test.sh
#	tests/fm-herdr-session-cleanup.test.sh
#	tests/fm-lint.test.sh
#	tests/fm-pending-reply.test.sh
#	tests/fm-pi-primary-live-e2e.test.sh
#	tests/fm-pi-primary-types.test.sh
#	tests/fm-pi-watch-extension.test.sh
#	tests/fm-pr-check-security.test.sh
#	tests/fm-secondmate-harness.test.sh
#	tests/fm-secondmate-liveness.test.sh
#	tests/fm-secondmate-safety.test.sh
#	tests/fm-secondmate-sync.test.sh
#	tests/fm-send-secondmate-marker-herdr-e2e.test.sh
#	tests/fm-send-strict.test.sh
#	tests/fm-session-start.test.sh
#	tests/fm-sessionstart-nudge.test.sh
#	tests/fm-spawn-dispatch-profile.test.sh
#	tests/fm-subagent-pretool-check.test.sh
#	tests/fm-supervision-instructions.test.sh
#	tests/fm-teardown.test.sh
#	tests/fm-test-isolation-proof.test.sh
#	tests/fm-test-run.test.sh
#	tests/fm-tmux-submit-busy.test.sh
#	tests/fm-turnend-guard.test.sh
#	tests/fm-watch-triage.test.sh
#	tests/fm-watcher-lock.test.sh
#	tests/herdr-test-safety.sh
@DereKk8
DereKk8 force-pushed the fm/fm-upstream-sync-a1 branch from 9216968 to da0f006 Compare August 1, 2026 20:20
@DereKk8
DereKk8 merged commit e252bad into main Aug 2, 2026
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.