Skip to content

feat(bin): show requested vs effective model in Herdr agent view - #3705

Open
Valentino-Sole wants to merge 11 commits into
kunchenguid:mainfrom
Valentino-Sole:fm/model-display
Open

Valentino-Sole wants to merge 11 commits into
kunchenguid:mainfrom
Valentino-Sole:fm/model-display

Conversation

@Valentino-Sole

Copy link
Copy Markdown

Summary

  • Adds fm-model-lib, fm-model-probe, and fm-model-sync to separate spawn-config requested model from runtime-verified effective model (exact API id only; aliases become UNKNOWN).
  • Wires spawn/relaunch meta, Claude hooks (submit/stop/session-start for compaction recovery), and Herdr report-metadata so the agent pane shows harness, effort, requested/effective model, and fallback history.
  • Registers tests/fm-model-display.test.sh in the portable test family.

Test plan

  • ./bin/fm-test-run.sh tests/fm-model-display.test.sh (8/8 pass)
  • Herdr pane shows compact model display after spawn on a live Claude worker
  • Relaunch preserves verified effective_model in meta and Herdr display

Valentino-Sole and others added 5 commits September 3, 2026 00:45
…lent (#2)

* fix(bin): verify the real GitHub merge outcome instead of reporting an unproved merge (#3064)

* fix(pr): verify GitHub merge outcome

* no-mistakes(review): Captain, fixed forge-only merge verification, queue guidance, metadata propagation

* no-mistakes(document): Correct forge-specific merge documentation

* no-mistakes(review): Captain: forge-only queue fix, focused tests pass

* no-mistakes(review): Captain: suppress closed-state guidance and prove parent regression

* no-mistakes(review): Captain: remove history proof; retain executable regressions

* no-mistakes(document): Clarify GitHub recording timing in architecture docs

* no-mistakes(document): Clarify outcome-aware PR merge recording documentation

* no-mistakes: apply CI fixes

* Revert "no-mistakes: apply CI fixes"

This reverts commit c326cfa9430c6173eedc8ff7f27d19d0552daf01.

The automatic CI repair round removed the up-front `gh` prerequisite check
while keeping the `gh` dependency: `bin/fm-pr-merge.sh` still calls
`gh api graphql` for the outcome read and `gh api` for the branch-rules read.
That left the same hard requirement without the clear named error, and review
immediately raised a new finding for exactly the failure the check prevents -
`gh-axi pr merge` landing the merge while the follow-up read fails, so the PR
metadata is never recorded.

The check is also symmetric with the GitLab arm directly above it, which
already refuses up front when `glab` or `jq` is missing, on the stated
principle that a missing tool should be a named prerequisite rather than a
merge that is armed and then refused for an unexplained reason.

The workflows this round was chasing sit at `action_required` because this is
a fork pull request; no code change can turn them green.

* fix(pr): keep PR bookkeeping when a merge outcome read fails

On the GitHub path a merge call that returned success was followed by
`github_read_outcome || exit 1`, so a transient API failure, rate limit,
or network blip during the read dropped out of the script before
`record_pr_metadata` ever ran. The merge could have landed while `pr=`
went unrecorded and the merge poll was never armed - bookkeeping lost on
a real merge. The failure path just above already recorded metadata
before exiting, so the error path was more careful than the success one.

Record the PR before that refusal. Recording arms the later merge poll
and is not a success claim, which is the same reasoning that keeps
`record_pr_metadata` on the gh-axi failure path. The refusal itself is
unchanged: exit stays non-zero and the message still names the concrete
observed state. Metadata is withheld only when the read succeeds and
proves the pull request neither merged nor queued.

Pin it with a case that stubs `gh api graphql` into failure after a
successful `gh-axi pr merge`, asserting both the non-zero exit and the
recorded metadata.

* no-mistakes(review): Aggregate queue rules and report conflicts explicitly

* fix(pr): keep the merge abstraction reachable and its bookkeeping intact

Two holes remained in the outcome-verified GitHub merge path, both on
installations where gh-axi is present but gh is not.

The verification preflight refused before bin/fm-pr-merge.sh ever reached
the configured gh-axi merge abstraction, so an installation without gh
could no longer merge at all. gh-axi now performs the merge unconditionally
and the queue-aware gh read became an optional enrichment: with gh on PATH
its GraphQL view still separates merged from queued, and without gh the
gh-axi view still proves a landed merge while every outcome it cannot prove
refuses.

The PR metadata recording sat behind the outcome read, so a merge that
landed before that read failed lost pr= and its merge poll. Recording now
happens once, before either forge call, which arms the poll without
claiming a landed outcome and leaves teardown a PR identity to verify
against no matter how the read ends.

Rebasing onto main also restored the durable merge-outcome reporting and
the GitLab landed-state confirmation that the conflict resolution dropped.

Tests pin each fix through the executable interface: the merge abstraction
is reached and verified with gh absent, a failed fallback read keeps its
bookkeeping, and a mock that snapshots the task meta during the forge call
proves pr= is recorded before the merge can land.

* no-mistakes(review): fix(pr): de-dup queue methods, fall back on failed gh read, refresh contracts

* no-mistakes(review): fix(pr): quote forge output and explain armed auto-merge on refusal

* no-mistakes(review): fix(pr): claim auto-merge armed only when the forge accepted it

* no-mistakes(review): fix(pr): tell the operator what each GitHub refusal could not observe

* no-mistakes(review): fix(pr): gate every forge-acceptance claim on a successful merge

* no-mistakes(document): align merge docs with verified GitHub outcome contract

* fix(pi): prevent duplicate captain outcome reports (#3184)

* fix(pi): stop reporting one merge to the captain twice

The supervision branch's captain-outcome note told main, unconditionally,
that the note "is not your own earlier output" and to relay it now. When
main had already reported the same event, that assertion was false and the
order turned the correct response - saying nothing new - into a mechanical
re-report, so the captain saw one merge reported twice in 16 seconds.

Two independent changes, both needed:

- The relay instruction is now conditional. It still names itself as a
  supervision outcome so main cannot mistake it for its own earlier answer
  (the silent loss that instruction exists to prevent), and it now lets
  main stay quiet about an outcome it has already given the captain.

- The merge case is closed at its source rather than left to that judgment.
  One merge reaches a home on two independent paths by design - main's own
  permanently main-owned merge poll, and the branch's task-local status
  wake - and main's captain-facing text only reaches the branch's mirror at
  main's turn end, so the branch can escalate before it could possibly see
  the captain was already told. bin/fm-pr-merge-notified.sh answers that
  question from bin/fm-pr-lib.sh's canonical merge-notification marker, so
  the answer holds regardless of mirror timing. A captain outcome naming an
  already-published merge is delivered as the ordinary rendered note
  instead of opening a follow-up turn: still appended, still visible, still
  recorded with the verdict the branch decided, minus the wasted turn.

Any error, timeout, or unreadable state relays the outcome. A duplicate
announces itself; a lost outcome does not.

Regression coverage drives the real delivery path in both directions: a new
outcome must still reach the captain in exactly one follow-up turn even
beside an unrelated published merge, and an already-published merge must
open no second turn while a different PR in the same task still does. The
merge path's real producer and this new consumer are exercised end to end
in tests/fm-pr-merge.test.sh.

Pi-only by construction: the delivery path lives in .pi/extensions, so no
other harness loads it, and the new script only reads existing markers.

* no-mistakes(review): Document accepted latest-marker suppression residual

* no-mistakes(review): Recheck ownership before merge outcome delivery

* no-mistakes(document): Document merge-outcome suppression exception

* refactor(pi): drop the source-level merge suppression, keep the envelope fix

The captain reviewed this branch and judged the source-level duplicate
suppression overly complicated for the problem it solved, and asked for
the change to be reduced to the envelope wording alone.

Remove the mergeIntoMain downgrade path, bin/fm-pr-merge-notified.sh, and
every test and document that existed only for it. What remains is the
conditional captain-outcome instruction: main is told to stay quiet about
an outcome it has already reported and to relay anything else, which
covers the duplicate without a second mechanism.

The silent-loss protection is untouched - the note is still typed,
self-describing, and delivered as one invisible follow-up turn - and the
behavioral tests still assert that, now requiring both halves of the
conditional instruction.

* no-mistakes(ci): Clarified in code comments and owned documentation that this is intentionally an M1-only, model-facing conditional relay fix—not source-level suppression—addressing Greptile’s mistaken scope expectation without changing runtime behavior. Net diff remains 3 files and 27 insertions. Verified with fm-pi-branch-extension tests, fm-lint, doc audience check, and git diff --check; all passed

* no-mistakes(ci): Strengthened the runtime delivery test to verify the captain outcome retains its required self-description and outcome text. Verified with `bash tests/fm-pi-branch-extension.test.sh`, `bin/fm-lint.sh`, `bin/fm-doc-audience-check.sh`, and `git diff --check`; all passed. The outer pipeline can now commit and attest the new head

* fix(bin): prioritize active pipeline-owned crew runs (#3194)

* fix(bin): bind the live pipeline-owned run instead of a superseded failed row

fm-crew-state.sh bound a superseded FAILED no-mistakes run to a task instead
of the LIVE replacement run: the live run's pipeline-owned lane head is not a
git object in the task worktree, so head-equality attribution rejected it and
the coarse runs-list fallback silently continued past the RUNNING row onto an
older failed row whose head equalled the stale worktree HEAD. The home summary
then flipped invalid and Bearings hid the home's live work (F10).

Attribution precedence now follows the daemon's own identity:
- An ACTIVE run for the task's branch binds without head equality while
  branch_sync.state is pipeline_owned (fm_nm_run_is_pipeline_owned_active);
  the pipeline owning the branch is itself the attribution.
- A genuinely failed run with no later run on the branch still reports failed
  through the unchanged head-equality path - real failures are not hidden.
- In the coarse runs scan, an unresolvable head is unknown attribution and
  stops the scan (fm_nm_head_resolvable) instead of falling through to an
  older row; a resolvable-but-mismatched head keeps the historical
  reused-branch skip.

The exemption never applies to a terminal run and requires pipeline_owned
specifically, both pinned by negative-control tests. Fixture shape verified
against the live incident run's real axi status output.

* no-mistakes(document): Updated run-attribution documentation ownership

* fix(pi): surface requested outcomes without replaying fleet events (#3211)

* fix(pi): surface requested supervision outcomes

* no-mistakes(review): Mirror in-flight captain requests before branch dispatch

* no-mistakes(review): Exercise real branch ownership and main outcome access

* no-mistakes(review): Preserve request tails and align verdict guidance

* no-mistakes(review): Preserve complete current captain requests

* no-mistakes(review): Require visible requested outcomes and realistic classification

* no-mistakes(document): Align supervision outcome documentation

* no-mistakes(ci): Fixed Greptile’s runtime-ordering finding. The extension now stages Pi’s authoritative `before_agent_start` prompt before SessionManager persistence and suppresses the later duplicate entry. Updated docs and behavioral regression to reproduce real Pi ordering and verify each prompt is mirrored exactly once. Passed branch-extension tests, supervision tests, strict Pi typecheck, full lint, and diff checks

* no-mistakes(review): Use canonical operational input classification

* no-mistakes(review): Filter legacy operational inputs canonically

* no-mistakes(document): Clarify captain request mirroring boundary

* no-mistakes(ci): Fixed the CI time-boundary failure in tests/fm-public-followup.test.sh by pinning its clock, including context-registry setup. This prevents follow-up fixtures from expiring based on wall time. Verified the full regression suite passes, project-owned lint passes, and git diff checks are clean

* no-mistakes(document): Clarify captain-visible supervision outcome documentation

* feat(bin): add concurrent bounded remote transport lanes (#3210)

* feat(bin): per-home remote transport lanes with cancellation, bounded send, and closed stdin

All remote commands for every home on one host used to serialize through one
single-job-at-a-time worker on one shared queue: a timed-out caller abandoned a
staged job that kept running, retries convoyed behind it, fm-send's remote leg
had no time bound, and staging captured the caller's stdin to EOF so any
fm-on.sh caller with an open stdin wedged staging indefinitely.

- The worker now serves one lane per staged home: same-home jobs run strictly
  FIFO in a new staging-sequence order while different homes run concurrently,
  each lane as its own top-level worker process (a backgrounded subshell does
  not reliably reap dead children, so a zombie group leader kept a finished
  command's process group signalable). Long-poll preemption is lane-scoped.
- A caller that disconnects or times out cancels its job: the entrypoint marks
  the record on any post-staging exit and probes its parent so a dead ssh
  channel cancels without a signal; the worker skips cancelled queued jobs,
  terminates a running cancelled job's process group, and reaps the record.
- fm-send's remote leg is bounded by FM_SEND_REMOTE_BUDGET (default 30s) and a
  bound hit exits through the existing unconfirmed-delivery contract, which
  stays idempotent because the remote enqueue deduplicates.
- fm-on.sh defaults the remote command's stdin to /dev/null; the three payload
  callers pass the new --stdin flag. Abandoned .stage.* litter is age-reaped.
- The job execution deadline no longer loses up to a second to clock
  truncation.

* no-mistakes(review): Protect live stages and validate send budgets early

* no-mistakes(review): Preserve sequence lock ownership during stale recovery

* no-mistakes(review): Allocate job sequences at publication boundary

* no-mistakes(review): Bound remote keys and extend stale lock recovery

* no-mistakes(document): Document bounded remote transport behavior

* no-mistakes(lint): Suppress intentional deferred-expansion lint warning

* no-mistakes(ci): Fixed stale sequence-lock recovery by reconciling the counter against published job records before allocating the next sequence, preventing duplicate sequences and same-home FIFO violations. Added a behavioral regression test reproducing displacement after publication and verifying execution order. Passed fm-remote-transport-lanes.test.sh, fm-remote-job.test.sh, fm-lint.sh, and git diff --check

* no-mistakes(review): Use atomic sequence claims and lossless lane keys

* no-mistakes(review): Recover regressed sequence hints and rate-limit claim reaping

* no-mistakes(review): Restrict worker heartbeats to serving loop

* no-mistakes(review): Verify supervisor identity before lane recovery signals

* no-mistakes(review): Verify tracked lane and claim owner identities

* no-mistakes(document): Clarify remote lane and transport contracts

* no-mistakes(ci): Fixed the CI time-boundary failure by pinning fm-public-followup tests to a deterministic clock, including context-registry setup. Verified tests/fm-public-followup.test.sh, tests/fm-remote-transport-lanes.test.sh, shellcheck, and git diff --check

* no-mistakes(review): Preserve assigned lane ownership of queued jobs

* no-mistakes(review): Reserve homes owned by foreign queued lanes

* no-mistakes(review): Preserve completed results during crash recovery

* no-mistakes(review): Harden claim cleanup, expiry, and cancellation races

* no-mistakes(review): Verify process groups and reap abandoned results

* no-mistakes(review): Stop leaderless groups and reap cancelled publications

* no-mistakes(document): Correct remote transport lifecycle documentation

* no-mistakes(lint): Quote done state comparisons for ShellCheck

* fix(bin): accelerate and bound changed test runs (#3250)

* fix(tests): make the changed-file map select per script and stabilize a budget flake

The changed-file map's bin/ fallback resolved a direct test reference to that
test's whole FAMILY. bin/fm-push-transition-lib.sh is named by exactly one
real-Herdr E2E, so a one-line change to it selected all 12 real-herdr-gated
scripts, including a 341s presentation E2E with no dependency on it.

Resolve direct test references per script, and keep resolving consumer bin/
scripts through the curated map so recorded family-level coupling survives.

Also fix a load-sensitive flake: the tool-update budget deadline is whole-second
granular, so a test budget of 1 left headroom anywhere in (0, 1] seconds and the
first budget check could already read as exhausted.

* feat(bin): make suite wall clock a result and let a family's concurrency be proven

--max-wall-ms fails a run whose wall clock exceeds the caller's budget, after
reporting the per-script results. A suite that stays green while outgrowing its
caller's invocation budget is the regression that got an agent killed mid-run
and retried invisibly, so duration has to be a result rather than a log note.

--pool on the isolation-proof harness runs the same concurrent proof over a
whole family, so 'is this family safe to parallelize?' is answered by a command
instead of a guess. Measured watcher-wake-lock and refused it: 3 of 18 scripts
fail under concurrency on wall-clock assertions about reaching the next poll.

* perf(bin): schedule the changed suite concurrently, longest first

The watcher-wake-lock family is proven concurrent-safe (two clean runs, 18
candidates, 0 failures at 4 workers; docs/fm-test-isolation-proof.md), so
--changed now schedules its proven-concurrent scripts with bounded parallelism
and runs any unproven remainder serially afterwards, never beside them.

Concurrent runs are ordered longest-hint-first. Workers are handed scripts in
order, so alphabetical order started the 193s fm-watch-triage last and stranded
it running alone: 395s wall against a 205s balanced four-worker sum.

An explicit --jobs keeps its strict refusal, so every CI lane is unchanged.

* fix(bin): bound a hung test instead of letting it hang the suite

tests/fm-calm-pi-extension.test.sh was observed running 17+ minutes against a
464ms recorded hint, and the suite had no per-script bound to stop it. An
unbounded suite is precisely what silently outruns a caller's invocation budget,
and --max-wall-ms is evaluated after the run so it cannot end one that never
finishes.

--per-script-timeout-secs terminates a script that outruns it and records exit
124, so the run still completes, accounts for the script, and fails. The
auto-concurrent --changed path applies 900s, far above the slowest real script
(the 341s Herdr presentation E2E), so it only ever converts a hang.

* no-mistakes(review): Enforce safe concurrency and descendant timeouts

* no-mistakes(review): Validate empty runs and isolation proof pools

* no-mistakes(review): Measure selection time in wall budget

* no-mistakes(review): Reap interrupted workers and bound finalization

* no-mistakes(review): Contain shutdown descendants and watchdog finalization

* no-mistakes(review): Honor remaining budget and close launch races

* no-mistakes(review): Restore timeout helper and simplify runner cleanup

* no-mistakes(review): Record isolation pool admission metadata

* no-mistakes(review): Bound Chrome reap and scope proof admission

* no-mistakes(review): Align proof scheduling and preserve budget summaries

* no-mistakes(review): Remove unreliable finalization watchdog

* no-mistakes(review): Freeze budget duration and enforce admission caps

* no-mistakes(document): Refresh test runner concurrency documentation

* no-mistakes(lint): Fix ShellCheck findings in test runner scripts

* no-mistakes(ci): Fixed Greptile’s concurrency-consent finding. `--changed` now remains serial by default; `--changed --jobs auto` explicitly opts into bounded concurrency and the automatic hang timeout. Updated documentation and added behavioral coverage proving serial default behavior, explicit concurrent scheduling, and refusal of `--jobs auto` outside `--changed`. Verified with `bash tests/fm-test-run.test.sh`, `bin/fm-lint.sh`, and `git diff --check`

* no-mistakes(review): Restore automatic changed-suite concurrency and timeout

* no-mistakes(review): Correct changed-suite contributor guidance

* no-mistakes(review): Reject gate-skipped isolation proofs

* no-mistakes(review): Correct automatic concurrency evidence

* no-mistakes(review): Isolate nested runner process groups

* no-mistakes(review): Remove unreliable signal cleanup machinery

* no-mistakes(test): Narrow changed-suite selection to executable contract owners

* no-mistakes(document): Document isolation proof skip and artifact semantics

* no-mistakes(ci): Fixed Greptile’s concurrency-consent finding. `--changed` now remains serial by default; bounded concurrency requires explicit `--jobs auto`. Updated behavioral coverage, contributor guidance, and isolation-proof commands accordingly. Verified with `tests/fm-test-run.test.sh`, `bin/fm-doc-audience-check.sh`, `bin/fm-lint.sh`, Bash syntax checks, and `git diff --check`; all passed

* no-mistakes(review): Restore plain changed-suite automatic concurrency

* no-mistakes(review): Record resolved changed-suite worker count

* fix(bin): keep a runner change selecting its whole curated family

A pipeline fix round narrowed the curated changed-file map so bin/fm-test-run.sh
and bin/fm-test-isolation-proof.sh selected only their own two contract tests,
and the documentation surfaces only the audience test. That cut this branch's
own changed selection from 33 scripts to 5.

The runner executes every pure-contract-unit script, so its contract test
passing proves its logic is right, not that the suite it drives still runs.
Narrowing it also makes any wall-clock claim about the changed suite trivially
true by not running the work.

Only the unmapped bin/* grep fallback resolves per script; curated mappings keep
their recorded family coupling.

* perf(bin): admit the pure-contract-unit family to bounded concurrency

A runner-file change selects pure-contract-unit, so that family decides the
changed suite's wall clock. With only watcher-wake-lock admitted, 14 of its 33
selected scripts fell to the serial tail and the selection measured 327.3s
against a 300s budget: the concurrent group was 19 scripts totalling 273.4s
while the tail alone was 215.7s.

bin/fm-test-isolation-proof.sh --pool pure-contract-unit --jobs 4 passes twice,
32 candidates, 0 failures, so the family is admitted on recorded evidence.

Full 33-script plain --changed: 327.3s -> 181.8s / 178.5s / 172.7s, 0 failures,
inside a 300000ms budget. Also states the per-script guard's derivation.

* no-mistakes(review): Align contract-unit concurrency cap with recorded proof

* no-mistakes(document): Record final changed-suite performance evidence

* fix(bin): keep an empty changed selection clean on stock macOS Bash

Under set -u, bash 3.2 treats "${arr[@]}" on an EMPTY array as an
unbound-variable error, while bash 4.4+ makes it a harmless no-op. The
concurrency work removed the early exit for an empty selection, so execution
fell through to the unguarded existence loop: on stock /bin/bash 3.2.57 a
contributor who changes only documentation and runs --changed got

  bin/fm-test-run.sh: line 1713: SCRIPTS[@]: unbound variable

with exit 1 and no summary, instead of a clean total=0 pass.

Restore the early exit, and guard every remaining array expansion reachable
with an empty selection. The reported duration is real elapsed invocation
time rather than a hardcoded zero, so a selection phase that outran
--max-wall-ms still fails.

Verified on this host with /bin/bash 3.2.57: exit 1 with the unbound-variable
error before, exit 0 with FM_TEST_SUMMARY total=0 after.

* no-mistakes(document): Document shell-bound changed-suite performance

---------

Co-authored-by: Kun Chen <kun-1@kunchenguid.com>

* feat(bin): publish per-home summary ledgers (#3222)

* feat(bin): publish per-home summary ledger

* no-mistakes(review): Bound and schedule home summary publication

* no-mistakes(review): Prove recurring watcher summary refresh cadence

* no-mistakes(review): Bound refresh workers and publish durable spawns

* no-mistakes(review): Fix atomic kill process-group coverage

* no-mistakes(review): Bound state initialization within refresh timeout

* no-mistakes(document): Document recurring bounded home-summary publication

* no-mistakes(review): Bound and log all best-effort refresh failures

* no-mistakes(review): Harden cadence and timeout regression coverage

* no-mistakes(document): Document home-summary runtime tuning

* no-mistakes(lint): Fix direct exit-code check in refresh test

* no-mistakes(ci): Fixed remote secondmate retirement recreating the deleted home: teardown now skips side-band summary refresh when its overridden state directory was removed. Verified with remote lifecycle E2E, teardown tests, home-summary tests, ShellCheck, and git diff checks

* no-mistakes(document): Clarify atomic home-summary publication guarantee

* fix(pi): gate first provider call on startup context (#3158)

* fix(pi): gate first call on startup context

* no-mistakes(document): Correct Pi startup prerequisite verification date

* no-mistakes(review): Captain, fix startup process-group retirement after leader exit

* no-mistakes(review): Captain, release reload exit listeners on shutdown

* no-mistakes(review): Captain, complete startup exit lifecycle ownership

* no-mistakes(review): Captain, release empty startup process-group ownership promptly

* no-mistakes(review): Captain, supervise startup ownership and restore failure fallback

* no-mistakes(review): Captain, restore live Pi supervisor execution

* no-mistakes(document): docs: clarify Pi startup prerequisite delivery

* fix(pi): restore Pi 0.84.4 renderer compatibility (#3261)

* fix(pi): restore 0.84.4 adapter compatibility

* no-mistakes(review): Restore Pi collapsed and expanded outcome parity

* no-mistakes(review): Preserve Pi stock previews through capability probing

* no-mistakes(document): Document Pi 0.84.4 renderer compatibility

* fix(bin): keep home-summary publication from starving supervision (#3273)

* fix(bin): keep home-summary publication bounded and off the watcher beat

A home whose tasks had accumulated ordinary status history could not publish
state/home-summary.json at all, and every attempt starved the watcher's
liveness beacon while it failed silently.

The producer's per-task open-decision fold spent tens of milliseconds per
status line on a bash 3.2 global bracket-class substitution used only as a
blank-line guard. On a real home that made the whole ledger producer take
minutes, so publication burned its full FM_HOME_SUMMARY_TIMEOUT on every
attempt and never completed. Replace that guard with an equivalent case glob
in the one fold owner, which both the whole-file and cursor-backed folds use.

Bound each per-task current-state read in the snapshot with
FM_SNAPSHOT_CREW_STATE_TIMEOUT. For a remote secondmate that read crosses ssh,
whose dead-peer detection deliberately never kills a slow-but-alive remote
command, so nothing else bounded it.

Detach the watcher's two publication triggers from the poll loop. The loop
owns the beacon that fm-guard.sh reads as proof supervision is alive, and an
inline publication put up to a full publication deadline between two beacon
touches. A single in-flight publication is tracked so a slow one cannot
accumulate clones.

Report a repeatedly failing publication at session start. Publication stays
deliberately non-fatal to its caller, so the existing bounded home-local
failure record is now surfaced as a HOME_SUMMARY bootstrap line once the
ledger is absent or stale and failures have been recorded since.

* no-mistakes(review): Preserve home-summary failure attempt ordering

* no-mistakes(review): Enforce durable home-summary single-flight and ordering

* no-mistakes(review): Derive failure ordering from publication boundaries

* no-mistakes(review): Restore best-effort failure logging and publication scoping

* no-mistakes(review): Make ordering regression sensitive to one failure

* no-mistakes(document): Correct HOME_SUMMARY diagnostic guidance

* fix(bin): prevent routine updates from hiding actionable status (#3268)

* fix(supervision): classify the appended status span, not the last line

An actionable project update could be classified as routine and absorbed, so
a worker that raised a decision, hit a blocker, failed, or finished stalled
silently with the captain never told.

Trigger, mask, symptom. A worker appends a captain-relevant event
(`needs-decision`, `blocked`, `failed`, `done`). Any later routine append -
a `working:` progress note - lands before the supervisor classifies the
batch; the watcher's 30s signal-grace linger exists precisely to coalesce a
status write with the same turn's turn-end, so this window is ordinary
rather than rare. Both supervisors then asked "is the LAST line
captain-relevant?", read the routine line, and absorbed the wake. The
`.seen-*` suppressor advanced either way, so nothing ever re-read the event.
When the crew was also provably working, the no-verb fallback absorbed it
too, which is why the event disappeared completely instead of surfacing late.

Reproduced end to end against a real watcher before any change: with the
trailing `working:` append the watcher never exits and the wake queue stays
empty; with that one line removed - the smallest counterfactual - the same
`needs-decision` surfaces and queues. The away-mode daemon's `classify_signal`
returns `self|routine signal` for a `blocked:` event under the same mask,
which is the worse case because no captain is present to notice.

The proven path was already in the tree: `status_open_decisions` fixed this
exact masking for the durable decision fold, and its header states the rule -
reading an append-only event log last-event-wins cannot represent an earlier
event that a later unrelated line moved past. The classification path was
never migrated to that read model. That is the earliest divergence, and the
fix is to migrate it rather than to special-case the symptom.

`status_span_first_actionable` in bin/fm-classify-lib.sh is the new single
owner: it reads the bytes at or after a caller-supplied position and returns
the first still-live captain-relevant event. Each supervisor supplies its own
position, because the always-on watcher and the away-mode daemon classify the
same stream independently and must not share one cursor: the watcher reads
the size already recorded in its `.seen-*` signature (no new state) and its
`.hb-surfaced-<task>` backstop marker, and the daemon its
`.subsuper-seen-status-<task>` marker. Those two markers held the escalated
line and now hold the escalated-through byte offset, which also removes a
second defect in the same code - content dedup silently swallowed a genuinely
new event whose text repeated an older one. An absent, malformed, or
past-the-end position reads the whole log, so uncertainty surfaces events
rather than losing them, and a marker an older build wrote as a status line
reads that way too. Status logs are only ever appended to, including across a
reused task id, so a recorded position keeps its meaning.

A `needs-decision`/`blocked` event in the span is retired only when the
whole-file fold proves its key closed; `status_open_decisions` stays the sole
owner of that rule, so same-key reopening and reserved-key namespaces need no
second implementation here. Every other captain-relevant event is terminal
and always actionable.

Both backstops now walk every status log instead of only those whose last
line looks captain-relevant, because the event a backstop most needs to catch
is exactly one a later append has moved past. That leaves
`scan_captain_relevant_statuses` with no callers, and it is removed rather
than left as a working copy of the defective read model.

Regression coverage exercises the classifier and both supervisors through
their own interfaces: the masked decision, the captain-reported
release/install completion followed by cleanup chatter, and the away-mode
blocker all surface; a routine append after an already-classified event stays
absorbed, so the fix does not convert ordinary progress into wakes; and the
heartbeat backstop catches a masked event the per-wake path missed. The
end-to-end watcher tests drive a real fm-watch.sh with the crew reported as
provably working, which is the configuration that made the original stall
silent.

Two further claims in the supplied RCA are deliberately not patched here.
"Repeated operational recoveries produced all-clear replies despite known
actions" is downstream of this same cause, not an independent contributor: an
all-clear reply is the documented response when the specific event needs no
action, so a classification that wrongly reported "no action" produces it, and
correcting the classification removes it. "The project was subjected to
validation requirements outside its accepted path" is delivery-mode selection,
which AGENTS.md section 7 owns; no code changed here touches it, so it is out
of scope.

Harness and backend axes were inspected rather than assumed: nothing in this
path reads a vendor-emitted signal. The status log's format and append
protocol are Firstmate's own and identical for every harness, and no runtime
backend reads or writes `.status` files (`bin/backends/*` contain no reference
to them). The surrounding triage's only backend touchpoints - pane capture and
the authoritative crew-state read - are unchanged. No live-harness guard
applies and no per-harness verification record changes.

Verified with `bin/fm-lint.sh`, `bin/fm-doc-audience-check.sh`, and
`bin/fm-test-run.sh --changed --base origin/main`.

* no-mistakes(review): Prevent status races and surface classification failures

* no-mistakes(review): Surface unreadable signals and preserve AFK endpoints

* no-mistakes(review): Route stale wakes through captured span verdicts

* no-mistakes(review): Retire supervision offsets with reused task state

* no-mistakes(review): Bind status offsets and preserve live decision origins

* no-mistakes(review): Strengthen status identity with verified birth time

* no-mistakes(review): Skip turn-end markers during status classification

* no-mistakes(review): Preserve status presentation with platform-strength identities

* no-mistakes(review): Retain failed wakes and advance routine checkpoints

* no-mistakes(review): Surface all events and retain unreadable wakes

* no-mistakes(review): Treat absent status logs as successful empty spans

* no-mistakes(review): Bound repeated classification failures with durable receipts

* revert(supervision): drop the failure-receipt and durable-retry machinery

Captain-authorized revert to the minimal fix. Review rounds added a durable
failure-receipt store and wake-retention-on-failure to bound repeated
classification failures. That machinery grew larger than the fix it protected
and kept producing its own defects: an unreadable log still looped forever
because the always-on watcher never consulted the receipt, and the receipt was
persisted before its diagnostic was durably queued, so a crash in between
swallowed the alarm outright. Those two defects go away with the code that
contained them rather than being repaired.

Removed: the failure-receipt path, fingerprint, record and clear helpers and
their retirement bookkeeping; the retention of a durable wake when
classification fails; and the error-propagation plumbing in both supervisors
that existed only to drive them.

Kept, because it is the accepted fix rather than the declined machinery: span
classification of the events appended since a supervisor last looked, in both
supervisors and both backstops; reporting every actionable event in a span and
committing a position only through what was reported; naming the live opening of
a reopened decision; treating an absent log as ordinary and an unreadable one as
worth reporting; the non-.status filter; and the platform-strength identity that
guards a position commit without failing a read.

Replacement behavior for a log that cannot be classified: report it once, do NOT
advance the classification position so the content is classified from where it
stopped once readable, and DO advance the wake signature so the report is
bounded to one per distinct file state. Reporting and reading are different acts:
telling the captain about a log is not the same as having read it, and only the
latter may move a classification position.

The residual risk is explicit and accepted: there is no guaranteed automatic
retry inside a crash-mid-read window, and the locked session-start replay of the
durable queue covers it. That rationale is recorded at mark_escalated_seen so a
future reader does not reintroduce the retry as a "missing" guarantee.

Also fixes lint failures that arrived with the review-fix commits and were never
caught because the run never reached its lint step: an unfollowable conditional
source directive, a second unquoted-expansion site left after a call was split
across lines, cleanup of the file being read inside its own read loop (restructured
to one post-loop teardown rather than three in-loop copies), stub functions in
tests that are invoked indirectly, and a test local left unused when its
assignment was replaced by a helper. bin/fm-lint.sh passes on the default branch,
so these were introduced here.

Verified with `bin/fm-lint.sh`, the end-to-end masked-decision and away-mode
reproductions, and `bin/fm-test-run.sh` over the supervision, wake-queue,
wake-drain, watch-arm and inactive-reconcile suites (6 scripts, 0 failures).

* no-mistakes(review): Correct classification failure contract documentation

* no-mistakes(review): Bound unreadable status reports without skipping classification

* no-mistakes(review): Preserve escalation markers when buffering fails

* no-mistakes(review): Detect permission recovery without advancing classification

* no-mistakes(document): Document status span classification contract

* no-mistakes(ci): Fixed CI failures by lazily loading classification helpers in fm-wake-lib, preserving minimal recovery/remote fixtures; added a public current-status marker helper and updated behavioral fixtures to use the v2 marker contract; resolved ShellCheck variable collisions in fm-control and fm-public-followup-lib. Verified fm-lint, bash syntax, fm-control, public-followup, wake-queue, send-resolve-key, captain-hold, pending-reply, remote-reply, remote-backlog-handoff, turnend-guard, and Claude autoarm tests. The Pi branch suite reached a separate local stock-render mismatch under Node 24; its CI-reported missing-classifier failure path is fixed

* no-mistakes(review): Escalate blockers while preserving declared-wait cadence

* no-mistakes(review): Clarify actionable events override wait self-handling

* no-mistakes(review): Surface rejected decisions and dangling status links

* no-mistakes(document): Document reserved-key reconciliation classification

* no-mistakes(ci): Fixed the flaky portable serial CI test by modeling the retained staging directory as genuinely owned by a live process and aging both fixtures deterministically. This removes scheduler-timing dependence while verifying the worker reaps abandoned staging and preserves live staging. Verified with fm-remote-transport-lanes.test.sh, bin/fm-lint.sh, bash syntax, and git diff --check

* no-mistakes(document): Correct away-mode classification documentation

* docs(skills): split harness adapter operations reference (#3289)

* docs: split harness adapter operations reference

* no-mistakes(review): Fix harness adapter routing and ownership contracts

* no-mistakes(review): Prune duplicate harness adapter ownership prose

* no-mistakes(review): Fix default effort routing and Grok max semantics

* no-mistakes(review): Remove source-only routing test and duplicate semantics

* no-mistakes(review): Add local harness adapter instruction evaluation

* no-mistakes(review): Fix harness evaluation gating and change mapping

* no-mistakes(test): Captain, require explicit harness instruction evaluator model

* no-mistakes(document): Fix harness adapter documentation references

* test: centralize shared shell fixtures (#3296)

* test(fixtures): share fake-toolchain and spawn-world builders

Future tests can start from tests/fixtures.sh instead of copying stubs, and a
no-mistakes version-floor bump is one constant rather than a multi-file edit.

Migrated this round: fm-busy-adapter-wiring, fm-spawn-pool-base-freshen,
fm-grok-harness, fm-tangle-guard, fm-gate-refuse, fm-spawn-dispatch-profile.
Left for opportunistic migration: remaining make_spawn_fakebin copies
(trace-context, kimi, muse, backend), the make_stubs send cluster, and the
fake no-mistakes version banners in bootstrap/session-start/secondmate suites.
Did not touch tests/fm-pr-check-security.test.sh.

* no-mistakes(review): Prevent fake SSH test from blocking on stdin

* no-mistakes(document): Clarify shared fixture documentation

* no-mistakes(ci): Fixed the flaky watcher triage test by extending its startup-sensitive timer-repair wait from 3s to 10s, matching existing loaded-runner budgets. Verified with the full tests/fm-watch-triage.test.sh suite, bash syntax validation, and git diff checks

* no-mistakes(ci): Fixed portable serial shard 4 by updating the inactive-reconcile fixture to prime status through the public fm_wake_status_mark_current API, ensuring classifier helpers load correctly and preventing the idle watcher from exiting. Verified the test three consecutive times, ran fm-test-fixtures, ShellCheck, bash syntax checks, and git diff checks. The outer no-mistakes executor can now bind a fresh attestation to the new head

* no-mistakes(ci): Added behavioral coverage proving the shared spawn tmux fixture defaults an unset FM_FAKE_PANE_PATH to empty. Verified the fixture suite, ShellCheck, syntax/diff checks, and all six migrated test suites; all passed. The outer executor can now bind a fresh no-mistakes attestation to the updated head

* refactor: retire legacy PR-check migration machinery (#3299)

* feat(bin): retire completed PR-check migration machinery

Every registered home already carried both completion markers, and no
installer still creates pre-migration checks. Remove the one-time migrate
script, its bootstrap/watch/teardown/docs surface, and migration-path tests
without weakening live check-trust or PR-poll authentication.

* no-mistakes(review): Restore live PR-check security coverage

* no-mistakes(document): Refresh retired PR-check documentation

* no-mistakes(ci): Fixed both failing CI checks. Updated inactive-reconcile setup to use the public status-marking interface, preventing false watcher exits. Made remote-job shutdown deterministic by stopping the complete worker tree before tampering. Verified both affected test suites, repeated inactive reconciliation, shell syntax, and git diff checks

* feat(bin): add trusted process-event extension bindings (#3247)

* feat(extensions): bind trusted external process-event adapters

* no-mistakes(review): Enforce owner and remote-home conformance

* no-mistakes(review): Enforce serialized remote extension package lifecycle

* no-mistakes(review): Enforce identity-conditional extension retirement

* no-mistakes(review): Serialize extension retirement and recover crash cuts

* no-mistakes(review): Unify retirement worker and lifecycle lock ownership

* no-mistakes(review): Harden extension lifecycle retirement serialization

* no-mistakes(review): Unify extension registration and overridden-state lifecycle boundaries

* no-mistakes(document): Clarify built-in-only captain answer routing

* no-mistakes(lint): Captain: fix extension binding ShellCheck findings

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes(review): Use isolated UID mapping for owner conformance

* no-mistakes(review): Captain: remove forbidden CI ownership wrapper

* no-mistakes(review): Serialize extension binding publication

* no-mistakes(review): Document ordinary CI owner-fixture exclusion

* no-mistakes(review): Quarantine orphaned handshake descendants

* no-mistakes(test): Fix orphan attribution

* no-mistakes(test): Harden process tracker baseline

* no-mistakes(test): Harden detached descendant attribution

* no-mistakes(test): Use exact invocation-group cleanup

* no-mistakes(test): Bound remote conformance transport crossings

* no-mistakes(test): Parallelize isolated extension conformance tests

* no-mistakes(test): Lifecycle suite still exceeds deadline

* feat(extensions): bind trusted external process-event adapters

* no-mistakes(review): Enforce owner and remote-home conformance

* no-mistakes(review): Enforce serialized remote extension package lifecycle

* no-mistakes(review): Enforce identity-conditional extension retirement

* no-mistakes(review): Serialize extension retirement and recover crash cuts

* no-mistakes(review): Unify retirement worker and lifecycle lock ownership

* no-mistakes(review): Harden extension lifecycle retirement serialization

* no-mistakes(review): Unify extension registration and overridden-state lifecycle boundaries

* no-mistakes(document): Clarify built-in-only captain answer routing

* no-mistakes(lint): Captain: fix extension binding ShellCheck findings

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes(review): Use isolated UID mapping for owner conformance

* no-mistakes(review): Captain: remove forbidden CI ownership wrapper

* no-mistakes(review): Serialize extension binding publication

* no-mistakes(review): Document ordinary CI owner-fixture exclusion

* no-mistakes(review): Quarantine orphaned handshake descendants

* no-mistakes(test): Fix orphan attribution

* no-mistakes(test): Harden process tracker baseline

* no-mistakes(test): Harden detached descendant attribution

* no-mistakes(test): Use exact invocation-group cleanup

* no-mistakes(test): Bound remote conformance transport crossings

* no-mistakes(test): Parallelize isolated extension conformance tests

* no-mistakes(test): Lifecycle suite still exceeds deadline

* no-mistakes(review): Split extension conformance and forward remote transfer input

* no-mistakes(review): Forward malformed remote payloads through fm-on

* no-mistakes(review): Bound extension coordinator failure cleanup

* no-mistakes(test): Skip repeated orphan sweep in coordinator children

* no-mistakes(test): Queue isolated extension sections through bounded workers

* no-mistakes(test): Bound extension coordinator lane cleanup

* no-mistakes(test): Split remote lifecycle coordinator sections

* no-mistakes(test): Coordinator probes pass; aggregate deadline remains

* no-mistakes(test): Launch extension sections concurrently

* no-mistakes(test): Fix coordinator marker publication

* no-mistakes(test): Stabilize extension binding coordinator timing

* no-mistakes(lint): Fix extension binding ShellCheck warnings

* fix(extensions): prove invocation cleanup before retirement

* no-mistakes(review): Harden process-event inbox confinement

* no-mistakes(review): Preserve legacy capture parity

* no-mistakes(review): Protect external registry staging

* no-mistakes(test): Stabilize bounded extension conformance aggregate

* no-mistakes(document): Document external evidence confinement

* no-mistakes(ci): CI phase fixed. The failure was a flaky fixture in `tests/fm-remote-transport-lanes.test.sh`: its “fresh/in-use” staging directory had no live owner identity, so the real worker correctly reaped it once the 1-second age boundary elapsed on slower CI. The fixture now records the active test shell’s exact PID/start identity and cleans those records before removal. Verified: `bash tests/fm-remote-transport-lanes.test.sh` exits 0 with all checks passing; `git diff --check` passes. Provider check retrieval was also retried successfully, resolving the selected manual CI finding. Changed file: `tests/fm-remote-transport-lanes.test.sh`

* no-mistakes(review): Harden extension staging and lifecycle reservation

* no-mistakes(review): Harden external staging and lifecycle reservations

* no-mistakes(review): Wire capture helper into remote conformance

* no-mistakes(review): Pin external capture handoff and signal failures

* no-mistakes(review): Bind pinned capture authority to inherited descriptor

* no-mistakes(review): Harden descriptor-bound capture authority

* no-mistakes(review): Harden core capture reservation authority

* no-mistakes(review): Harden capture reservation boundaries

* no-mistakes(review): Harden capture reservations and cleanup

* no-mistakes(review): Harden capture handoff and reservation cleanup

* no-mistakes(review): Bind capture handoff to claim descriptors

* no-mistakes(review): Release lifecycle locks after host crashes

* no-mistakes(review): Pin reservation recovery to recorded state roots

* no-mistakes(review): Reject control bytes in claim state roots

* no-mistakes(test): Stabilize extension capture descriptor handoff

* no-mistakes(document): Document extension capture authority boundary

* no-mistakes(lint): Fix ShellCheck extension binding warnings

* no-mistakes(ci): CI phase result: fixed `bin/fm-procevent.sh` by initializing the shared `capture_state` sentinel for built-in adapters under `set -u`. This prevents normal built-in captures from aborting before publication. Verified: `bash -n bin/fm-procevent.sh` and `git diff --check` pass. The focused process-event suite was run locally but stopped earlier at a local detached-runner claim failure (`reconcile never claimed the registered source`), before the CI-reported post-capture path; CI evidence confirms the fixed unset-variable failure affected the failing remote, board, watcher, and process-event checks

* no-mistakes(document): Correct extension namespace creation timing

* no-mistakes(lint): Initialize capture locals for ShellCheck

* fix(bin): deliver safety rules to promoted workers (#3269)

* fix(bin): deliver the real definition of done to a promoted scout, and ban --yes

A promoted scout used to receive a free-form placeholder instead of the
mode-specific Definition of done a briefed ship worker gets, so it never
saw the ask-user escalation rule or the --yes prohibition. That gap is the
concrete reason one incident's worker drove validation with --yes and
answered its own ask-user findings.

- Add bin/fm-dod-lib.sh as the single owner of a ship task's mode-specific
  Definition of done, rendered by both bin/fm-brief.sh and bin/fm-promote.sh
  so the two contracts cannot drift.
- bin/fm-promote.sh now writes data/<id>/ship-instructions.md carrying the
  scratch inventory, clean base, ship branch, and that Definition of done,
  and prints the fm-send.sh command that delivers it.
- State the --yes ban as a prohibition rather than a preference, without
  claiming an enforcement the tool does not provide.
- Cover both through the real promotion and brief paths in
  tests/fm-task-delivery.test.sh and tests/fm-brief.test.sh.

* no-mistakes(review): Publish promotion instructions before committing task state

* no-mistakes(review): Supersede conflicting scout delivery rules after promotion

* no-mistakes(review): Reject invalid promotion instruction destinations

* no-mistakes(document): Align documentation with promotion delivery contracts

* no-mistakes(ci): Fixed both CI findings. Promoted workers now receive an explicit worktree-isolation check before branch creation, with instructions to stop and escalate if they are in the primary checkout. Updated behavioral coverage to verify the delivered promotion payload, and aligned the ask-user authority test with the new fleet-wide --yes prohibition. Verified with bin/fm-lint.sh, tests/fm-brief.test.sh, tests/fm-ask-user-authority.test.sh, tests/fm-task-delivery.test.sh, and git diff --check

* no-mistakes(ci): Made tests/fm-ask-user-authority.test.sh executable so the modified colocated behavioral test runs directly like the surrounding test suite. Verified bin/fm-lint.sh, fm-brief, ask-user-authority, and task-delivery tests; all pass. git diff --check is clean

* no-mistakes(ci): Strengthened tests/fm-task-delivery.test.sh to behaviorally verify that real promotion and brief generation deliver byte-identical Definition-of-done blocks for all three modes. Verified tests/fm-task-delivery.test.sh, tests/fm-brief.test.sh, bin/fm-lint.sh, and git diff --check. The outer pipeline can now commit and attest the updated head

* no-mistakes(ci): Fixed promotion isolation instructions so any checkout other than the launched disposable worktree requires escalation, including another non-primary worktree. Updated behavioral coverage against the delivered promotion payload. Verified fm-task-delivery, fm-brief, fm-ask-user-authority, full fm-lint/ShellCheck, workflow lint, and git diff checks

* fix(bin): present Lavish feedback as structured output (#3321)

* fix(bin): present complete Lavish board feedback as structured output

Give the Lavish adapter a read-only presentation so a handler sees every
annotation and the session-ending tag=message as its own field, instead of
grepping a truncated raw capture.

* no-mistakes(review): Preserve unquoted messages and prioritize captain prose

* no-mistakes(document): Document structured Lavish result reads

* no-mistakes(ci): Fixed Lavish `read` completeness: rows missing declared fields are excluded from presented items, counted as malformed, and force `complete: no`. Added behavioral regression coverage through the adapter interface. `bin/fm-lint.sh`, syntax checks, and focused valid/malformed read checks passed. The portable-serial failure was an unrelated secondmate cooldown timing flake

* fix: keep task records and backlog transitions atomic (#3322)

* fix(records): pair backlog transitions with the record that moves

Dispatch and completion each moved a task's physical record and its
backlog row as two independently timed steps, so a crash or a forgotten
follow-up could leave the two disagreeing: a record with no in-flight
row, an in-flight row with no owner, or a finished task still shown in
flight.

Fold each backlog transition into the script that performs the physical
change, under the per-task lock it already holds and before it reports
success. Dispatch moves the item to In flight after publishing the task
record and fails loudly, removing its provisional record, when that
transition cannot land. Completion records an authoritative close and
performs it before removing the record, so an interrupted cleanup can be
finished later, and its closing message now confirms what already
happened rather than instructing a future step.

Add a same-home reconciliation sweep to session start so a home that was
interrupted mid-transition settles its own books on restart, replaying a
recorded close and restoring an in-flight row it already owns a worker
for. It never reads or writes another home; the fleet snapshot and the
cross-home nudge stay as backstops.

Close records are validated before they are trusted: the file is read as
raw bytes and rejected outright when it carries a NUL or other control
byte, every field must be well formed and non-duplicated, the id must
match the record it was found under, the data location must resolve
inside this home, and each close argument must carry a permitted,
well-formed value. Writer and reader share one validator so a record
this home publishes always remains replayable, independent of locale.

Homes configured for a manual backlog, and homes with no backlog at all,
stay exempt and are unaffected.

* no-mistakes(review): Remove stale bootstrap migration helper invocation

* no-mistakes(review): Preserve pending closes and narrow signal deferral

* no-mistakes(review): Record close before destructive teardown

* no-mistakes(review): Refuse pending closes before creating resources

* no-mistakes(review): Guard relaunches and preserve cleanup warnings

* no-mistakes(review): Reject symlinked records and clarify cleanup guidance

* no-mistakes(review): Align dispatch eligibility and protect close replay

* no-mistakes(review): Unify exact task incarnation parsing

* no-mistakes(review): Render resolved configured backlog path

* no-mistakes(review): Harden transition path boundaries against symlinks

* no-mistakes(review): Validate lifecycle state before resource actions

* no-mistakes(review): Enforce transition tooling and continuous state locks

* no-mistakes(review): Consolidate same-home lifecycle file boundaries

* no-mistakes(review): Enforce canonical lifecycle containment and tooling contracts

* no-mistakes(review): Reject final-component lifecycle record symlinks

* no-mistakes(document): Document lifecycle record path boundaries

* no-mistakes(lint): Quote literal done tokens in atomicity tests

* no-mistakes(ci): Fixed all PR-caused CI failures: bootstrap now treats an absent state directory as an empty fresh home while retaining unsafe-state checks; nested remote secondmate retirement accepts records already removed with the retired home; teardown fixtures now provide valid data/manual-backend configuration; and the manual reminder assertion checks the configured absolute backlog path. Verified the reported tests, remote lifecycle E2E, backlog atomicity suite, Bash syntax, diff checks, and ShellCheck. The documented pre-existing captain-hold failure was intentionally untouched

* no-mistakes(ci): Fixed Behavior portable serial 3 by adding `od` to the teardown test’s lsof-free PATH fixture. The new close-record validator legitimately requires `od`; its omission caused teardown to fail before process-group cleanup and stall the shard. Verified the full `tests/fm-teardown.test.sh` suite passes, plus Bash syntax, ShellCheck, and `git diff --check`

* no-mistakes(ci): Fixed close replay to durably retain incomplete-cleanup evidence before removing task metadata. Subsequent retries now emit the reconciliation warning even after a backlog probe or close failure. Updated the behavioral regression and verified the full atomicity suite under stock macOS Bash 3.2, plus shellcheck and diff checks

* fix(records): validate record bytes without an uncurated tool

The byte validation added for close records and directory paths shelled
out to od. The spawn and teardown lifecycle runs under a curated command
set that deliberately excludes it, so on any restricted PATH the check
could not run, the data directory read as unresolvable, and dispatch and
cleanup refused - wedging the lifecycle rather than protecting it.

An earlier attempt made the failing test pass by adding od to that
curated set. That fixed the test to agree with the defect and quietly
widened the contract the fixture exists to pin, so it is reverted here.

Inspect the bytes with perl instead, which is already in the curated set
and already used in this repo for the same portability reason. The
emitted values are identical to od's, so the rejection semantics are
unchanged: NUL and other control bytes are still refused, legitimate
paths containing spaces or non-ASCII characters still round-trip, and
the check stays independent of the process locale.

The restricted-PATH teardown case now passes because the validator no
longer needs od, not because the fixture was loosened.

* no-mistakes(review): Enforce dispatch eligibility and atomic remote record publication

* no-mistakes(document): Document dispatch eligibility and cleanup alerts

* fix(bin): contain promote and Relay metadata publishing (#3342)

* fix: publish promote and Relay meta rewrites through contained replace

Bare mv still rewrote live task records in place, so a symlink meta could
be followed to a target outside state/. Route those field rewrites through
the shared publisher and drop the unused library aliases.

Co-authored-by: Cursor <cursoragent@cursor.com>

* no-mistakes(review): Refuse dangling symlinks during X metadata clear

* no-mistakes(review): Refuse unsafe metadata before follow-up and promotion side effects

* no-mistakes(review): Exercise dangling symlink refusal through clear helper

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(bin): absorb turn-end wakes during bounded pane churn (#2877)

* fix(watch): absorb a turn-end whose pane churned since the previous poll

The watcher's "absorb a benign turn-end when the crew is provably working"
triage was structurally unreachable for any harness whose semantic busy state
has no verified source. crew_absorb_class only reports working for an actively
running no-mistakes step or an exact busy verdict, and bin/fm-crew-state.sh can
only answer unknown for such an adapter, so codex crewmates surfaced a signal
wake at every turn boundary with nothing to act on - a full supervisor drain,
inspect and acknowledge turn per worker turn, scaling with the number of workers
in flight and drowning the wakes that matter in identical noise.

Widen the proof rather than bound the wake rate. A wake carrying only bare
turn-ended markers is now also benign when the task's pane content changed since
the previous poll, compared against the same state/.hash-* marker the staleness
backbone already records and already trusts as liveness. That evidence claims no
harness semantics, so it fabricates no busy verdict an adapter has not earned,
and it needs no adapter cooperation.

Absorb stays evidence-driven in both directions. A wake naming any status file
keeps the strict proof, every captain-relevant verb still surfaces immediately,
and an unresolvable task, a missing prior hash, a failed or empty capture, or an
unchanged pane all surface exactly as before. The absorb defers rather than
swallows: a crew that has stopped renders nothing further, so its now-static pane
surfaces through the staleness backbone within a poll or two. Bounding the
surfacing rate instead would have suppressed genuinely stopped workers.

The derivation lives with the .hash-* marker format in bin/fm-watch.sh, which
owns it, and costs one bounded capture reached only for a no-verb turn-end whose
crew is not already provably working.

* no-mistakes(review): Captain, guard pane-churn absorption from collisions and secondmates

* no-mistakes(review): Captain, make watcher marker identities injective

* no-mistakes(review): Captain, isolate ambiguous legacy markers and restore Herdr sourcing

* no-mistakes(review): Captain, localize pane-churn collision guard

* no-mistakes(review): Captain, reject malformed pane-churn hashes

* no-mistakes(document): Document pane-churn turn-end evidence

* no-mistakes: apply CI fixes

* fix(watch): gate and bound the pane-churn turn-end absorb

Make the pane-churn form of positive work evidence opt-in per home and
bound how long it may defer one endpoint's bare turn-ends.

Absorbing a bare turn-end on pane churn is now reached only when the home
creates config/turnend-churn-absorb. The other two proofs read a verdict
the harness itself vouches for, while this one infers execution from
rendered bytes, so widening the absorb is a home's choice rather than a
default every fleet inherits. With the flag absent the predicate returns
on its first line and triage is unchanged.

Churn and pane staleness read the same pane, so neither can be the
other's only backstop. A pane that renders continuously never presents
the two consecutive identical hashes the staleness backbone needs, so an
unbounded churn absorb left a worker that had genuinely stopped behind
such a renderer with no path to surface at all. One endpoint's turn-ends
may now ride churn evidence for at most FM_TURNEND_CHURN_ABSORB_SECS,
tracked in state/.churn-since-*, after which the wake surfaces and the
window restarts. The bound is evaluated before any .stale- state is
touched, so a wake that surfaces there leaves the staleness backbone's
own classification alone.

Covers both with behavioral tests: the same churning fixture that absorbs
with the flag surfaces and queues without it, and a spent deferral window
surfaces and restarts. The four existing safety guards now run with the
flag enabled so they keep proving their specific guard.

* no-mistakes(review): Fail closed on invalid churn deferral state

* no-mistakes(review): Validate persisted churn deadlines before arithmetic

* no-mistakes(review): Make churn deadlines transactional and bounds safe

* no-mistakes(review): Compose turn-end evidence per task from one snapshot

* no-mistakes(review): Restore strict turn-end fallback guards

* no-mistakes(document): Clarify pane-churn supervision documentation

* no-mistakes(lint): Fix watcher arithmetic lint issues

* no-mistakes: apply CI fixes

* no-mistakes(document): Clar…
…n-upstream-sync

# Conflicts:
#	.agents/skills/bootstrap-diagnostics/SKILL.md
#	.pi/extensions/fm-branch-supervision.ts
#	AGENTS.md
#	bin/fm-backlog-transition-lib.sh
#	bin/fm-bootstrap.sh
#	bin/fm-branch-prompt.sh
#	bin/fm-classify-lib.sh
#	bin/fm-dod-lib.sh
#	bin/fm-inactive-reconcile.sh
#	bin/fm-promote.sh
#	bin/fm-spawn.sh
#	bin/fm-teardown.sh
#	docs/architecture.md
#	docs/configuration.md
#	docs/pi-supervision-branch.md
#	docs/scripts.md
#	tests/fixtures.sh
#	tests/fm-backlog-atomicity.test.sh
#	tests/fm-branch-supervision.test.sh
#	tests/fm-brief.test.sh
#	tests/fm-pi-branch-extension.test.sh
#	tests/fm-public-followup.test.sh
#	tests/fm-remote-transport-lanes.test.sh
#	tests/fm-task-delivery.test.sh
…PC (#4)

* fix(bin): verify the real GitHub merge outcome instead of reporting an unproved merge (#3064)

* fix(pr): verify GitHub merge outcome

* no-mistakes(review): Captain, fixed forge-only merge verification, queue guidance, metadata propagation

* no-mistakes(document): Correct forge-specific merge documentation

* no-mistakes(review): Captain: forge-only queue fix, focused tests pass

* no-mistakes(review): Captain: suppress closed-state guidance and prove parent regression

* no-mistakes(review): Captain: remove history proof; retain executable regressions

* no-mistakes(document): Clarify GitHub recording timing in architecture docs

* no-mistakes(document): Clarify outcome-aware PR merge recording documentation

* no-mistakes: apply CI fixes

* Revert "no-mistakes: apply CI fixes"

This reverts commit c326cfa9430c6173eedc8ff7f27d19d0552daf01.

The automatic CI repair round removed the up-front `gh` prerequisite check
while keeping the `gh` dependency: `bin/fm-pr-merge.sh` still calls
`gh api graphql` for the outcome read and `gh api` for the branch-rules read.
That left the same hard requirement without the clear named error, and review
immediately raised a new finding for exactly the failure the check prevents -
`gh-axi pr merge` landing the merge while the follow-up read fails, so the PR
metadata is never recorded.

The check is also symmetric with the GitLab arm directly above it, which
already refuses up front when `glab` or `jq` is missing, on the stated
principle that a missing tool should be a named prerequisite rather than a
merge that is armed and then refused for an unexplained reason.

The workflows this round was chasing sit at `action_required` because this is
a fork pull request; no code change can turn them green.

* fix(pr): keep PR bookkeeping when a merge outcome read fails

On the GitHub path a merge call that returned success was followed by
`github_read_outcome || exit 1`, so a transient API failure, rate limit,
or network blip during the read dropped out of the script before
`record_pr_metadata` ever ran. The merge could have landed while `pr=`
went unrecorded and the merge poll was never armed - bookkeeping lost on
a real merge. The failure path just above already recorded metadata
before exiting, so the error path was more careful than the success one.

Record the PR before that refusal. Recording arms the later merge poll
and is not a success claim, which is the same reasoning that keeps
`record_pr_metadata` on the gh-axi failure path. The refusal itself is
unchanged: exit stays non-zero and the message still names the concrete
observed state. Metadata is withheld only when the read succeeds and
proves the pull request neither merged nor queued.

Pin it with a case that stubs `gh api graphql` into failure after a
successful `gh-axi pr merge`, asserting both the non-zero exit and the
recorded metadata.

* no-mistakes(review): Aggregate queue rules and report conflicts explicitly

* fix(pr): keep the merge abstraction reachable and its bookkeeping intact

Two holes remained in the outcome-verified GitHub merge path, both on
installations where gh-axi is present but gh is not.

The verification preflight refused before bin/fm-pr-merge.sh ever reached
the configured gh-axi merge abstraction, so an installation without gh
could no longer merge at all. gh-axi now performs the merge unconditionally
and the queue-aware gh read became an optional enrichment: with gh on PATH
its GraphQL view still separates merged from queued, and without gh the
gh-axi view still proves a landed merge while every outcome it cannot prove
refuses.

The PR metadata recording sat behind the outcome read, so a merge that
landed before that read failed lost pr= and its merge poll. Recording now
happens once, before either forge call, which arms the poll without
claiming a landed outcome and leaves teardown a PR identity to verify
against no matter how the read ends.

Rebasing onto main also restored the durable merge-outcome reporting and
the GitLab landed-state confirmation that the conflict resolution dropped.

Tests pin each fix through the executable interface: the merge abstraction
is reached and verified with gh absent, a failed fallback read keeps its
bookkeeping, and a mock that snapshots the task meta during the forge call
proves pr= is recorded before the merge can land.

* no-mistakes(review): fix(pr): de-dup queue methods, fall back on failed gh read, refresh contracts

* no-mistakes(review): fix(pr): quote forge output and explain armed auto-merge on refusal

* no-mistakes(review): fix(pr): claim auto-merge armed only when the forge accepted it

* no-mistakes(review): fix(pr): tell the operator what each GitHub refusal could not observe

* no-mistakes(review): fix(pr): gate every forge-acceptance claim on a successful merge

* no-mistakes(document): align merge docs with verified GitHub outcome contract

* fix(pi): prevent duplicate captain outcome reports (#3184)

* fix(pi): stop reporting one merge to the captain twice

The supervision branch's captain-outcome note told main, unconditionally,
that the note "is not your own earlier output" and to relay it now. When
main had already reported the same event, that assertion was false and the
order turned the correct response - saying nothing new - into a mechanical
re-report, so the captain saw one merge reported twice in 16 seconds.

Two independent changes, both needed:

- The relay instruction is now conditional. It still names itself as a
  supervision outcome so main cannot mistake it for its own earlier answer
  (the silent loss that instruction exists to prevent), and it now lets
  main stay quiet about an outcome it has already given the captain.

- The merge case is closed at its source rather than left to that judgment.
  One merge reaches a home on two independent paths by design - main's own
  permanently main-owned merge poll, and the branch's task-local status
  wake - and main's captain-facing text only reaches the branch's mirror at
  main's turn end, so the branch can escalate before it could possibly see
  the captain was already told. bin/fm-pr-merge-notified.sh answers that
  question from bin/fm-pr-lib.sh's canonical merge-notification marker, so
  the answer holds regardless of mirror timing. A captain outcome naming an
  already-published merge is delivered as the ordinary rendered note
  instead of opening a follow-up turn: still appended, still visible, still
  recorded with the verdict the branch decided, minus the wasted turn.

Any error, timeout, or unreadable state relays the outcome. A duplicate
announces itself; a lost outcome does not.

Regression coverage drives the real delivery path in both directions: a new
outcome must still reach the captain in exactly one follow-up turn even
beside an unrelated published merge, and an already-published merge must
open no second turn while a different PR in the same task still does. The
merge path's real producer and this new consumer are exercised end to end
in tests/fm-pr-merge.test.sh.

Pi-only by construction: the delivery path lives in .pi/extensions, so no
other harness loads it, and the new script only reads existing markers.

* no-mistakes(review): Document accepted latest-marker suppression residual

* no-mistakes(review): Recheck ownership before merge outcome delivery

* no-mistakes(document): Document merge-outcome suppression exception

* refactor(pi): drop the source-level merge suppression, keep the envelope fix

The captain reviewed this branch and judged the source-level duplicate
suppression overly complicated for the problem it solved, and asked for
the change to be reduced to the envelope wording alone.

Remove the mergeIntoMain downgrade path, bin/fm-pr-merge-notified.sh, and
every test and document that existed only for it. What remains is the
conditional captain-outcome instruction: main is told to stay quiet about
an outcome it has already reported and to relay anything else, which
covers the duplicate without a second mechanism.

The silent-loss protection is untouched - the note is still typed,
self-describing, and delivered as one invisible follow-up turn - and the
behavioral tests still assert that, now requiring both halves of the
conditional instruction.

* no-mistakes(ci): Clarified in code comments and owned documentation that this is intentionally an M1-only, model-facing conditional relay fix—not source-level suppression—addressing Greptile’s mistaken scope expectation without changing runtime behavior. Net diff remains 3 files and 27 insertions. Verified with fm-pi-branch-extension tests, fm-lint, doc audience check, and git diff --check; all passed

* no-mistakes(ci): Strengthened the runtime delivery test to verify the captain outcome retains its required self-description and outcome text. Verified with `bash tests/fm-pi-branch-extension.test.sh`, `bin/fm-lint.sh`, `bin/fm-doc-audience-check.sh`, and `git diff --check`; all passed. The outer pipeline can now commit and attest the new head

* fix(bin): prioritize active pipeline-owned crew runs (#3194)

* fix(bin): bind the live pipeline-owned run instead of a superseded failed row

fm-crew-state.sh bound a superseded FAILED no-mistakes run to a task instead
of the LIVE replacement run: the live run's pipeline-owned lane head is not a
git object in the task worktree, so head-equality attribution rejected it and
the coarse runs-list fallback silently continued past the RUNNING row onto an
older failed row whose head equalled the stale worktree HEAD. The home summary
then flipped invalid and Bearings hid the home's live work (F10).

Attribution precedence now follows the daemon's own identity:
- An ACTIVE run for the task's branch binds without head equality while
  branch_sync.state is pipeline_owned (fm_nm_run_is_pipeline_owned_active);
  the pipeline owning the branch is itself the attribution.
- A genuinely failed run with no later run on the branch still reports failed
  through the unchanged head-equality path - real failures are not hidden.
- In the coarse runs scan, an unresolvable head is unknown attribution and
  stops the scan (fm_nm_head_resolvable) instead of falling through to an
  older row; a resolvable-but-mismatched head keeps the historical
  reused-branch skip.

The exemption never applies to a terminal run and requires pipeline_owned
specifically, both pinned by negative-control tests. Fixture shape verified
against the live incident run's real axi status output.

* no-mistakes(document): Updated run-attribution documentation ownership

* fix(pi): surface requested outcomes without replaying fleet events (#3211)

* fix(pi): surface requested supervision outcomes

* no-mistakes(review): Mirror in-flight captain requests before branch dispatch

* no-mistakes(review): Exercise real branch ownership and main outcome access

* no-mistakes(review): Preserve request tails and align verdict guidance

* no-mistakes(review): Preserve complete current captain requests

* no-mistakes(review): Require visible requested outcomes and realistic classification

* no-mistakes(document): Align supervision outcome documentation

* no-mistakes(ci): Fixed Greptile’s runtime-ordering finding. The extension now stages Pi’s authoritative `before_agent_start` prompt before SessionManager persistence and suppresses the later duplicate entry. Updated docs and behavioral regression to reproduce real Pi ordering and verify each prompt is mirrored exactly once. Passed branch-extension tests, supervision tests, strict Pi typecheck, full lint, and diff checks

* no-mistakes(review): Use canonical operational input classification

* no-mistakes(review): Filter legacy operational inputs canonically

* no-mistakes(document): Clarify captain request mirroring boundary

* no-mistakes(ci): Fixed the CI time-boundary failure in tests/fm-public-followup.test.sh by pinning its clock, including context-registry setup. This prevents follow-up fixtures from expiring based on wall time. Verified the full regression suite passes, project-owned lint passes, and git diff checks are clean

* no-mistakes(document): Clarify captain-visible supervision outcome documentation

* feat(bin): add concurrent bounded remote transport lanes (#3210)

* feat(bin): per-home remote transport lanes with cancellation, bounded send, and closed stdin

All remote commands for every home on one host used to serialize through one
single-job-at-a-time worker on one shared queue: a timed-out caller abandoned a
staged job that kept running, retries convoyed behind it, fm-send's remote leg
had no time bound, and staging captured the caller's stdin to EOF so any
fm-on.sh caller with an open stdin wedged staging indefinitely.

- The worker now serves one lane per staged home: same-home jobs run strictly
  FIFO in a new staging-sequence order while different homes run concurrently,
  each lane as its own top-level worker process (a backgrounded subshell does
  not reliably reap dead children, so a zombie group leader kept a finished
  command's process group signalable). Long-poll preemption is lane-scoped.
- A caller that disconnects or times out cancels its job: the entrypoint marks
  the record on any post-staging exit and probes its parent so a dead ssh
  channel cancels without a signal; the worker skips cancelled queued jobs,
  terminates a running cancelled job's process group, and reaps the record.
- fm-send's remote leg is bounded by FM_SEND_REMOTE_BUDGET (default 30s) and a
  bound hit exits through the existing unconfirmed-delivery contract, which
  stays idempotent because the remote enqueue deduplicates.
- fm-on.sh defaults the remote command's stdin to /dev/null; the three payload
  callers pass the new --stdin flag. Abandoned .stage.* litter is age-reaped.
- The job execution deadline no longer loses up to a second to clock
  truncation.

* no-mistakes(review): Protect live stages and validate send budgets early

* no-mistakes(review): Preserve sequence lock ownership during stale recovery

* no-mistakes(review): Allocate job sequences at publication boundary

* no-mistakes(review): Bound remote keys and extend stale lock recovery

* no-mistakes(document): Document bounded remote transport behavior

* no-mistakes(lint): Suppress intentional deferred-expansion lint warning

* no-mistakes(ci): Fixed stale sequence-lock recovery by reconciling the counter against published job records before allocating the next sequence, preventing duplicate sequences and same-home FIFO violations. Added a behavioral regression test reproducing displacement after publication and verifying execution order. Passed fm-remote-transport-lanes.test.sh, fm-remote-job.test.sh, fm-lint.sh, and git diff --check

* no-mistakes(review): Use atomic sequence claims and lossless lane keys

* no-mistakes(review): Recover regressed sequence hints and rate-limit claim reaping

* no-mistakes(review): Restrict worker heartbeats to serving loop

* no-mistakes(review): Verify supervisor identity before lane recovery signals

* no-mistakes(review): Verify tracked lane and claim owner identities

* no-mistakes(document): Clarify remote lane and transport contracts

* no-mistakes(ci): Fixed the CI time-boundary failure by pinning fm-public-followup tests to a deterministic clock, including context-registry setup. Verified tests/fm-public-followup.test.sh, tests/fm-remote-transport-lanes.test.sh, shellcheck, and git diff --check

* no-mistakes(review): Preserve assigned lane ownership of queued jobs

* no-mistakes(review): Reserve homes owned by foreign queued lanes

* no-mistakes(review): Preserve completed results during crash recovery

* no-mistakes(review): Harden claim cleanup, expiry, and cancellation races

* no-mistakes(review): Verify process groups and reap abandoned results

* no-mistakes(review): Stop leaderless groups and reap cancelled publications

* no-mistakes(document): Correct remote transport lifecycle documentation

* no-mistakes(lint): Quote done state comparisons for ShellCheck

* fix(bin): accelerate and bound changed test runs (#3250)

* fix(tests): make the changed-file map select per script and stabilize a budget flake

The changed-file map's bin/ fallback resolved a direct test reference to that
test's whole FAMILY. bin/fm-push-transition-lib.sh is named by exactly one
real-Herdr E2E, so a one-line change to it selected all 12 real-herdr-gated
scripts, including a 341s presentation E2E with no dependency on it.

Resolve direct test references per script, and keep resolving consumer bin/
scripts through the curated map so recorded family-level coupling survives.

Also fix a load-sensitive flake: the tool-update budget deadline is whole-second
granular, so a test budget of 1 left headroom anywhere in (0, 1] seconds and the
first budget check could already read as exhausted.

* feat(bin): make suite wall clock a result and let a family's concurrency be proven

--max-wall-ms fails a run whose wall clock exceeds the caller's budget, after
reporting the per-script results. A suite that stays green while outgrowing its
caller's invocation budget is the regression that got an agent killed mid-run
and retried invisibly, so duration has to be a result rather than a log note.

--pool on the isolation-proof harness runs the same concurrent proof over a
whole family, so 'is this family safe to parallelize?' is answered by a command
instead of a guess. Measured watcher-wake-lock and refused it: 3 of 18 scripts
fail under concurrency on wall-clock assertions about reaching the next poll.

* perf(bin): schedule the changed suite concurrently, longest first

The watcher-wake-lock family is proven concurrent-safe (two clean runs, 18
candidates, 0 failures at 4 workers; docs/fm-test-isolation-proof.md), so
--changed now schedules its proven-concurrent scripts with bounded parallelism
and runs any unproven remainder serially afterwards, never beside them.

Concurrent runs are ordered longest-hint-first. Workers are handed scripts in
order, so alphabetical order started the 193s fm-watch-triage last and stranded
it running alone: 395s wall against a 205s balanced four-worker sum.

An explicit --jobs keeps its strict refusal, so every CI lane is unchanged.

* fix(bin): bound a hung test instead of letting it hang the suite

tests/fm-calm-pi-extension.test.sh was observed running 17+ minutes against a
464ms recorded hint, and the suite had no per-script bound to stop it. An
unbounded suite is precisely what silently outruns a caller's invocation budget,
and --max-wall-ms is evaluated after the run so it cannot end one that never
finishes.

--per-script-timeout-secs terminates a script that outruns it and records exit
124, so the run still completes, accounts for the script, and fails. The
auto-concurrent --changed path applies 900s, far above the slowest real script
(the 341s Herdr presentation E2E), so it only ever converts a hang.

* no-mistakes(review): Enforce safe concurrency and descendant timeouts

* no-mistakes(review): Validate empty runs and isolation proof pools

* no-mistakes(review): Measure selection time in wall budget

* no-mistakes(review): Reap interrupted workers and bound finalization

* no-mistakes(review): Contain shutdown descendants and watchdog finalization

* no-mistakes(review): Honor remaining budget and close launch races

* no-mistakes(review): Restore timeout helper and simplify runner cleanup

* no-mistakes(review): Record isolation pool admission metadata

* no-mistakes(review): Bound Chrome reap and scope proof admission

* no-mistakes(review): Align proof scheduling and preserve budget summaries

* no-mistakes(review): Remove unreliable finalization watchdog

* no-mistakes(review): Freeze budget duration and enforce admission caps

* no-mistakes(document): Refresh test runner concurrency documentation

* no-mistakes(lint): Fix ShellCheck findings in test runner scripts

* no-mistakes(ci): Fixed Greptile’s concurrency-consent finding. `--changed` now remains serial by default; `--changed --jobs auto` explicitly opts into bounded concurrency and the automatic hang timeout. Updated documentation and added behavioral coverage proving serial default behavior, explicit concurrent scheduling, and refusal of `--jobs auto` outside `--changed`. Verified with `bash tests/fm-test-run.test.sh`, `bin/fm-lint.sh`, and `git diff --check`

* no-mistakes(review): Restore automatic changed-suite concurrency and timeout

* no-mistakes(review): Correct changed-suite contributor guidance

* no-mistakes(review): Reject gate-skipped isolation proofs

* no-mistakes(review): Correct automatic concurrency evidence

* no-mistakes(review): Isolate nested runner process groups

* no-mistakes(review): Remove unreliable signal cleanup machinery

* no-mistakes(test): Narrow changed-suite selection to executable contract owners

* no-mistakes(document): Document isolation proof skip and artifact semantics

* no-mistakes(ci): Fixed Greptile’s concurrency-consent finding. `--changed` now remains serial by default; bounded concurrency requires explicit `--jobs auto`. Updated behavioral coverage, contributor guidance, and isolation-proof commands accordingly. Verified with `tests/fm-test-run.test.sh`, `bin/fm-doc-audience-check.sh`, `bin/fm-lint.sh`, Bash syntax checks, and `git diff --check`; all passed

* no-mistakes(review): Restore plain changed-suite automatic concurrency

* no-mistakes(review): Record resolved changed-suite worker count

* fix(bin): keep a runner change selecting its whole curated family

A pipeline fix round narrowed the curated changed-file map so bin/fm-test-run.sh
and bin/fm-test-isolation-proof.sh selected only their own two contract tests,
and the documentation surfaces only the audience test. That cut this branch's
own changed selection from 33 scripts to 5.

The runner executes every pure-contract-unit script, so its contract test
passing proves its logic is right, not that the suite it drives still runs.
Narrowing it also makes any wall-clock claim about the changed suite trivially
true by not running the work.

Only the unmapped bin/* grep fallback resolves per script; curated mappings keep
their recorded family coupling.

* perf(bin): admit the pure-contract-unit family to bounded concurrency

A runner-file change selects pure-contract-unit, so that family decides the
changed suite's wall clock. With only watcher-wake-lock admitted, 14 of its 33
selected scripts fell to the serial tail and the selection measured 327.3s
against a 300s budget: the concurrent group was 19 scripts totalling 273.4s
while the tail alone was 215.7s.

bin/fm-test-isolation-proof.sh --pool pure-contract-unit --jobs 4 passes twice,
32 candidates, 0 failures, so the family is admitted on recorded evidence.

Full 33-script plain --changed: 327.3s -> 181.8s / 178.5s / 172.7s, 0 failures,
inside a 300000ms budget. Also states the per-script guard's derivation.

* no-mistakes(review): Align contract-unit concurrency cap with recorded proof

* no-mistakes(document): Record final changed-suite performance evidence

* fix(bin): keep an empty changed selection clean on stock macOS Bash

Under set -u, bash 3.2 treats "${arr[@]}" on an EMPTY array as an
unbound-variable error, while bash 4.4+ makes it a harmless no-op. The
concurrency work removed the early exit for an empty selection, so execution
fell through to the unguarded existence loop: on stock /bin/bash 3.2.57 a
contributor who changes only documentation and runs --changed got

  bin/fm-test-run.sh: line 1713: SCRIPTS[@]: unbound variable

with exit 1 and no summary, instead of a clean total=0 pass.

Restore the early exit, and guard every remaining array expansion reachable
with an empty selection. The reported duration is real elapsed invocation
time rather than a hardcoded zero, so a selection phase that outran
--max-wall-ms still fails.

Verified on this host with /bin/bash 3.2.57: exit 1 with the unbound-variable
error before, exit 0 with FM_TEST_SUMMARY total=0 after.

* no-mistakes(document): Document shell-bound changed-suite performance

---------

Co-authored-by: Kun Chen <kun-1@kunchenguid.com>

* feat(bin): publish per-home summary ledgers (#3222)

* feat(bin): publish per-home summary ledger

* no-mistakes(review): Bound and schedule home summary publication

* no-mistakes(review): Prove recurring watcher summary refresh cadence

* no-mistakes(review): Bound refresh workers and publish durable spawns

* no-mistakes(review): Fix atomic kill process-group coverage

* no-mistakes(review): Bound state initialization within refresh timeout

* no-mistakes(document): Document recurring bounded home-summary publication

* no-mistakes(review): Bound and log all best-effort refresh failures

* no-mistakes(review): Harden cadence and timeout regression coverage

* no-mistakes(document): Document home-summary runtime tuning

* no-mistakes(lint): Fix direct exit-code check in refresh test

* no-mistakes(ci): Fixed remote secondmate retirement recreating the deleted home: teardown now skips side-band summary refresh when its overridden state directory was removed. Verified with remote lifecycle E2E, teardown tests, home-summary tests, ShellCheck, and git diff checks

* no-mistakes(document): Clarify atomic home-summary publication guarantee

* fix(pi): gate first provider call on startup context (#3158)

* fix(pi): gate first call on startup context

* no-mistakes(document): Correct Pi startup prerequisite verification date

* no-mistakes(review): Captain, fix startup process-group retirement after leader exit

* no-mistakes(review): Captain, release reload exit listeners on shutdown

* no-mistakes(review): Captain, complete startup exit lifecycle ownership

* no-mistakes(review): Captain, release empty startup process-group ownership promptly

* no-mistakes(review): Captain, supervise startup ownership and restore failure fallback

* no-mistakes(review): Captain, restore live Pi supervisor execution

* no-mistakes(document): docs: clarify Pi startup prerequisite delivery

* fix(pi): restore Pi 0.84.4 renderer compatibility (#3261)

* fix(pi): restore 0.84.4 adapter compatibility

* no-mistakes(review): Restore Pi collapsed and expanded outcome parity

* no-mistakes(review): Preserve Pi stock previews through capability probing

* no-mistakes(document): Document Pi 0.84.4 renderer compatibility

* fix(bin): keep home-summary publication from starving supervision (#3273)

* fix(bin): keep home-summary publication bounded and off the watcher beat

A home whose tasks had accumulated ordinary status history could not publish
state/home-summary.json at all, and every attempt starved the watcher's
liveness beacon while it failed silently.

The producer's per-task open-decision fold spent tens of milliseconds per
status line on a bash 3.2 global bracket-class substitution used only as a
blank-line guard. On a real home that made the whole ledger producer take
minutes, so publication burned its full FM_HOME_SUMMARY_TIMEOUT on every
attempt and never completed. Replace that guard with an equivalent case glob
in the one fold owner, which both the whole-file and cursor-backed folds use.

Bound each per-task current-state read in the snapshot with
FM_SNAPSHOT_CREW_STATE_TIMEOUT. For a remote secondmate that read crosses ssh,
whose dead-peer detection deliberately never kills a slow-but-alive remote
command, so nothing else bounded it.

Detach the watcher's two publication triggers from the poll loop. The loop
owns the beacon that fm-guard.sh reads as proof supervision is alive, and an
inline publication put up to a full publication deadline between two beacon
touches. A single in-flight publication is tracked so a slow one cannot
accumulate clones.

Report a repeatedly failing publication at session start. Publication stays
deliberately non-fatal to its caller, so the existing bounded home-local
failure record is now surfaced as a HOME_SUMMARY bootstrap line once the
ledger is absent or stale and failures have been recorded since.

* no-mistakes(review): Preserve home-summary failure attempt ordering

* no-mistakes(review): Enforce durable home-summary single-flight and ordering

* no-mistakes(review): Derive failure ordering from publication boundaries

* no-mistakes(review): Restore best-effort failure logging and publication scoping

* no-mistakes(review): Make ordering regression sensitive to one failure

* no-mistakes(document): Correct HOME_SUMMARY diagnostic guidance

* fix(bin): prevent routine updates from hiding actionable status (#3268)

* fix(supervision): classify the appended status span, not the last line

An actionable project update could be classified as routine and absorbed, so
a worker that raised a decision, hit a blocker, failed, or finished stalled
silently with the captain never told.

Trigger, mask, symptom. A worker appends a captain-relevant event
(`needs-decision`, `blocked`, `failed`, `done`). Any later routine append -
a `working:` progress note - lands before the supervisor classifies the
batch; the watcher's 30s signal-grace linger exists precisely to coalesce a
status write with the same turn's turn-end, so this window is ordinary
rather than rare. Both supervisors then asked "is the LAST line
captain-relevant?", read the routine line, and absorbed the wake. The
`.seen-*` suppressor advanced either way, so nothing ever re-read the event.
When the crew was also provably working, the no-verb fallback absorbed it
too, which is why the event disappeared completely instead of surfacing late.

Reproduced end to end against a real watcher before any change: with the
trailing `working:` append the watcher never exits and the wake queue stays
empty; with that one line removed - the smallest counterfactual - the same
`needs-decision` surfaces and queues. The away-mode daemon's `classify_signal`
returns `self|routine signal` for a `blocked:` event under the same mask,
which is the worse case because no captain is present to notice.

The proven path was already in the tree: `status_open_decisions` fixed this
exact masking for the durable decision fold, and its header states the rule -
reading an append-only event log last-event-wins cannot represent an earlier
event that a later unrelated line moved past. The classification path was
never migrated to that read model. That is the earliest divergence, and the
fix is to migrate it rather than to special-case the symptom.

`status_span_first_actionable` in bin/fm-classify-lib.sh is the new single
owner: it reads the bytes at or after a caller-supplied position and returns
the first still-live captain-relevant event. Each supervisor supplies its own
position, because the always-on watcher and the away-mode daemon classify the
same stream independently and must not share one cursor: the watcher reads
the size already recorded in its `.seen-*` signature (no new state) and its
`.hb-surfaced-<task>` backstop marker, and the daemon its
`.subsuper-seen-status-<task>` marker. Those two markers held the escalated
line and now hold the escalated-through byte offset, which also removes a
second defect in the same code - content dedup silently swallowed a genuinely
new event whose text repeated an older one. An absent, malformed, or
past-the-end position reads the whole log, so uncertainty surfaces events
rather than losing them, and a marker an older build wrote as a status line
reads that way too. Status logs are only ever appended to, including across a
reused task id, so a recorded position keeps its meaning.

A `needs-decision`/`blocked` event in the span is retired only when the
whole-file fold proves its key closed; `status_open_decisions` stays the sole
owner of that rule, so same-key reopening and reserved-key namespaces need no
second implementation here. Every other captain-relevant event is terminal
and always actionable.

Both backstops now walk every status log instead of only those whose last
line looks captain-relevant, because the event a backstop most needs to catch
is exactly one a later append has moved past. That leaves
`scan_captain_relevant_statuses` with no callers, and it is removed rather
than left as a working copy of the defective read model.

Regression coverage exercises the classifier and both supervisors through
their own interfaces: the masked decision, the captain-reported
release/install completion followed by cleanup chatter, and the away-mode
blocker all surface; a routine append after an already-classified event stays
absorbed, so the fix does not convert ordinary progress into wakes; and the
heartbeat backstop catches a masked event the per-wake path missed. The
end-to-end watcher tests drive a real fm-watch.sh with the crew reported as
provably working, which is the configuration that made the original stall
silent.

Two further claims in the supplied RCA are deliberately not patched here.
"Repeated operational recoveries produced all-clear replies despite known
actions" is downstream of this same cause, not an independent contributor: an
all-clear reply is the documented response when the specific event needs no
action, so a classification that wrongly reported "no action" produces it, and
correcting the classification removes it. "The project was subjected to
validation requirements outside its accepted path" is delivery-mode selection,
which AGENTS.md section 7 owns; no code changed here touches it, so it is out
of scope.

Harness and backend axes were inspected rather than assumed: nothing in this
path reads a vendor-emitted signal. The status log's format and append
protocol are Firstmate's own and identical for every harness, and no runtime
backend reads or writes `.status` files (`bin/backends/*` contain no reference
to them). The surrounding triage's only backend touchpoints - pane capture and
the authoritative crew-state read - are unchanged. No live-harness guard
applies and no per-harness verification record changes.

Verified with `bin/fm-lint.sh`, `bin/fm-doc-audience-check.sh`, and
`bin/fm-test-run.sh --changed --base origin/main`.

* no-mistakes(review): Prevent status races and surface classification failures

* no-mistakes(review): Surface unreadable signals and preserve AFK endpoints

* no-mistakes(review): Route stale wakes through captured span verdicts

* no-mistakes(review): Retire supervision offsets with reused task state

* no-mistakes(review): Bind status offsets and preserve live decision origins

* no-mistakes(review): Strengthen status identity with verified birth time

* no-mistakes(review): Skip turn-end markers during status classification

* no-mistakes(review): Preserve status presentation with platform-strength identities

* no-mistakes(review): Retain failed wakes and advance routine checkpoints

* no-mistakes(review): Surface all events and retain unreadable wakes

* no-mistakes(review): Treat absent status logs as successful empty spans

* no-mistakes(review): Bound repeated classification failures with durable receipts

* revert(supervision): drop the failure-receipt and durable-retry machinery

Captain-authorized revert to the minimal fix. Review rounds added a durable
failure-receipt store and wake-retention-on-failure to bound repeated
classification failures. That machinery grew larger than the fix it protected
and kept producing its own defects: an unreadable log still looped forever
because the always-on watcher never consulted the receipt, and the receipt was
persisted before its diagnostic was durably queued, so a crash in between
swallowed the alarm outright. Those two defects go away with the code that
contained them rather than being repaired.

Removed: the failure-receipt path, fingerprint, record and clear helpers and
their retirement bookkeeping; the retention of a durable wake when
classification fails; and the error-propagation plumbing in both supervisors
that existed only to drive them.

Kept, because it is the accepted fix rather than the declined machinery: span
classification of the events appended since a supervisor last looked, in both
supervisors and both backstops; reporting every actionable event in a span and
committing a position only through what was reported; naming the live opening of
a reopened decision; treating an absent log as ordinary and an unreadable one as
worth reporting; the non-.status filter; and the platform-strength identity that
guards a position commit without failing a read.

Replacement behavior for a log that cannot be classified: report it once, do NOT
advance the classification position so the content is classified from where it
stopped once readable, and DO advance the wake signature so the report is
bounded to one per distinct file state. Reporting and reading are different acts:
telling the captain about a log is not the same as having read it, and only the
latter may move a classification position.

The residual risk is explicit and accepted: there is no guaranteed automatic
retry inside a crash-mid-read window, and the locked session-start replay of the
durable queue covers it. That rationale is recorded at mark_escalated_seen so a
future reader does not reintroduce the retry as a "missing" guarantee.

Also fixes lint failures that arrived with the review-fix commits and were never
caught because the run never reached its lint step: an unfollowable conditional
source directive, a second unquoted-expansion site left after a call was split
across lines, cleanup of the file being read inside its own read loop (restructured
to one post-loop teardown rather than three in-loop copies), stub functions in
tests that are invoked indirectly, and a test local left unused when its
assignment was replaced by a helper. bin/fm-lint.sh passes on the default branch,
so these were introduced here.

Verified with `bin/fm-lint.sh`, the end-to-end masked-decision and away-mode
reproductions, and `bin/fm-test-run.sh` over the supervision, wake-queue,
wake-drain, watch-arm and inactive-reconcile suites (6 scripts, 0 failures).

* no-mistakes(review): Correct classification failure contract documentation

* no-mistakes(review): Bound unreadable status reports without skipping classification

* no-mistakes(review): Preserve escalation markers when buffering fails

* no-mistakes(review): Detect permission recovery without advancing classification

* no-mistakes(document): Document status span classification contract

* no-mistakes(ci): Fixed CI failures by lazily loading classification helpers in fm-wake-lib, preserving minimal recovery/remote fixtures; added a public current-status marker helper and updated behavioral fixtures to use the v2 marker contract; resolved ShellCheck variable collisions in fm-control and fm-public-followup-lib. Verified fm-lint, bash syntax, fm-control, public-followup, wake-queue, send-resolve-key, captain-hold, pending-reply, remote-reply, remote-backlog-handoff, turnend-guard, and Claude autoarm tests. The Pi branch suite reached a separate local stock-render mismatch under Node 24; its CI-reported missing-classifier failure path is fixed

* no-mistakes(review): Escalate blockers while preserving declared-wait cadence

* no-mistakes(review): Clarify actionable events override wait self-handling

* no-mistakes(review): Surface rejected decisions and dangling status links

* no-mistakes(document): Document reserved-key reconciliation classification

* no-mistakes(ci): Fixed the flaky portable serial CI test by modeling the retained staging directory as genuinely owned by a live process and aging both fixtures deterministically. This removes scheduler-timing dependence while verifying the worker reaps abandoned staging and preserves live staging. Verified with fm-remote-transport-lanes.test.sh, bin/fm-lint.sh, bash syntax, and git diff --check

* no-mistakes(document): Correct away-mode classification documentation

* docs(skills): split harness adapter operations reference (#3289)

* docs: split harness adapter operations reference

* no-mistakes(review): Fix harness adapter routing and ownership contracts

* no-mistakes(review): Prune duplicate harness adapter ownership prose

* no-mistakes(review): Fix default effort routing and Grok max semantics

* no-mistakes(review): Remove source-only routing test and duplicate semantics

* no-mistakes(review): Add local harness adapter instruction evaluation

* no-mistakes(review): Fix harness evaluation gating and change mapping

* no-mistakes(test): Captain, require explicit harness instruction evaluator model

* no-mistakes(document): Fix harness adapter documentation references

* test: centralize shared shell fixtures (#3296)

* test(fixtures): share fake-toolchain and spawn-world builders

Future tests can start from tests/fixtures.sh instead of copying stubs, and a
no-mistakes version-floor bump is one constant rather than a multi-file edit.

Migrated this round: fm-busy-adapter-wiring, fm-spawn-pool-base-freshen,
fm-grok-harness, fm-tangle-guard, fm-gate-refuse, fm-spawn-dispatch-profile.
Left for opportunistic migration: remaining make_spawn_fakebin copies
(trace-context, kimi, muse, backend), the make_stubs send cluster, and the
fake no-mistakes version banners in bootstrap/session-start/secondmate suites.
Did not touch tests/fm-pr-check-security.test.sh.

* no-mistakes(review): Prevent fake SSH test from blocking on stdin

* no-mistakes(document): Clarify shared fixture documentation

* no-mistakes(ci): Fixed the flaky watcher triage test by extending its startup-sensitive timer-repair wait from 3s to 10s, matching existing loaded-runner budgets. Verified with the full tests/fm-watch-triage.test.sh suite, bash syntax validation, and git diff checks

* no-mistakes(ci): Fixed portable serial shard 4 by updating the inactive-reconcile fixture to prime status through the public fm_wake_status_mark_current API, ensuring classifier helpers load correctly and preventing the idle watcher from exiting. Verified the test three consecutive times, ran fm-test-fixtures, ShellCheck, bash syntax checks, and git diff checks. The outer no-mistakes executor can now bind a fresh attestation to the new head

* no-mistakes(ci): Added behavioral coverage proving the shared spawn tmux fixture defaults an unset FM_FAKE_PANE_PATH to empty. Verified the fixture suite, ShellCheck, syntax/diff checks, and all six migrated test suites; all passed. The outer executor can now bind a fresh no-mistakes attestation to the updated head

* refactor: retire legacy PR-check migration machinery (#3299)

* feat(bin): retire completed PR-check migration machinery

Every registered home already carried both completion markers, and no
installer still creates pre-migration checks. Remove the one-time migrate
script, its bootstrap/watch/teardown/docs surface, and migration-path tests
without weakening live check-trust or PR-poll authentication.

* no-mistakes(review): Restore live PR-check security coverage

* no-mistakes(document): Refresh retired PR-check documentation

* no-mistakes(ci): Fixed both failing CI checks. Updated inactive-reconcile setup to use the public status-marking interface, preventing false watcher exits. Made remote-job shutdown deterministic by stopping the complete worker tree before tampering. Verified both affected test suites, repeated inactive reconciliation, shell syntax, and git diff checks

* feat(bin): add trusted process-event extension bindings (#3247)

* feat(extensions): bind trusted external process-event adapters

* no-mistakes(review): Enforce owner and remote-home conformance

* no-mistakes(review): Enforce serialized remote extension package lifecycle

* no-mistakes(review): Enforce identity-conditional extension retirement

* no-mistakes(review): Serialize extension retirement and recover crash cuts

* no-mistakes(review): Unify retirement worker and lifecycle lock ownership

* no-mistakes(review): Harden extension lifecycle retirement serialization

* no-mistakes(review): Unify extension registration and overridden-state lifecycle boundaries

* no-mistakes(document): Clarify built-in-only captain answer routing

* no-mistakes(lint): Captain: fix extension binding ShellCheck findings

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes(review): Use isolated UID mapping for owner conformance

* no-mistakes(review): Captain: remove forbidden CI ownership wrapper

* no-mistakes(review): Serialize extension binding publication

* no-mistakes(review): Document ordinary CI owner-fixture exclusion

* no-mistakes(review): Quarantine orphaned handshake descendants

* no-mistakes(test): Fix orphan attribution

* no-mistakes(test): Harden process tracker baseline

* no-mistakes(test): Harden detached descendant attribution

* no-mistakes(test): Use exact invocation-group cleanup

* no-mistakes(test): Bound remote conformance transport crossings

* no-mistakes(test): Parallelize isolated extension conformance tests

* no-mistakes(test): Lifecycle suite still exceeds deadline

* feat(extensions): bind trusted external process-event adapters

* no-mistakes(review): Enforce owner and remote-home conformance

* no-mistakes(review): Enforce serialized remote extension package lifecycle

* no-mistakes(review): Enforce identity-conditional extension retirement

* no-mistakes(review): Serialize extension retirement and recover crash cuts

* no-mistakes(review): Unify retirement worker and lifecycle lock ownership

* no-mistakes(review): Harden extension lifecycle retirement serialization

* no-mistakes(review): Unify extension registration and overridden-state lifecycle boundaries

* no-mistakes(document): Clarify built-in-only captain answer routing

* no-mistakes(lint): Captain: fix extension binding ShellCheck findings

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes(review): Use isolated UID mapping for owner conformance

* no-mistakes(review): Captain: remove forbidden CI ownership wrapper

* no-mistakes(review): Serialize extension binding publication

* no-mistakes(review): Document ordinary CI owner-fixture exclusion

* no-mistakes(review): Quarantine orphaned handshake descendants

* no-mistakes(test): Fix orphan attribution

* no-mistakes(test): Harden process tracker baseline

* no-mistakes(test): Harden detached descendant attribution

* no-mistakes(test): Use exact invocation-group cleanup

* no-mistakes(test): Bound remote conformance transport crossings

* no-mistakes(test): Parallelize isolated extension conformance tests

* no-mistakes(test): Lifecycle suite still exceeds deadline

* no-mistakes(review): Split extension conformance and forward remote transfer input

* no-mistakes(review): Forward malformed remote payloads through fm-on

* no-mistakes(review): Bound extension coordinator failure cleanup

* no-mistakes(test): Skip repeated orphan sweep in coordinator children

* no-mistakes(test): Queue isolated extension sections through bounded workers

* no-mistakes(test): Bound extension coordinator lane cleanup

* no-mistakes(test): Split remote lifecycle coordinator sections

* no-mistakes(test): Coordinator probes pass; aggregate deadline remains

* no-mistakes(test): Launch extension sections concurrently

* no-mistakes(test): Fix coordinator marker publication

* no-mistakes(test): Stabilize extension binding coordinator timing

* no-mistakes(lint): Fix extension binding ShellCheck warnings

* fix(extensions): prove invocation cleanup before retirement

* no-mistakes(review): Harden process-event inbox confinement

* no-mistakes(review): Preserve legacy capture parity

* no-mistakes(review): Protect external registry staging

* no-mistakes(test): Stabilize bounded extension conformance aggregate

* no-mistakes(document): Document external evidence confinement

* no-mistakes(ci): CI phase fixed. The failure was a flaky fixture in `tests/fm-remote-transport-lanes.test.sh`: its “fresh/in-use” staging directory had no live owner identity, so the real worker correctly reaped it once the 1-second age boundary elapsed on slower CI. The fixture now records the active test shell’s exact PID/start identity and cleans those records before removal. Verified: `bash tests/fm-remote-transport-lanes.test.sh` exits 0 with all checks passing; `git diff --check` passes. Provider check retrieval was also retried successfully, resolving the selected manual CI finding. Changed file: `tests/fm-remote-transport-lanes.test.sh`

* no-mistakes(review): Harden extension staging and lifecycle reservation

* no-mistakes(review): Harden external staging and lifecycle reservations

* no-mistakes(review): Wire capture helper into remote conformance

* no-mistakes(review): Pin external capture handoff and signal failures

* no-mistakes(review): Bind pinned capture authority to inherited descriptor

* no-mistakes(review): Harden descriptor-bound capture authority

* no-mistakes(review): Harden core capture reservation authority

* no-mistakes(review): Harden capture reservation boundaries

* no-mistakes(review): Harden capture reservations and cleanup

* no-mistakes(review): Harden capture handoff and reservation cleanup

* no-mistakes(review): Bind capture handoff to claim descriptors

* no-mistakes(review): Release lifecycle locks after host crashes

* no-mistakes(review): Pin reservation recovery to recorded state roots

* no-mistakes(review): Reject control bytes in claim state roots

* no-mistakes(test): Stabilize extension capture descriptor handoff

* no-mistakes(document): Document extension capture authority boundary

* no-mistakes(lint): Fix ShellCheck extension binding warnings

* no-mistakes(ci): CI phase result: fixed `bin/fm-procevent.sh` by initializing the shared `capture_state` sentinel for built-in adapters under `set -u`. This prevents normal built-in captures from aborting before publication. Verified: `bash -n bin/fm-procevent.sh` and `git diff --check` pass. The focused process-event suite was run locally but stopped earlier at a local detached-runner claim failure (`reconcile never claimed the registered source`), before the CI-reported post-capture path; CI evidence confirms the fixed unset-variable failure affected the failing remote, board, watcher, and process-event checks

* no-mistakes(document): Correct extension namespace creation timing

* no-mistakes(lint): Initialize capture locals for ShellCheck

* fix(bin): deliver safety rules to promoted workers (#3269)

* fix(bin): deliver the real definition of done to a promoted scout, and ban --yes

A promoted scout used to receive a free-form placeholder instead of the
mode-specific Definition of done a briefed ship worker gets, so it never
saw the ask-user escalation rule or the --yes prohibition. That gap is the
concrete reason one incident's worker drove validation with --yes and
answered its own ask-user findings.

- Add bin/fm-dod-lib.sh as the single owner of a ship task's mode-specific
  Definition of done, rendered by both bin/fm-brief.sh and bin/fm-promote.sh
  so the two contracts cannot drift.
- bin/fm-promote.sh now writes data/<id>/ship-instructions.md carrying the
  scratch inventory, clean base, ship branch, and that Definition of done,
  and prints the fm-send.sh command that delivers it.
- State the --yes ban as a prohibition rather than a preference, without
  claiming an enforcement the tool does not provide.
- Cover both through the real promotion and brief paths in
  tests/fm-task-delivery.test.sh and tests/fm-brief.test.sh.

* no-mistakes(review): Publish promotion instructions before committing task state

* no-mistakes(review): Supersede conflicting scout delivery rules after promotion

* no-mistakes(review): Reject invalid promotion instruction destinations

* no-mistakes(document): Align documentation with promotion delivery contracts

* no-mistakes(ci): Fixed both CI findings. Promoted workers now receive an explicit worktree-isolation check before branch creation, with instructions to stop and escalate if they are in the primary checkout. Updated behavioral coverage to verify the delivered promotion payload, and aligned the ask-user authority test with the new fleet-wide --yes prohibition. Verified with bin/fm-lint.sh, tests/fm-brief.test.sh, tests/fm-ask-user-authority.test.sh, tests/fm-task-delivery.test.sh, and git diff --check

* no-mistakes(ci): Made tests/fm-ask-user-authority.test.sh executable so the modified colocated behavioral test runs directly like the surrounding test suite. Verified bin/fm-lint.sh, fm-brief, ask-user-authority, and task-delivery tests; all pass. git diff --check is clean

* no-mistakes(ci): Strengthened tests/fm-task-delivery.test.sh to behaviorally verify that real promotion and brief generation deliver byte-identical Definition-of-done blocks for all three modes. Verified tests/fm-task-delivery.test.sh, tests/fm-brief.test.sh, bin/fm-lint.sh, and git diff --check. The outer pipeline can now commit and attest the updated head

* no-mistakes(ci): Fixed promotion isolation instructions so any checkout other than the launched disposable worktree requires escalation, including another non-primary worktree. Updated behavioral coverage against the delivered promotion payload. Verified fm-task-delivery, fm-brief, fm-ask-user-authority, full fm-lint/ShellCheck, workflow lint, and git diff checks

* fix(bin): present Lavish feedback as structured output (#3321)

* fix(bin): present complete Lavish board feedback as structured output

Give the Lavish adapter a read-only presentation so a handler sees every
annotation and the session-ending tag=message as its own field, instead of
grepping a truncated raw capture.

* no-mistakes(review): Preserve unquoted messages and prioritize captain prose

* no-mistakes(document): Document structured Lavish result reads

* no-mistakes(ci): Fixed Lavish `read` completeness: rows missing declared fields are excluded from presented items, counted as malformed, and force `complete: no`. Added behavioral regression coverage through the adapter interface. `bin/fm-lint.sh`, syntax checks, and focused valid/malformed read checks passed. The portable-serial failure was an unrelated secondmate cooldown timing flake

* fix: keep task records and backlog transitions atomic (#3322)

* fix(records): pair backlog transitions with the record that moves

Dispatch and completion each moved a task's physical record and its
backlog row as two independently timed steps, so a crash or a forgotten
follow-up could leave the two disagreeing: a record with no in-flight
row, an in-flight row with no owner, or a finished task still shown in
flight.

Fold each backlog transition into the script that performs the physical
change, under the per-task lock it already holds and before it reports
success. Dispatch moves the item to In flight after publishing the task
record and fails loudly, removing its provisional record, when that
transition cannot land. Completion records an authoritative close and
performs it before removing the record, so an interrupted cleanup can be
finished later, and its closing message now confirms what already
happened rather than instructing a future step.

Add a same-home reconciliation sweep to session start so a home that was
interrupted mid-transition settles its own books on restart, replaying a
recorded close and restoring an in-flight row it already owns a worker
for. It never reads or writes another home; the fleet snapshot and the
cross-home nudge stay as backstops.

Close records are validated before they are trusted: the file is read as
raw bytes and rejected outright when it carries a NUL or other control
byte, every field must be well formed and non-duplicated, the id must
match the record it was found under, the data location must resolve
inside this home, and each close argument must carry a permitted,
well-formed value. Writer and reader share one validator so a record
this home publishes always remains replayable, independent of locale.

Homes configured for a manual backlog, and homes with no backlog at all,
stay exempt and are unaffected.

* no-mistakes(review): Remove stale bootstrap migration helper invocation

* no-mistakes(review): Preserve pending closes and narrow signal deferral

* no-mistakes(review): Record close before destructive teardown

* no-mistakes(review): Refuse pending closes before creating resources

* no-mistakes(review): Guard relaunches and preserve cleanup warnings

* no-mistakes(review): Reject symlinked records and clarify cleanup guidance

* no-mistakes(review): Align dispatch eligibility and protect close replay

* no-mistakes(review): Unify exact task incarnation parsing

* no-mistakes(review): Render resolved configured backlog path

* no-mistakes(review): Harden transition path boundaries against symlinks

* no-mistakes(review): Validate lifecycle state before resource actions

* no-mistakes(review): Enforce transition tooling and continuous state locks

* no-mistakes(review): Consolidate same-home lifecycle file boundaries

* no-mistakes(review): Enforce canonical lifecycle containment and tooling contracts

* no-mistakes(review): Reject final-component lifecycle record symlinks

* no-mistakes(document): Document lifecycle record path boundaries

* no-mistakes(lint): Quote literal done tokens in atomicity tests

* no-mistakes(ci): Fixed all PR-caused CI failures: bootstrap now treats an absent state directory as an empty fresh home while retaining unsafe-state checks; nested remote secondmate retirement accepts records already removed with the retired home; teardown fixtures now provide valid data/manual-backend configuration; and the manual reminder assertion checks the configured absolute backlog path. Verified the reported tests, remote lifecycle E2E, backlog atomicity suite, Bash syntax, diff checks, and ShellCheck. The documented pre-existing captain-hold failure was intentionally untouched

* no-mistakes(ci): Fixed Behavior portable serial 3 by adding `od` to the teardown test’s lsof-free PATH fixture. The new close-record validator legitimately requires `od`; its omission caused teardown to fail before process-group cleanup and stall the shard. Verified the full `tests/fm-teardown.test.sh` suite passes, plus Bash syntax, ShellCheck, and `git diff --check`

* no-mistakes(ci): Fixed close replay to durably retain incomplete-cleanup evidence before removing task metadata. Subsequent retries now emit the reconciliation warning even after a backlog probe or close failure. Updated the behavioral regression and verified the full atomicity suite under stock macOS Bash 3.2, plus shellcheck and diff checks

* fix(records): validate record bytes without an uncurated tool

The byte validation added for close records and directory paths shelled
out to od. The spawn and teardown lifecycle runs under a curated command
set that deliberately excludes it, so on any restricted PATH the check
could not run, the data directory read as unresolvable, and dispatch and
cleanup refused - wedging the lifecycle rather than protecting it.

An earlier attempt made the failing test pass by adding od to that
curated set. That fixed the test to agree with the defect and quietly
widened the contract the fixture exists to pin, so it is reverted here.

Inspect the bytes with perl instead, which is already in the curated set
and already used in this repo for the same portability reason. The
emitted values are identical to od's, so the rejection semantics are
unchanged: NUL and other control bytes are still refused, legitimate
paths containing spaces or non-ASCII characters still round-trip, and
the check stays independent of the process locale.

The restricted-PATH teardown case now passes because the validator no
longer needs od, not because the fixture was loosened.

* no-mistakes(review): Enforce dispatch eligibility and atomic remote record publication

* no-mistakes(document): Document dispatch eligibility and cleanup alerts

* fix(bin): contain promote and Relay metadata publishing (#3342)

* fix: publish promote and Relay meta rewrites through contained replace

Bare mv still rewrote live task records in place, so a symlink meta could
be followed to a target outside state/. Route those field rewrites through
the shared publisher and drop the unused library aliases.

Co-authored-by: Cursor <cursoragent@cursor.com>

* no-mistakes(review): Refuse dangling symlinks during X metadata clear

* no-mistakes(review): Refuse unsafe metadata before follow-up and promotion side effects

* no-mistakes(review): Exercise dangling symlink refusal through clear helper

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(bin): absorb turn-end wakes during bounded pane churn (#2877)

* fix(watch): absorb a turn-end whose pane churned since the previous poll

The watcher's "absorb a benign turn-end when the crew is provably working"
triage was structurally unreachable for any harness whose semantic busy state
has no verified source. crew_absorb_class only reports working for an actively
running no-mistakes step or an exact busy verdict, and bin/fm-crew-state.sh can
only answer unknown for such an adapter, so codex crewmates surfaced a signal
wake at every turn boundary with nothing to act on - a full supervisor drain,
inspect and acknowledge turn per worker turn, scaling with the number of workers
in flight and drowning the wakes that matter in identical noise.

Widen the proof rather than bound the wake rate. A wake carrying only bare
turn-ended markers is now also benign when the task's pane content changed since
the previous poll, compared against the same state/.hash-* marker the staleness
backbone already records and already trusts as liveness. That evidence claims no
harness semantics, so it fabricates no busy verdict an adapter has not earned,
and it needs no adapter cooperation.

Absorb stays evidence-driven in both directions. A wake naming any status file
keeps the strict proof, every captain-relevant verb still surfaces immediately,
and an unresolvable task, a missing prior hash, a failed or empty capture, or an
unchanged pane all surface exactly as before. The absorb defers rather than
swallows: a crew that has stopped renders nothing further, so its now-static pane
surfaces through the staleness backbone within a poll or two. Bounding the
surfacing rate instead would have suppressed genuinely stopped workers.

The derivation lives with the .hash-* marker format in bin/fm-watch.sh, which
owns it, and costs one bounded capture reached only for a no-verb turn-end whose
crew is not already provably working.

* no-mistakes(review): Captain, guard pane-churn absorption from collisions and secondmates

* no-mistakes(review): Captain, make watcher marker identities injective

* no-mistakes(review): Captain, isolate ambiguous legacy markers and restore Herdr sourcing

* no-mistakes(review): Captain, localize pane-churn collision guard

* no-mistakes(review): Captain, reject malformed pane-churn hashes

* no-mistakes(document): Document pane-churn turn-end evidence

* no-mistakes: apply CI fixes

* fix(watch): gate and bound the pane-churn turn-end absorb

Make the pane-churn form of positive work evidence opt-in per home and
bound how long it may defer one endpoint's bare turn-ends.

Absorbing a bare turn-end on pane churn is now reached only when the home
creates config/turnend-churn-absorb. The other two proofs read a verdict
the harness itself vouches for, while this one infers execution from
rendered bytes, so widening the absorb is a home's choice rather than a
default every fleet inherits. With the flag absent the predicate returns
on its first line and triage is unchanged.

Churn and pane staleness read the same pane, so neither can be the
other's only backstop. A pane that renders continuously never presents
the two consecutive identical hashes the staleness backbone needs, so an
unbounded churn absorb left a worker that had genuinely stopped behind
such a renderer with no path to surface at all. One endpoint's turn-ends
may now ride churn evidence for at most FM_TURNEND_CHURN_ABSORB_SECS,
tracked in state/.churn-since-*, after which the wake surfaces and the
window restarts. The bound is evaluated before any .stale- state is
touched, so a wake that surfaces there leaves the staleness backbone's
own classification alone.

Covers both with behavioral tests: the same churning fixture that absorbs
with the flag surfaces and queues without it, and a spent deferral window
surfaces and restarts. The four existing safety guards now run with the
flag enabled so they keep proving their specific guard.

* no-mistakes(review): Fail closed on invalid churn deferral state

* no-mistakes(review): Validate persisted churn deadlines before arithmetic

* no-mistakes(review): Make churn deadlines transactional and bounds safe

* no-mistakes(review): Compose turn-end evidence per task from one snapshot

* no-mistakes(review): Restore strict turn-end fallback guards

* no-mistakes(document): Clarify pane-churn supervision documentation

* no-mistakes(lint): Fix watcher arithmetic lint issues

* no-mistakes: apply CI fixes

* no-mistakes(document): Clarif…
* fix(bin): verify the real GitHub merge outcome instead of reporting an unproved merge (#3064)

* fix(pr): verify GitHub merge outcome

* no-mistakes(review): Captain, fixed forge-only merge verification, queue guidance, metadata propagation

* no-mistakes(document): Correct forge-specific merge documentation

* no-mistakes(review): Captain: forge-only queue fix, focused tests pass

* no-mistakes(review): Captain: suppress closed-state guidance and prove parent regression

* no-mistakes(review): Captain: remove history proof; retain executable regressions

* no-mistakes(document): Clarify GitHub recording timing in architecture docs

* no-mistakes(document): Clarify outcome-aware PR merge recording documentation

* no-mistakes: apply CI fixes

* Revert "no-mistakes: apply CI fixes"

This reverts commit c326cfa9430c6173eedc8ff7f27d19d0552daf01.

The automatic CI repair round removed the up-front `gh` prerequisite check
while keeping the `gh` dependency: `bin/fm-pr-merge.sh` still calls
`gh api graphql` for the outcome read and `gh api` for the branch-rules read.
That left the same hard requirement without the clear named error, and review
immediately raised a new finding for exactly the failure the check prevents -
`gh-axi pr merge` landing the merge while the follow-up read fails, so the PR
metadata is never recorded.

The check is also symmetric with the GitLab arm directly above it, which
already refuses up front when `glab` or `jq` is missing, on the stated
principle that a missing tool should be a named prerequisite rather than a
merge that is armed and then refused for an unexplained reason.

The workflows this round was chasing sit at `action_required` because this is
a fork pull request; no code change can turn them green.

* fix(pr): keep PR bookkeeping when a merge outcome read fails

On the GitHub path a merge call that returned success was followed by
`github_read_outcome || exit 1`, so a transient API failure, rate limit,
or network blip during the read dropped out of the script before
`record_pr_metadata` ever ran. The merge could have landed while `pr=`
went unrecorded and the merge poll was never armed - bookkeeping lost on
a real merge. The failure path just above already recorded metadata
before exiting, so the error path was more careful than the success one.

Record the PR before that refusal. Recording arms the later merge poll
and is not a success claim, which is the same reasoning that keeps
`record_pr_metadata` on the gh-axi failure path. The refusal itself is
unchanged: exit stays non-zero and the message still names the concrete
observed state. Metadata is withheld only when the read succeeds and
proves the pull request neither merged nor queued.

Pin it with a case that stubs `gh api graphql` into failure after a
successful `gh-axi pr merge`, asserting both the non-zero exit and the
recorded metadata.

* no-mistakes(review): Aggregate queue rules and report conflicts explicitly

* fix(pr): keep the merge abstraction reachable and its bookkeeping intact

Two holes remained in the outcome-verified GitHub merge path, both on
installations where gh-axi is present but gh is not.

The verification preflight refused before bin/fm-pr-merge.sh ever reached
the configured gh-axi merge abstraction, so an installation without gh
could no longer merge at all. gh-axi now performs the merge unconditionally
and the queue-aware gh read became an optional enrichment: with gh on PATH
its GraphQL view still separates merged from queued, and without gh the
gh-axi view still proves a landed merge while every outcome it cannot prove
refuses.

The PR metadata recording sat behind the outcome read, so a merge that
landed before that read failed lost pr= and its merge poll. Recording now
happens once, before either forge call, which arms the poll without
claiming a landed outcome and leaves teardown a PR identity to verify
against no matter how the read ends.

Rebasing onto main also restored the durable merge-outcome reporting and
the GitLab landed-state confirmation that the conflict resolution dropped.

Tests pin each fix through the executable interface: the merge abstraction
is reached and verified with gh absent, a failed fallback read keeps its
bookkeeping, and a mock that snapshots the task meta during the forge call
proves pr= is recorded before the merge can land.

* no-mistakes(review): fix(pr): de-dup queue methods, fall back on failed gh read, refresh contracts

* no-mistakes(review): fix(pr): quote forge output and explain armed auto-merge on refusal

* no-mistakes(review): fix(pr): claim auto-merge armed only when the forge accepted it

* no-mistakes(review): fix(pr): tell the operator what each GitHub refusal could not observe

* no-mistakes(review): fix(pr): gate every forge-acceptance claim on a successful merge

* no-mistakes(document): align merge docs with verified GitHub outcome contract

* fix(pi): prevent duplicate captain outcome reports (#3184)

* fix(pi): stop reporting one merge to the captain twice

The supervision branch's captain-outcome note told main, unconditionally,
that the note "is not your own earlier output" and to relay it now. When
main had already reported the same event, that assertion was false and the
order turned the correct response - saying nothing new - into a mechanical
re-report, so the captain saw one merge reported twice in 16 seconds.

Two independent changes, both needed:

- The relay instruction is now conditional. It still names itself as a
  supervision outcome so main cannot mistake it for its own earlier answer
  (the silent loss that instruction exists to prevent), and it now lets
  main stay quiet about an outcome it has already given the captain.

- The merge case is closed at its source rather than left to that judgment.
  One merge reaches a home on two independent paths by design - main's own
  permanently main-owned merge poll, and the branch's task-local status
  wake - and main's captain-facing text only reaches the branch's mirror at
  main's turn end, so the branch can escalate before it could possibly see
  the captain was already told. bin/fm-pr-merge-notified.sh answers that
  question from bin/fm-pr-lib.sh's canonical merge-notification marker, so
  the answer holds regardless of mirror timing. A captain outcome naming an
  already-published merge is delivered as the ordinary rendered note
  instead of opening a follow-up turn: still appended, still visible, still
  recorded with the verdict the branch decided, minus the wasted turn.

Any error, timeout, or unreadable state relays the outcome. A duplicate
announces itself; a lost outcome does not.

Regression coverage drives the real delivery path in both directions: a new
outcome must still reach the captain in exactly one follow-up turn even
beside an unrelated published merge, and an already-published merge must
open no second turn while a different PR in the same task still does. The
merge path's real producer and this new consumer are exercised end to end
in tests/fm-pr-merge.test.sh.

Pi-only by construction: the delivery path lives in .pi/extensions, so no
other harness loads it, and the new script only reads existing markers.

* no-mistakes(review): Document accepted latest-marker suppression residual

* no-mistakes(review): Recheck ownership before merge outcome delivery

* no-mistakes(document): Document merge-outcome suppression exception

* refactor(pi): drop the source-level merge suppression, keep the envelope fix

The captain reviewed this branch and judged the source-level duplicate
suppression overly complicated for the problem it solved, and asked for
the change to be reduced to the envelope wording alone.

Remove the mergeIntoMain downgrade path, bin/fm-pr-merge-notified.sh, and
every test and document that existed only for it. What remains is the
conditional captain-outcome instruction: main is told to stay quiet about
an outcome it has already reported and to relay anything else, which
covers the duplicate without a second mechanism.

The silent-loss protection is untouched - the note is still typed,
self-describing, and delivered as one invisible follow-up turn - and the
behavioral tests still assert that, now requiring both halves of the
conditional instruction.

* no-mistakes(ci): Clarified in code comments and owned documentation that this is intentionally an M1-only, model-facing conditional relay fix—not source-level suppression—addressing Greptile’s mistaken scope expectation without changing runtime behavior. Net diff remains 3 files and 27 insertions. Verified with fm-pi-branch-extension tests, fm-lint, doc audience check, and git diff --check; all passed

* no-mistakes(ci): Strengthened the runtime delivery test to verify the captain outcome retains its required self-description and outcome text. Verified with `bash tests/fm-pi-branch-extension.test.sh`, `bin/fm-lint.sh`, `bin/fm-doc-audience-check.sh`, and `git diff --check`; all passed. The outer pipeline can now commit and attest the new head

* fix(bin): prioritize active pipeline-owned crew runs (#3194)

* fix(bin): bind the live pipeline-owned run instead of a superseded failed row

fm-crew-state.sh bound a superseded FAILED no-mistakes run to a task instead
of the LIVE replacement run: the live run's pipeline-owned lane head is not a
git object in the task worktree, so head-equality attribution rejected it and
the coarse runs-list fallback silently continued past the RUNNING row onto an
older failed row whose head equalled the stale worktree HEAD. The home summary
then flipped invalid and Bearings hid the home's live work (F10).

Attribution precedence now follows the daemon's own identity:
- An ACTIVE run for the task's branch binds without head equality while
  branch_sync.state is pipeline_owned (fm_nm_run_is_pipeline_owned_active);
  the pipeline owning the branch is itself the attribution.
- A genuinely failed run with no later run on the branch still reports failed
  through the unchanged head-equality path - real failures are not hidden.
- In the coarse runs scan, an unresolvable head is unknown attribution and
  stops the scan (fm_nm_head_resolvable) instead of falling through to an
  older row; a resolvable-but-mismatched head keeps the historical
  reused-branch skip.

The exemption never applies to a terminal run and requires pipeline_owned
specifically, both pinned by negative-control tests. Fixture shape verified
against the live incident run's real axi status output.

* no-mistakes(document): Updated run-attribution documentation ownership

* fix(pi): surface requested outcomes without replaying fleet events (#3211)

* fix(pi): surface requested supervision outcomes

* no-mistakes(review): Mirror in-flight captain requests before branch dispatch

* no-mistakes(review): Exercise real branch ownership and main outcome access

* no-mistakes(review): Preserve request tails and align verdict guidance

* no-mistakes(review): Preserve complete current captain requests

* no-mistakes(review): Require visible requested outcomes and realistic classification

* no-mistakes(document): Align supervision outcome documentation

* no-mistakes(ci): Fixed Greptile’s runtime-ordering finding. The extension now stages Pi’s authoritative `before_agent_start` prompt before SessionManager persistence and suppresses the later duplicate entry. Updated docs and behavioral regression to reproduce real Pi ordering and verify each prompt is mirrored exactly once. Passed branch-extension tests, supervision tests, strict Pi typecheck, full lint, and diff checks

* no-mistakes(review): Use canonical operational input classification

* no-mistakes(review): Filter legacy operational inputs canonically

* no-mistakes(document): Clarify captain request mirroring boundary

* no-mistakes(ci): Fixed the CI time-boundary failure in tests/fm-public-followup.test.sh by pinning its clock, including context-registry setup. This prevents follow-up fixtures from expiring based on wall time. Verified the full regression suite passes, project-owned lint passes, and git diff checks are clean

* no-mistakes(document): Clarify captain-visible supervision outcome documentation

* feat(bin): add concurrent bounded remote transport lanes (#3210)

* feat(bin): per-home remote transport lanes with cancellation, bounded send, and closed stdin

All remote commands for every home on one host used to serialize through one
single-job-at-a-time worker on one shared queue: a timed-out caller abandoned a
staged job that kept running, retries convoyed behind it, fm-send's remote leg
had no time bound, and staging captured the caller's stdin to EOF so any
fm-on.sh caller with an open stdin wedged staging indefinitely.

- The worker now serves one lane per staged home: same-home jobs run strictly
  FIFO in a new staging-sequence order while different homes run concurrently,
  each lane as its own top-level worker process (a backgrounded subshell does
  not reliably reap dead children, so a zombie group leader kept a finished
  command's process group signalable). Long-poll preemption is lane-scoped.
- A caller that disconnects or times out cancels its job: the entrypoint marks
  the record on any post-staging exit and probes its parent so a dead ssh
  channel cancels without a signal; the worker skips cancelled queued jobs,
  terminates a running cancelled job's process group, and reaps the record.
- fm-send's remote leg is bounded by FM_SEND_REMOTE_BUDGET (default 30s) and a
  bound hit exits through the existing unconfirmed-delivery contract, which
  stays idempotent because the remote enqueue deduplicates.
- fm-on.sh defaults the remote command's stdin to /dev/null; the three payload
  callers pass the new --stdin flag. Abandoned .stage.* litter is age-reaped.
- The job execution deadline no longer loses up to a second to clock
  truncation.

* no-mistakes(review): Protect live stages and validate send budgets early

* no-mistakes(review): Preserve sequence lock ownership during stale recovery

* no-mistakes(review): Allocate job sequences at publication boundary

* no-mistakes(review): Bound remote keys and extend stale lock recovery

* no-mistakes(document): Document bounded remote transport behavior

* no-mistakes(lint): Suppress intentional deferred-expansion lint warning

* no-mistakes(ci): Fixed stale sequence-lock recovery by reconciling the counter against published job records before allocating the next sequence, preventing duplicate sequences and same-home FIFO violations. Added a behavioral regression test reproducing displacement after publication and verifying execution order. Passed fm-remote-transport-lanes.test.sh, fm-remote-job.test.sh, fm-lint.sh, and git diff --check

* no-mistakes(review): Use atomic sequence claims and lossless lane keys

* no-mistakes(review): Recover regressed sequence hints and rate-limit claim reaping

* no-mistakes(review): Restrict worker heartbeats to serving loop

* no-mistakes(review): Verify supervisor identity before lane recovery signals

* no-mistakes(review): Verify tracked lane and claim owner identities

* no-mistakes(document): Clarify remote lane and transport contracts

* no-mistakes(ci): Fixed the CI time-boundary failure by pinning fm-public-followup tests to a deterministic clock, including context-registry setup. Verified tests/fm-public-followup.test.sh, tests/fm-remote-transport-lanes.test.sh, shellcheck, and git diff --check

* no-mistakes(review): Preserve assigned lane ownership of queued jobs

* no-mistakes(review): Reserve homes owned by foreign queued lanes

* no-mistakes(review): Preserve completed results during crash recovery

* no-mistakes(review): Harden claim cleanup, expiry, and cancellation races

* no-mistakes(review): Verify process groups and reap abandoned results

* no-mistakes(review): Stop leaderless groups and reap cancelled publications

* no-mistakes(document): Correct remote transport lifecycle documentation

* no-mistakes(lint): Quote done state comparisons for ShellCheck

* fix(bin): accelerate and bound changed test runs (#3250)

* fix(tests): make the changed-file map select per script and stabilize a budget flake

The changed-file map's bin/ fallback resolved a direct test reference to that
test's whole FAMILY. bin/fm-push-transition-lib.sh is named by exactly one
real-Herdr E2E, so a one-line change to it selected all 12 real-herdr-gated
scripts, including a 341s presentation E2E with no dependency on it.

Resolve direct test references per script, and keep resolving consumer bin/
scripts through the curated map so recorded family-level coupling survives.

Also fix a load-sensitive flake: the tool-update budget deadline is whole-second
granular, so a test budget of 1 left headroom anywhere in (0, 1] seconds and the
first budget check could already read as exhausted.

* feat(bin): make suite wall clock a result and let a family's concurrency be proven

--max-wall-ms fails a run whose wall clock exceeds the caller's budget, after
reporting the per-script results. A suite that stays green while outgrowing its
caller's invocation budget is the regression that got an agent killed mid-run
and retried invisibly, so duration has to be a result rather than a log note.

--pool on the isolation-proof harness runs the same concurrent proof over a
whole family, so 'is this family safe to parallelize?' is answered by a command
instead of a guess. Measured watcher-wake-lock and refused it: 3 of 18 scripts
fail under concurrency on wall-clock assertions about reaching the next poll.

* perf(bin): schedule the changed suite concurrently, longest first

The watcher-wake-lock family is proven concurrent-safe (two clean runs, 18
candidates, 0 failures at 4 workers; docs/fm-test-isolation-proof.md), so
--changed now schedules its proven-concurrent scripts with bounded parallelism
and runs any unproven remainder serially afterwards, never beside them.

Concurrent runs are ordered longest-hint-first. Workers are handed scripts in
order, so alphabetical order started the 193s fm-watch-triage last and stranded
it running alone: 395s wall against a 205s balanced four-worker sum.

An explicit --jobs keeps its strict refusal, so every CI lane is unchanged.

* fix(bin): bound a hung test instead of letting it hang the suite

tests/fm-calm-pi-extension.test.sh was observed running 17+ minutes against a
464ms recorded hint, and the suite had no per-script bound to stop it. An
unbounded suite is precisely what silently outruns a caller's invocation budget,
and --max-wall-ms is evaluated after the run so it cannot end one that never
finishes.

--per-script-timeout-secs terminates a script that outruns it and records exit
124, so the run still completes, accounts for the script, and fails. The
auto-concurrent --changed path applies 900s, far above the slowest real script
(the 341s Herdr presentation E2E), so it only ever converts a hang.

* no-mistakes(review): Enforce safe concurrency and descendant timeouts

* no-mistakes(review): Validate empty runs and isolation proof pools

* no-mistakes(review): Measure selection time in wall budget

* no-mistakes(review): Reap interrupted workers and bound finalization

* no-mistakes(review): Contain shutdown descendants and watchdog finalization

* no-mistakes(review): Honor remaining budget and close launch races

* no-mistakes(review): Restore timeout helper and simplify runner cleanup

* no-mistakes(review): Record isolation pool admission metadata

* no-mistakes(review): Bound Chrome reap and scope proof admission

* no-mistakes(review): Align proof scheduling and preserve budget summaries

* no-mistakes(review): Remove unreliable finalization watchdog

* no-mistakes(review): Freeze budget duration and enforce admission caps

* no-mistakes(document): Refresh test runner concurrency documentation

* no-mistakes(lint): Fix ShellCheck findings in test runner scripts

* no-mistakes(ci): Fixed Greptile’s concurrency-consent finding. `--changed` now remains serial by default; `--changed --jobs auto` explicitly opts into bounded concurrency and the automatic hang timeout. Updated documentation and added behavioral coverage proving serial default behavior, explicit concurrent scheduling, and refusal of `--jobs auto` outside `--changed`. Verified with `bash tests/fm-test-run.test.sh`, `bin/fm-lint.sh`, and `git diff --check`

* no-mistakes(review): Restore automatic changed-suite concurrency and timeout

* no-mistakes(review): Correct changed-suite contributor guidance

* no-mistakes(review): Reject gate-skipped isolation proofs

* no-mistakes(review): Correct automatic concurrency evidence

* no-mistakes(review): Isolate nested runner process groups

* no-mistakes(review): Remove unreliable signal cleanup machinery

* no-mistakes(test): Narrow changed-suite selection to executable contract owners

* no-mistakes(document): Document isolation proof skip and artifact semantics

* no-mistakes(ci): Fixed Greptile’s concurrency-consent finding. `--changed` now remains serial by default; bounded concurrency requires explicit `--jobs auto`. Updated behavioral coverage, contributor guidance, and isolation-proof commands accordingly. Verified with `tests/fm-test-run.test.sh`, `bin/fm-doc-audience-check.sh`, `bin/fm-lint.sh`, Bash syntax checks, and `git diff --check`; all passed

* no-mistakes(review): Restore plain changed-suite automatic concurrency

* no-mistakes(review): Record resolved changed-suite worker count

* fix(bin): keep a runner change selecting its whole curated family

A pipeline fix round narrowed the curated changed-file map so bin/fm-test-run.sh
and bin/fm-test-isolation-proof.sh selected only their own two contract tests,
and the documentation surfaces only the audience test. That cut this branch's
own changed selection from 33 scripts to 5.

The runner executes every pure-contract-unit script, so its contract test
passing proves its logic is right, not that the suite it drives still runs.
Narrowing it also makes any wall-clock claim about the changed suite trivially
true by not running the work.

Only the unmapped bin/* grep fallback resolves per script; curated mappings keep
their recorded family coupling.

* perf(bin): admit the pure-contract-unit family to bounded concurrency

A runner-file change selects pure-contract-unit, so that family decides the
changed suite's wall clock. With only watcher-wake-lock admitted, 14 of its 33
selected scripts fell to the serial tail and the selection measured 327.3s
against a 300s budget: the concurrent group was 19 scripts totalling 273.4s
while the tail alone was 215.7s.

bin/fm-test-isolation-proof.sh --pool pure-contract-unit --jobs 4 passes twice,
32 candidates, 0 failures, so the family is admitted on recorded evidence.

Full 33-script plain --changed: 327.3s -> 181.8s / 178.5s / 172.7s, 0 failures,
inside a 300000ms budget. Also states the per-script guard's derivation.

* no-mistakes(review): Align contract-unit concurrency cap with recorded proof

* no-mistakes(document): Record final changed-suite performance evidence

* fix(bin): keep an empty changed selection clean on stock macOS Bash

Under set -u, bash 3.2 treats "${arr[@]}" on an EMPTY array as an
unbound-variable error, while bash 4.4+ makes it a harmless no-op. The
concurrency work removed the early exit for an empty selection, so execution
fell through to the unguarded existence loop: on stock /bin/bash 3.2.57 a
contributor who changes only documentation and runs --changed got

  bin/fm-test-run.sh: line 1713: SCRIPTS[@]: unbound variable

with exit 1 and no summary, instead of a clean total=0 pass.

Restore the early exit, and guard every remaining array expansion reachable
with an empty selection. The reported duration is real elapsed invocation
time rather than a hardcoded zero, so a selection phase that outran
--max-wall-ms still fails.

Verified on this host with /bin/bash 3.2.57: exit 1 with the unbound-variable
error before, exit 0 with FM_TEST_SUMMARY total=0 after.

* no-mistakes(document): Document shell-bound changed-suite performance

---------

Co-authored-by: Kun Chen <kun-1@kunchenguid.com>

* feat(bin): publish per-home summary ledgers (#3222)

* feat(bin): publish per-home summary ledger

* no-mistakes(review): Bound and schedule home summary publication

* no-mistakes(review): Prove recurring watcher summary refresh cadence

* no-mistakes(review): Bound refresh workers and publish durable spawns

* no-mistakes(review): Fix atomic kill process-group coverage

* no-mistakes(review): Bound state initialization within refresh timeout

* no-mistakes(document): Document recurring bounded home-summary publication

* no-mistakes(review): Bound and log all best-effort refresh failures

* no-mistakes(review): Harden cadence and timeout regression coverage

* no-mistakes(document): Document home-summary runtime tuning

* no-mistakes(lint): Fix direct exit-code check in refresh test

* no-mistakes(ci): Fixed remote secondmate retirement recreating the deleted home: teardown now skips side-band summary refresh when its overridden state directory was removed. Verified with remote lifecycle E2E, teardown tests, home-summary tests, ShellCheck, and git diff checks

* no-mistakes(document): Clarify atomic home-summary publication guarantee

* fix(pi): gate first provider call on startup context (#3158)

* fix(pi): gate first call on startup context

* no-mistakes(document): Correct Pi startup prerequisite verification date

* no-mistakes(review): Captain, fix startup process-group retirement after leader exit

* no-mistakes(review): Captain, release reload exit listeners on shutdown

* no-mistakes(review): Captain, complete startup exit lifecycle ownership

* no-mistakes(review): Captain, release empty startup process-group ownership promptly

* no-mistakes(review): Captain, supervise startup ownership and restore failure fallback

* no-mistakes(review): Captain, restore live Pi supervisor execution

* no-mistakes(document): docs: clarify Pi startup prerequisite delivery

* fix(pi): restore Pi 0.84.4 renderer compatibility (#3261)

* fix(pi): restore 0.84.4 adapter compatibility

* no-mistakes(review): Restore Pi collapsed and expanded outcome parity

* no-mistakes(review): Preserve Pi stock previews through capability probing

* no-mistakes(document): Document Pi 0.84.4 renderer compatibility

* fix(bin): keep home-summary publication from starving supervision (#3273)

* fix(bin): keep home-summary publication bounded and off the watcher beat

A home whose tasks had accumulated ordinary status history could not publish
state/home-summary.json at all, and every attempt starved the watcher's
liveness beacon while it failed silently.

The producer's per-task open-decision fold spent tens of milliseconds per
status line on a bash 3.2 global bracket-class substitution used only as a
blank-line guard. On a real home that made the whole ledger producer take
minutes, so publication burned its full FM_HOME_SUMMARY_TIMEOUT on every
attempt and never completed. Replace that guard with an equivalent case glob
in the one fold owner, which both the whole-file and cursor-backed folds use.

Bound each per-task current-state read in the snapshot with
FM_SNAPSHOT_CREW_STATE_TIMEOUT. For a remote secondmate that read crosses ssh,
whose dead-peer detection deliberately never kills a slow-but-alive remote
command, so nothing else bounded it.

Detach the watcher's two publication triggers from the poll loop. The loop
owns the beacon that fm-guard.sh reads as proof supervision is alive, and an
inline publication put up to a full publication deadline between two beacon
touches. A single in-flight publication is tracked so a slow one cannot
accumulate clones.

Report a repeatedly failing publication at session start. Publication stays
deliberately non-fatal to its caller, so the existing bounded home-local
failure record is now surfaced as a HOME_SUMMARY bootstrap line once the
ledger is absent or stale and failures have been recorded since.

* no-mistakes(review): Preserve home-summary failure attempt ordering

* no-mistakes(review): Enforce durable home-summary single-flight and ordering

* no-mistakes(review): Derive failure ordering from publication boundaries

* no-mistakes(review): Restore best-effort failure logging and publication scoping

* no-mistakes(review): Make ordering regression sensitive to one failure

* no-mistakes(document): Correct HOME_SUMMARY diagnostic guidance

* fix(bin): prevent routine updates from hiding actionable status (#3268)

* fix(supervision): classify the appended status span, not the last line

An actionable project update could be classified as routine and absorbed, so
a worker that raised a decision, hit a blocker, failed, or finished stalled
silently with the captain never told.

Trigger, mask, symptom. A worker appends a captain-relevant event
(`needs-decision`, `blocked`, `failed`, `done`). Any later routine append -
a `working:` progress note - lands before the supervisor classifies the
batch; the watcher's 30s signal-grace linger exists precisely to coalesce a
status write with the same turn's turn-end, so this window is ordinary
rather than rare. Both supervisors then asked "is the LAST line
captain-relevant?", read the routine line, and absorbed the wake. The
`.seen-*` suppressor advanced either way, so nothing ever re-read the event.
When the crew was also provably working, the no-verb fallback absorbed it
too, which is why the event disappeared completely instead of surfacing late.

Reproduced end to end against a real watcher before any change: with the
trailing `working:` append the watcher never exits and the wake queue stays
empty; with that one line removed - the smallest counterfactual - the same
`needs-decision` surfaces and queues. The away-mode daemon's `classify_signal`
returns `self|routine signal` for a `blocked:` event under the same mask,
which is the worse case because no captain is present to notice.

The proven path was already in the tree: `status_open_decisions` fixed this
exact masking for the durable decision fold, and its header states the rule -
reading an append-only event log last-event-wins cannot represent an earlier
event that a later unrelated line moved past. The classification path was
never migrated to that read model. That is the earliest divergence, and the
fix is to migrate it rather than to special-case the symptom.

`status_span_first_actionable` in bin/fm-classify-lib.sh is the new single
owner: it reads the bytes at or after a caller-supplied position and returns
the first still-live captain-relevant event. Each supervisor supplies its own
position, because the always-on watcher and the away-mode daemon classify the
same stream independently and must not share one cursor: the watcher reads
the size already recorded in its `.seen-*` signature (no new state) and its
`.hb-surfaced-<task>` backstop marker, and the daemon its
`.subsuper-seen-status-<task>` marker. Those two markers held the escalated
line and now hold the escalated-through byte offset, which also removes a
second defect in the same code - content dedup silently swallowed a genuinely
new event whose text repeated an older one. An absent, malformed, or
past-the-end position reads the whole log, so uncertainty surfaces events
rather than losing them, and a marker an older build wrote as a status line
reads that way too. Status logs are only ever appended to, including across a
reused task id, so a recorded position keeps its meaning.

A `needs-decision`/`blocked` event in the span is retired only when the
whole-file fold proves its key closed; `status_open_decisions` stays the sole
owner of that rule, so same-key reopening and reserved-key namespaces need no
second implementation here. Every other captain-relevant event is terminal
and always actionable.

Both backstops now walk every status log instead of only those whose last
line looks captain-relevant, because the event a backstop most needs to catch
is exactly one a later append has moved past. That leaves
`scan_captain_relevant_statuses` with no callers, and it is removed rather
than left as a working copy of the defective read model.

Regression coverage exercises the classifier and both supervisors through
their own interfaces: the masked decision, the captain-reported
release/install completion followed by cleanup chatter, and the away-mode
blocker all surface; a routine append after an already-classified event stays
absorbed, so the fix does not convert ordinary progress into wakes; and the
heartbeat backstop catches a masked event the per-wake path missed. The
end-to-end watcher tests drive a real fm-watch.sh with the crew reported as
provably working, which is the configuration that made the original stall
silent.

Two further claims in the supplied RCA are deliberately not patched here.
"Repeated operational recoveries produced all-clear replies despite known
actions" is downstream of this same cause, not an independent contributor: an
all-clear reply is the documented response when the specific event needs no
action, so a classification that wrongly reported "no action" produces it, and
correcting the classification removes it. "The project was subjected to
validation requirements outside its accepted path" is delivery-mode selection,
which AGENTS.md section 7 owns; no code changed here touches it, so it is out
of scope.

Harness and backend axes were inspected rather than assumed: nothing in this
path reads a vendor-emitted signal. The status log's format and append
protocol are Firstmate's own and identical for every harness, and no runtime
backend reads or writes `.status` files (`bin/backends/*` contain no reference
to them). The surrounding triage's only backend touchpoints - pane capture and
the authoritative crew-state read - are unchanged. No live-harness guard
applies and no per-harness verification record changes.

Verified with `bin/fm-lint.sh`, `bin/fm-doc-audience-check.sh`, and
`bin/fm-test-run.sh --changed --base origin/main`.

* no-mistakes(review): Prevent status races and surface classification failures

* no-mistakes(review): Surface unreadable signals and preserve AFK endpoints

* no-mistakes(review): Route stale wakes through captured span verdicts

* no-mistakes(review): Retire supervision offsets with reused task state

* no-mistakes(review): Bind status offsets and preserve live decision origins

* no-mistakes(review): Strengthen status identity with verified birth time

* no-mistakes(review): Skip turn-end markers during status classification

* no-mistakes(review): Preserve status presentation with platform-strength identities

* no-mistakes(review): Retain failed wakes and advance routine checkpoints

* no-mistakes(review): Surface all events and retain unreadable wakes

* no-mistakes(review): Treat absent status logs as successful empty spans

* no-mistakes(review): Bound repeated classification failures with durable receipts

* revert(supervision): drop the failure-receipt and durable-retry machinery

Captain-authorized revert to the minimal fix. Review rounds added a durable
failure-receipt store and wake-retention-on-failure to bound repeated
classification failures. That machinery grew larger than the fix it protected
and kept producing its own defects: an unreadable log still looped forever
because the always-on watcher never consulted the receipt, and the receipt was
persisted before its diagnostic was durably queued, so a crash in between
swallowed the alarm outright. Those two defects go away with the code that
contained them rather than being repaired.

Removed: the failure-receipt path, fingerprint, record and clear helpers and
their retirement bookkeeping; the retention of a durable wake when
classification fails; and the error-propagation plumbing in both supervisors
that existed only to drive them.

Kept, because it is the accepted fix rather than the declined machinery: span
classification of the events appended since a supervisor last looked, in both
supervisors and both backstops; reporting every actionable event in a span and
committing a position only through what was reported; naming the live opening of
a reopened decision; treating an absent log as ordinary and an unreadable one as
worth reporting; the non-.status filter; and the platform-strength identity that
guards a position commit without failing a read.

Replacement behavior for a log that cannot be classified: report it once, do NOT
advance the classification position so the content is classified from where it
stopped once readable, and DO advance the wake signature so the report is
bounded to one per distinct file state. Reporting and reading are different acts:
telling the captain about a log is not the same as having read it, and only the
latter may move a classification position.

The residual risk is explicit and accepted: there is no guaranteed automatic
retry inside a crash-mid-read window, and the locked session-start replay of the
durable queue covers it. That rationale is recorded at mark_escalated_seen so a
future reader does not reintroduce the retry as a "missing" guarantee.

Also fixes lint failures that arrived with the review-fix commits and were never
caught because the run never reached its lint step: an unfollowable conditional
source directive, a second unquoted-expansion site left after a call was split
across lines, cleanup of the file being read inside its own read loop (restructured
to one post-loop teardown rather than three in-loop copies), stub functions in
tests that are invoked indirectly, and a test local left unused when its
assignment was replaced by a helper. bin/fm-lint.sh passes on the default branch,
so these were introduced here.

Verified with `bin/fm-lint.sh`, the end-to-end masked-decision and away-mode
reproductions, and `bin/fm-test-run.sh` over the supervision, wake-queue,
wake-drain, watch-arm and inactive-reconcile suites (6 scripts, 0 failures).

* no-mistakes(review): Correct classification failure contract documentation

* no-mistakes(review): Bound unreadable status reports without skipping classification

* no-mistakes(review): Preserve escalation markers when buffering fails

* no-mistakes(review): Detect permission recovery without advancing classification

* no-mistakes(document): Document status span classification contract

* no-mistakes(ci): Fixed CI failures by lazily loading classification helpers in fm-wake-lib, preserving minimal recovery/remote fixtures; added a public current-status marker helper and updated behavioral fixtures to use the v2 marker contract; resolved ShellCheck variable collisions in fm-control and fm-public-followup-lib. Verified fm-lint, bash syntax, fm-control, public-followup, wake-queue, send-resolve-key, captain-hold, pending-reply, remote-reply, remote-backlog-handoff, turnend-guard, and Claude autoarm tests. The Pi branch suite reached a separate local stock-render mismatch under Node 24; its CI-reported missing-classifier failure path is fixed

* no-mistakes(review): Escalate blockers while preserving declared-wait cadence

* no-mistakes(review): Clarify actionable events override wait self-handling

* no-mistakes(review): Surface rejected decisions and dangling status links

* no-mistakes(document): Document reserved-key reconciliation classification

* no-mistakes(ci): Fixed the flaky portable serial CI test by modeling the retained staging directory as genuinely owned by a live process and aging both fixtures deterministically. This removes scheduler-timing dependence while verifying the worker reaps abandoned staging and preserves live staging. Verified with fm-remote-transport-lanes.test.sh, bin/fm-lint.sh, bash syntax, and git diff --check

* no-mistakes(document): Correct away-mode classification documentation

* docs(skills): split harness adapter operations reference (#3289)

* docs: split harness adapter operations reference

* no-mistakes(review): Fix harness adapter routing and ownership contracts

* no-mistakes(review): Prune duplicate harness adapter ownership prose

* no-mistakes(review): Fix default effort routing and Grok max semantics

* no-mistakes(review): Remove source-only routing test and duplicate semantics

* no-mistakes(review): Add local harness adapter instruction evaluation

* no-mistakes(review): Fix harness evaluation gating and change mapping

* no-mistakes(test): Captain, require explicit harness instruction evaluator model

* no-mistakes(document): Fix harness adapter documentation references

* test: centralize shared shell fixtures (#3296)

* test(fixtures): share fake-toolchain and spawn-world builders

Future tests can start from tests/fixtures.sh instead of copying stubs, and a
no-mistakes version-floor bump is one constant rather than a multi-file edit.

Migrated this round: fm-busy-adapter-wiring, fm-spawn-pool-base-freshen,
fm-grok-harness, fm-tangle-guard, fm-gate-refuse, fm-spawn-dispatch-profile.
Left for opportunistic migration: remaining make_spawn_fakebin copies
(trace-context, kimi, muse, backend), the make_stubs send cluster, and the
fake no-mistakes version banners in bootstrap/session-start/secondmate suites.
Did not touch tests/fm-pr-check-security.test.sh.

* no-mistakes(review): Prevent fake SSH test from blocking on stdin

* no-mistakes(document): Clarify shared fixture documentation

* no-mistakes(ci): Fixed the flaky watcher triage test by extending its startup-sensitive timer-repair wait from 3s to 10s, matching existing loaded-runner budgets. Verified with the full tests/fm-watch-triage.test.sh suite, bash syntax validation, and git diff checks

* no-mistakes(ci): Fixed portable serial shard 4 by updating the inactive-reconcile fixture to prime status through the public fm_wake_status_mark_current API, ensuring classifier helpers load correctly and preventing the idle watcher from exiting. Verified the test three consecutive times, ran fm-test-fixtures, ShellCheck, bash syntax checks, and git diff checks. The outer no-mistakes executor can now bind a fresh attestation to the new head

* no-mistakes(ci): Added behavioral coverage proving the shared spawn tmux fixture defaults an unset FM_FAKE_PANE_PATH to empty. Verified the fixture suite, ShellCheck, syntax/diff checks, and all six migrated test suites; all passed. The outer executor can now bind a fresh no-mistakes attestation to the updated head

* refactor: retire legacy PR-check migration machinery (#3299)

* feat(bin): retire completed PR-check migration machinery

Every registered home already carried both completion markers, and no
installer still creates pre-migration checks. Remove the one-time migrate
script, its bootstrap/watch/teardown/docs surface, and migration-path tests
without weakening live check-trust or PR-poll authentication.

* no-mistakes(review): Restore live PR-check security coverage

* no-mistakes(document): Refresh retired PR-check documentation

* no-mistakes(ci): Fixed both failing CI checks. Updated inactive-reconcile setup to use the public status-marking interface, preventing false watcher exits. Made remote-job shutdown deterministic by stopping the complete worker tree before tampering. Verified both affected test suites, repeated inactive reconciliation, shell syntax, and git diff checks

* feat(bin): add trusted process-event extension bindings (#3247)

* feat(extensions): bind trusted external process-event adapters

* no-mistakes(review): Enforce owner and remote-home conformance

* no-mistakes(review): Enforce serialized remote extension package lifecycle

* no-mistakes(review): Enforce identity-conditional extension retirement

* no-mistakes(review): Serialize extension retirement and recover crash cuts

* no-mistakes(review): Unify retirement worker and lifecycle lock ownership

* no-mistakes(review): Harden extension lifecycle retirement serialization

* no-mistakes(review): Unify extension registration and overridden-state lifecycle boundaries

* no-mistakes(document): Clarify built-in-only captain answer routing

* no-mistakes(lint): Captain: fix extension binding ShellCheck findings

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes(review): Use isolated UID mapping for owner conformance

* no-mistakes(review): Captain: remove forbidden CI ownership wrapper

* no-mistakes(review): Serialize extension binding publication

* no-mistakes(review): Document ordinary CI owner-fixture exclusion

* no-mistakes(review): Quarantine orphaned handshake descendants

* no-mistakes(test): Fix orphan attribution

* no-mistakes(test): Harden process tracker baseline

* no-mistakes(test): Harden detached descendant attribution

* no-mistakes(test): Use exact invocation-group cleanup

* no-mistakes(test): Bound remote conformance transport crossings

* no-mistakes(test): Parallelize isolated extension conformance tests

* no-mistakes(test): Lifecycle suite still exceeds deadline

* feat(extensions): bind trusted external process-event adapters

* no-mistakes(review): Enforce owner and remote-home conformance

* no-mistakes(review): Enforce serialized remote extension package lifecycle

* no-mistakes(review): Enforce identity-conditional extension retirement

* no-mistakes(review): Serialize extension retirement and recover crash cuts

* no-mistakes(review): Unify retirement worker and lifecycle lock ownership

* no-mistakes(review): Harden extension lifecycle retirement serialization

* no-mistakes(review): Unify extension registration and overridden-state lifecycle boundaries

* no-mistakes(document): Clarify built-in-only captain answer routing

* no-mistakes(lint): Captain: fix extension binding ShellCheck findings

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes(review): Use isolated UID mapping for owner conformance

* no-mistakes(review): Captain: remove forbidden CI ownership wrapper

* no-mistakes(review): Serialize extension binding publication

* no-mistakes(review): Document ordinary CI owner-fixture exclusion

* no-mistakes(review): Quarantine orphaned handshake descendants

* no-mistakes(test): Fix orphan attribution

* no-mistakes(test): Harden process tracker baseline

* no-mistakes(test): Harden detached descendant attribution

* no-mistakes(test): Use exact invocation-group cleanup

* no-mistakes(test): Bound remote conformance transport crossings

* no-mistakes(test): Parallelize isolated extension conformance tests

* no-mistakes(test): Lifecycle suite still exceeds deadline

* no-mistakes(review): Split extension conformance and forward remote transfer input

* no-mistakes(review): Forward malformed remote payloads through fm-on

* no-mistakes(review): Bound extension coordinator failure cleanup

* no-mistakes(test): Skip repeated orphan sweep in coordinator children

* no-mistakes(test): Queue isolated extension sections through bounded workers

* no-mistakes(test): Bound extension coordinator lane cleanup

* no-mistakes(test): Split remote lifecycle coordinator sections

* no-mistakes(test): Coordinator probes pass; aggregate deadline remains

* no-mistakes(test): Launch extension sections concurrently

* no-mistakes(test): Fix coordinator marker publication

* no-mistakes(test): Stabilize extension binding coordinator timing

* no-mistakes(lint): Fix extension binding ShellCheck warnings

* fix(extensions): prove invocation cleanup before retirement

* no-mistakes(review): Harden process-event inbox confinement

* no-mistakes(review): Preserve legacy capture parity

* no-mistakes(review): Protect external registry staging

* no-mistakes(test): Stabilize bounded extension conformance aggregate

* no-mistakes(document): Document external evidence confinement

* no-mistakes(ci): CI phase fixed. The failure was a flaky fixture in `tests/fm-remote-transport-lanes.test.sh`: its “fresh/in-use” staging directory had no live owner identity, so the real worker correctly reaped it once the 1-second age boundary elapsed on slower CI. The fixture now records the active test shell’s exact PID/start identity and cleans those records before removal. Verified: `bash tests/fm-remote-transport-lanes.test.sh` exits 0 with all checks passing; `git diff --check` passes. Provider check retrieval was also retried successfully, resolving the selected manual CI finding. Changed file: `tests/fm-remote-transport-lanes.test.sh`

* no-mistakes(review): Harden extension staging and lifecycle reservation

* no-mistakes(review): Harden external staging and lifecycle reservations

* no-mistakes(review): Wire capture helper into remote conformance

* no-mistakes(review): Pin external capture handoff and signal failures

* no-mistakes(review): Bind pinned capture authority to inherited descriptor

* no-mistakes(review): Harden descriptor-bound capture authority

* no-mistakes(review): Harden core capture reservation authority

* no-mistakes(review): Harden capture reservation boundaries

* no-mistakes(review): Harden capture reservations and cleanup

* no-mistakes(review): Harden capture handoff and reservation cleanup

* no-mistakes(review): Bind capture handoff to claim descriptors

* no-mistakes(review): Release lifecycle locks after host crashes

* no-mistakes(review): Pin reservation recovery to recorded state roots

* no-mistakes(review): Reject control bytes in claim state roots

* no-mistakes(test): Stabilize extension capture descriptor handoff

* no-mistakes(document): Document extension capture authority boundary

* no-mistakes(lint): Fix ShellCheck extension binding warnings

* no-mistakes(ci): CI phase result: fixed `bin/fm-procevent.sh` by initializing the shared `capture_state` sentinel for built-in adapters under `set -u`. This prevents normal built-in captures from aborting before publication. Verified: `bash -n bin/fm-procevent.sh` and `git diff --check` pass. The focused process-event suite was run locally but stopped earlier at a local detached-runner claim failure (`reconcile never claimed the registered source`), before the CI-reported post-capture path; CI evidence confirms the fixed unset-variable failure affected the failing remote, board, watcher, and process-event checks

* no-mistakes(document): Correct extension namespace creation timing

* no-mistakes(lint): Initialize capture locals for ShellCheck

* fix(bin): deliver safety rules to promoted workers (#3269)

* fix(bin): deliver the real definition of done to a promoted scout, and ban --yes

A promoted scout used to receive a free-form placeholder instead of the
mode-specific Definition of done a briefed ship worker gets, so it never
saw the ask-user escalation rule or the --yes prohibition. That gap is the
concrete reason one incident's worker drove validation with --yes and
answered its own ask-user findings.

- Add bin/fm-dod-lib.sh as the single owner of a ship task's mode-specific
  Definition of done, rendered by both bin/fm-brief.sh and bin/fm-promote.sh
  so the two contracts cannot drift.
- bin/fm-promote.sh now writes data/<id>/ship-instructions.md carrying the
  scratch inventory, clean base, ship branch, and that Definition of done,
  and prints the fm-send.sh command that delivers it.
- State the --yes ban as a prohibition rather than a preference, without
  claiming an enforcement the tool does not provide.
- Cover both through the real promotion and brief paths in
  tests/fm-task-delivery.test.sh and tests/fm-brief.test.sh.

* no-mistakes(review): Publish promotion instructions before committing task state

* no-mistakes(review): Supersede conflicting scout delivery rules after promotion

* no-mistakes(review): Reject invalid promotion instruction destinations

* no-mistakes(document): Align documentation with promotion delivery contracts

* no-mistakes(ci): Fixed both CI findings. Promoted workers now receive an explicit worktree-isolation check before branch creation, with instructions to stop and escalate if they are in the primary checkout. Updated behavioral coverage to verify the delivered promotion payload, and aligned the ask-user authority test with the new fleet-wide --yes prohibition. Verified with bin/fm-lint.sh, tests/fm-brief.test.sh, tests/fm-ask-user-authority.test.sh, tests/fm-task-delivery.test.sh, and git diff --check

* no-mistakes(ci): Made tests/fm-ask-user-authority.test.sh executable so the modified colocated behavioral test runs directly like the surrounding test suite. Verified bin/fm-lint.sh, fm-brief, ask-user-authority, and task-delivery tests; all pass. git diff --check is clean

* no-mistakes(ci): Strengthened tests/fm-task-delivery.test.sh to behaviorally verify that real promotion and brief generation deliver byte-identical Definition-of-done blocks for all three modes. Verified tests/fm-task-delivery.test.sh, tests/fm-brief.test.sh, bin/fm-lint.sh, and git diff --check. The outer pipeline can now commit and attest the updated head

* no-mistakes(ci): Fixed promotion isolation instructions so any checkout other than the launched disposable worktree requires escalation, including another non-primary worktree. Updated behavioral coverage against the delivered promotion payload. Verified fm-task-delivery, fm-brief, fm-ask-user-authority, full fm-lint/ShellCheck, workflow lint, and git diff checks

* fix(bin): present Lavish feedback as structured output (#3321)

* fix(bin): present complete Lavish board feedback as structured output

Give the Lavish adapter a read-only presentation so a handler sees every
annotation and the session-ending tag=message as its own field, instead of
grepping a truncated raw capture.

* no-mistakes(review): Preserve unquoted messages and prioritize captain prose

* no-mistakes(document): Document structured Lavish result reads

* no-mistakes(ci): Fixed Lavish `read` completeness: rows missing declared fields are excluded from presented items, counted as malformed, and force `complete: no`. Added behavioral regression coverage through the adapter interface. `bin/fm-lint.sh`, syntax checks, and focused valid/malformed read checks passed. The portable-serial failure was an unrelated secondmate cooldown timing flake

* fix: keep task records and backlog transitions atomic (#3322)

* fix(records): pair backlog transitions with the record that moves

Dispatch and completion each moved a task's physical record and its
backlog row as two independently timed steps, so a crash or a forgotten
follow-up could leave the two disagreeing: a record with no in-flight
row, an in-flight row with no owner, or a finished task still shown in
flight.

Fold each backlog transition into the script that performs the physical
change, under the per-task lock it already holds and before it reports
success. Dispatch moves the item to In flight after publishing the task
record and fails loudly, removing its provisional record, when that
transition cannot land. Completion records an authoritative close and
performs it before removing the record, so an interrupted cleanup can be
finished later, and its closing message now confirms what already
happened rather than instructing a future step.

Add a same-home reconciliation sweep to session start so a home that was
interrupted mid-transition settles its own books on restart, replaying a
recorded close and restoring an in-flight row it already owns a worker
for. It never reads or writes another home; the fleet snapshot and the
cross-home nudge stay as backstops.

Close records are validated before they are trusted: the file is read as
raw bytes and rejected outright when it carries a NUL or other control
byte, every field must be well formed and non-duplicated, the id must
match the record it was found under, the data location must resolve
inside this home, and each close argument must carry a permitted,
well-formed value. Writer and reader share one validator so a record
this home publishes always remains replayable, independent of locale.

Homes configured for a manual backlog, and homes with no backlog at all,
stay exempt and are unaffected.

* no-mistakes(review): Remove stale bootstrap migration helper invocation

* no-mistakes(review): Preserve pending closes and narrow signal deferral

* no-mistakes(review): Record close before destructive teardown

* no-mistakes(review): Refuse pending closes before creating resources

* no-mistakes(review): Guard relaunches and preserve cleanup warnings

* no-mistakes(review): Reject symlinked records and clarify cleanup guidance

* no-mistakes(review): Align dispatch eligibility and protect close replay

* no-mistakes(review): Unify exact task incarnation parsing

* no-mistakes(review): Render resolved configured backlog path

* no-mistakes(review): Harden transition path boundaries against symlinks

* no-mistakes(review): Validate lifecycle state before resource actions

* no-mistakes(review): Enforce transition tooling and continuous state locks

* no-mistakes(review): Consolidate same-home lifecycle file boundaries

* no-mistakes(review): Enforce canonical lifecycle containment and tooling contracts

* no-mistakes(review): Reject final-component lifecycle record symlinks

* no-mistakes(document): Document lifecycle record path boundaries

* no-mistakes(lint): Quote literal done tokens in atomicity tests

* no-mistakes(ci): Fixed all PR-caused CI failures: bootstrap now treats an absent state directory as an empty fresh home while retaining unsafe-state checks; nested remote secondmate retirement accepts records already removed with the retired home; teardown fixtures now provide valid data/manual-backend configuration; and the manual reminder assertion checks the configured absolute backlog path. Verified the reported tests, remote lifecycle E2E, backlog atomicity suite, Bash syntax, diff checks, and ShellCheck. The documented pre-existing captain-hold failure was intentionally untouched

* no-mistakes(ci): Fixed Behavior portable serial 3 by adding `od` to the teardown test’s lsof-free PATH fixture. The new close-record validator legitimately requires `od`; its omission caused teardown to fail before process-group cleanup and stall the shard. Verified the full `tests/fm-teardown.test.sh` suite passes, plus Bash syntax, ShellCheck, and `git diff --check`

* no-mistakes(ci): Fixed close replay to durably retain incomplete-cleanup evidence before removing task metadata. Subsequent retries now emit the reconciliation warning even after a backlog probe or close failure. Updated the behavioral regression and verified the full atomicity suite under stock macOS Bash 3.2, plus shellcheck and diff checks

* fix(records): validate record bytes without an uncurated tool

The byte validation added for close records and directory paths shelled
out to od. The spawn and teardown lifecycle runs under a curated command
set that deliberately excludes it, so on any restricted PATH the check
could not run, the data directory read as unresolvable, and dispatch and
cleanup refused - wedging the lifecycle rather than protecting it.

An earlier attempt made the failing test pass by adding od to that
curated set. That fixed the test to agree with the defect and quietly
widened the contract the fixture exists to pin, so it is reverted here.

Inspect the bytes with perl instead, which is already in the curated set
and already used in this repo for the same portability reason. The
emitted values are identical to od's, so the rejection semantics are
unchanged: NUL and other control bytes are still refused, legitimate
paths containing spaces or non-ASCII characters still round-trip, and
the check stays independent of the process locale.

The restricted-PATH teardown case now passes because the validator no
longer needs od, not because the fixture was loosened.

* no-mistakes(review): Enforce dispatch eligibility and atomic remote record publication

* no-mistakes(document): Document dispatch eligibility and cleanup alerts

* fix(bin): contain promote and Relay metadata publishing (#3342)

* fix: publish promote and Relay meta rewrites through contained replace

Bare mv still rewrote live task records in place, so a symlink meta could
be followed to a target outside state/. Route those field rewrites through
the shared publisher and drop the unused library aliases.

Co-authored-by: Cursor <cursoragent@cursor.com>

* no-mistakes(review): Refuse dangling symlinks during X metadata clear

* no-mistakes(review): Refuse unsafe metadata before follow-up and promotion side effects

* no-mistakes(review): Exercise dangling symlink refusal through clear helper

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(bin): absorb turn-end wakes during bounded pane churn (#2877)

* fix(watch): absorb a turn-end whose pane churned since the previous poll

The watcher's "absorb a benign turn-end when the crew is provably working"
triage was structurally unreachable for any harness whose semantic busy state
has no verified source. crew_absorb_class only reports working for an actively
running no-mistakes step or an exact busy verdict, and bin/fm-crew-state.sh can
only answer unknown for such an adapter, so codex crewmates surfaced a signal
wake at every turn boundary with nothing to act on - a full supervisor drain,
inspect and acknowledge turn per worker turn, scaling with the number of workers
in flight and drowning the wakes that matter in identical noise.

Widen the proof rather than bound the wake rate. A wake carrying only bare
turn-ended markers is now also benign when the task's pane content changed since
the previous poll, compared against the same state/.hash-* marker the staleness
backbone already records and already trusts as liveness. That evidence claims no
harness semantics, so it fabricates no busy verdict an adapter has not earned,
and it needs no adapter cooperation.

Absorb stays evidence-driven in both directions. A wake naming any status file
keeps the strict proof, every captain-relevant verb still surfaces immediately,
and an unresolvable task, a missing prior hash, a failed or empty capture, or an
unchanged pane all surface exactly as before. The absorb defers rather than
swallows: a crew that has stopped renders nothing further, so its now-static pane
surfaces through the staleness backbone within a poll or two. Bounding the
surfacing rate instead would have suppressed genuinely stopped workers.

The derivation lives with the .hash-* marker format in bin/fm-watch.sh, which
owns it, and costs one bounded capture reached only for a no-verb turn-end whose
crew is not already provably working.

* no-mistakes(review): Captain, guard pane-churn absorption from collisions and secondmates

* no-mistakes(review): Captain, make watcher marker identities injective

* no-mistakes(review): Captain, isolate ambiguous legacy markers and restore Herdr sourcing

* no-mistakes(review): Captain, localize pane-churn collision guard

* no-mistakes(review): Captain, reject malformed pane-churn hashes

* no-mistakes(document): Document pane-churn turn-end evidence

* no-mistakes: apply CI fixes

* fix(watch): gate and bound the pane-churn turn-end absorb

Make the pane-churn form of positive work evidence opt-in per home and
bound how long it may defer one endpoint's bare turn-ends.

Absorbing a bare turn-end on pane churn is now reached only when the home
creates config/turnend-churn-absorb. The other two proofs read a verdict
the harness itself vouches for, while this one infers execution from
rendered bytes, so widening the absorb is a home's choice rather than a
default every fleet inherits. With the flag absent the predicate returns
on its first line and triage is unchanged.

Churn and pane staleness read the same pane, so neither can be the
other's only backstop. A pane that renders continuously never presents
the two consecutive identical hashes the staleness backbone needs, so an
unbounded churn absorb left a worker that had genuinely stopped behind
such a renderer with no path to surface at all. One endpoint's turn-ends
may now ride churn evidence for at most FM_TURNEND_CHURN_ABSORB_SECS,
tracked in state/.churn-since-*, after which the wake surfaces and the
window restarts. The bound is evaluated before any .stale- state is
touched, so a wake that surfaces there leaves the staleness backbone's
own classification alone.

Covers both with behavioral tests: the same churning fixture that absorbs
with the flag surfaces and queues without it, and a spent deferral window
surfaces and restarts. The four existing safety guards now run with the
flag enabled so they keep proving their specific guard.

* no-mistakes(review): Fail closed on invalid churn deferral state

* no-mistakes(review): Validate persisted churn deadlines before arithmetic

* no-mistakes(review): Make churn deadlines transactional and bounds safe

* no-mistakes(review): Compose turn-end evidence per task from one snapshot

* no-mistakes(review): Restore strict turn-end fallback guards

* no-mistakes(document): Clarify pane-churn supervision documentation

* no-mistakes(lint): Fix watcher arithmetic lint issues

* no-mistakes: apply CI fixes

* no-mistakes(document): Clarify pa…
)

* fix(bin): verify the real GitHub merge outcome instead of reporting an unproved merge (#3064)

* fix(pr): verify GitHub merge outcome

* no-mistakes(review): Captain, fixed forge-only merge verification, queue guidance, metadata propagation

* no-mistakes(document): Correct forge-specific merge documentation

* no-mistakes(review): Captain: forge-only queue fix, focused tests pass

* no-mistakes(review): Captain: suppress closed-state guidance and prove parent regression

* no-mistakes(review): Captain: remove history proof; retain executable regressions

* no-mistakes(document): Clarify GitHub recording timing in architecture docs

* no-mistakes(document): Clarify outcome-aware PR merge recording documentation

* no-mistakes: apply CI fixes

* Revert "no-mistakes: apply CI fixes"

This reverts commit c326cfa9430c6173eedc8ff7f27d19d0552daf01.

The automatic CI repair round removed the up-front `gh` prerequisite check
while keeping the `gh` dependency: `bin/fm-pr-merge.sh` still calls
`gh api graphql` for the outcome read and `gh api` for the branch-rules read.
That left the same hard requirement without the clear named error, and review
immediately raised a new finding for exactly the failure the check prevents -
`gh-axi pr merge` landing the merge while the follow-up read fails, so the PR
metadata is never recorded.

The check is also symmetric with the GitLab arm directly above it, which
already refuses up front when `glab` or `jq` is missing, on the stated
principle that a missing tool should be a named prerequisite rather than a
merge that is armed and then refused for an unexplained reason.

The workflows this round was chasing sit at `action_required` because this is
a fork pull request; no code change can turn them green.

* fix(pr): keep PR bookkeeping when a merge outcome read fails

On the GitHub path a merge call that returned success was followed by
`github_read_outcome || exit 1`, so a transient API failure, rate limit,
or network blip during the read dropped out of the script before
`record_pr_metadata` ever ran. The merge could have landed while `pr=`
went unrecorded and the merge poll was never armed - bookkeeping lost on
a real merge. The failure path just above already recorded metadata
before exiting, so the error path was more careful than the success one.

Record the PR before that refusal. Recording arms the later merge poll
and is not a success claim, which is the same reasoning that keeps
`record_pr_metadata` on the gh-axi failure path. The refusal itself is
unchanged: exit stays non-zero and the message still names the concrete
observed state. Metadata is withheld only when the read succeeds and
proves the pull request neither merged nor queued.

Pin it with a case that stubs `gh api graphql` into failure after a
successful `gh-axi pr merge`, asserting both the non-zero exit and the
recorded metadata.

* no-mistakes(review): Aggregate queue rules and report conflicts explicitly

* fix(pr): keep the merge abstraction reachable and its bookkeeping intact

Two holes remained in the outcome-verified GitHub merge path, both on
installations where gh-axi is present but gh is not.

The verification preflight refused before bin/fm-pr-merge.sh ever reached
the configured gh-axi merge abstraction, so an installation without gh
could no longer merge at all. gh-axi now performs the merge unconditionally
and the queue-aware gh read became an optional enrichment: with gh on PATH
its GraphQL view still separates merged from queued, and without gh the
gh-axi view still proves a landed merge while every outcome it cannot prove
refuses.

The PR metadata recording sat behind the outcome read, so a merge that
landed before that read failed lost pr= and its merge poll. Recording now
happens once, before either forge call, which arms the poll without
claiming a landed outcome and leaves teardown a PR identity to verify
against no matter how the read ends.

Rebasing onto main also restored the durable merge-outcome reporting and
the GitLab landed-state confirmation that the conflict resolution dropped.

Tests pin each fix through the executable interface: the merge abstraction
is reached and verified with gh absent, a failed fallback read keeps its
bookkeeping, and a mock that snapshots the task meta during the forge call
proves pr= is recorded before the merge can land.

* no-mistakes(review): fix(pr): de-dup queue methods, fall back on failed gh read, refresh contracts

* no-mistakes(review): fix(pr): quote forge output and explain armed auto-merge on refusal

* no-mistakes(review): fix(pr): claim auto-merge armed only when the forge accepted it

* no-mistakes(review): fix(pr): tell the operator what each GitHub refusal could not observe

* no-mistakes(review): fix(pr): gate every forge-acceptance claim on a successful merge

* no-mistakes(document): align merge docs with verified GitHub outcome contract

* fix(pi): prevent duplicate captain outcome reports (#3184)

* fix(pi): stop reporting one merge to the captain twice

The supervision branch's captain-outcome note told main, unconditionally,
that the note "is not your own earlier output" and to relay it now. When
main had already reported the same event, that assertion was false and the
order turned the correct response - saying nothing new - into a mechanical
re-report, so the captain saw one merge reported twice in 16 seconds.

Two independent changes, both needed:

- The relay instruction is now conditional. It still names itself as a
  supervision outcome so main cannot mistake it for its own earlier answer
  (the silent loss that instruction exists to prevent), and it now lets
  main stay quiet about an outcome it has already given the captain.

- The merge case is closed at its source rather than left to that judgment.
  One merge reaches a home on two independent paths by design - main's own
  permanently main-owned merge poll, and the branch's task-local status
  wake - and main's captain-facing text only reaches the branch's mirror at
  main's turn end, so the branch can escalate before it could possibly see
  the captain was already told. bin/fm-pr-merge-notified.sh answers that
  question from bin/fm-pr-lib.sh's canonical merge-notification marker, so
  the answer holds regardless of mirror timing. A captain outcome naming an
  already-published merge is delivered as the ordinary rendered note
  instead of opening a follow-up turn: still appended, still visible, still
  recorded with the verdict the branch decided, minus the wasted turn.

Any error, timeout, or unreadable state relays the outcome. A duplicate
announces itself; a lost outcome does not.

Regression coverage drives the real delivery path in both directions: a new
outcome must still reach the captain in exactly one follow-up turn even
beside an unrelated published merge, and an already-published merge must
open no second turn while a different PR in the same task still does. The
merge path's real producer and this new consumer are exercised end to end
in tests/fm-pr-merge.test.sh.

Pi-only by construction: the delivery path lives in .pi/extensions, so no
other harness loads it, and the new script only reads existing markers.

* no-mistakes(review): Document accepted latest-marker suppression residual

* no-mistakes(review): Recheck ownership before merge outcome delivery

* no-mistakes(document): Document merge-outcome suppression exception

* refactor(pi): drop the source-level merge suppression, keep the envelope fix

The captain reviewed this branch and judged the source-level duplicate
suppression overly complicated for the problem it solved, and asked for
the change to be reduced to the envelope wording alone.

Remove the mergeIntoMain downgrade path, bin/fm-pr-merge-notified.sh, and
every test and document that existed only for it. What remains is the
conditional captain-outcome instruction: main is told to stay quiet about
an outcome it has already reported and to relay anything else, which
covers the duplicate without a second mechanism.

The silent-loss protection is untouched - the note is still typed,
self-describing, and delivered as one invisible follow-up turn - and the
behavioral tests still assert that, now requiring both halves of the
conditional instruction.

* no-mistakes(ci): Clarified in code comments and owned documentation that this is intentionally an M1-only, model-facing conditional relay fix—not source-level suppression—addressing Greptile’s mistaken scope expectation without changing runtime behavior. Net diff remains 3 files and 27 insertions. Verified with fm-pi-branch-extension tests, fm-lint, doc audience check, and git diff --check; all passed

* no-mistakes(ci): Strengthened the runtime delivery test to verify the captain outcome retains its required self-description and outcome text. Verified with `bash tests/fm-pi-branch-extension.test.sh`, `bin/fm-lint.sh`, `bin/fm-doc-audience-check.sh`, and `git diff --check`; all passed. The outer pipeline can now commit and attest the new head

* fix(bin): prioritize active pipeline-owned crew runs (#3194)

* fix(bin): bind the live pipeline-owned run instead of a superseded failed row

fm-crew-state.sh bound a superseded FAILED no-mistakes run to a task instead
of the LIVE replacement run: the live run's pipeline-owned lane head is not a
git object in the task worktree, so head-equality attribution rejected it and
the coarse runs-list fallback silently continued past the RUNNING row onto an
older failed row whose head equalled the stale worktree HEAD. The home summary
then flipped invalid and Bearings hid the home's live work (F10).

Attribution precedence now follows the daemon's own identity:
- An ACTIVE run for the task's branch binds without head equality while
  branch_sync.state is pipeline_owned (fm_nm_run_is_pipeline_owned_active);
  the pipeline owning the branch is itself the attribution.
- A genuinely failed run with no later run on the branch still reports failed
  through the unchanged head-equality path - real failures are not hidden.
- In the coarse runs scan, an unresolvable head is unknown attribution and
  stops the scan (fm_nm_head_resolvable) instead of falling through to an
  older row; a resolvable-but-mismatched head keeps the historical
  reused-branch skip.

The exemption never applies to a terminal run and requires pipeline_owned
specifically, both pinned by negative-control tests. Fixture shape verified
against the live incident run's real axi status output.

* no-mistakes(document): Updated run-attribution documentation ownership

* fix(pi): surface requested outcomes without replaying fleet events (#3211)

* fix(pi): surface requested supervision outcomes

* no-mistakes(review): Mirror in-flight captain requests before branch dispatch

* no-mistakes(review): Exercise real branch ownership and main outcome access

* no-mistakes(review): Preserve request tails and align verdict guidance

* no-mistakes(review): Preserve complete current captain requests

* no-mistakes(review): Require visible requested outcomes and realistic classification

* no-mistakes(document): Align supervision outcome documentation

* no-mistakes(ci): Fixed Greptile’s runtime-ordering finding. The extension now stages Pi’s authoritative `before_agent_start` prompt before SessionManager persistence and suppresses the later duplicate entry. Updated docs and behavioral regression to reproduce real Pi ordering and verify each prompt is mirrored exactly once. Passed branch-extension tests, supervision tests, strict Pi typecheck, full lint, and diff checks

* no-mistakes(review): Use canonical operational input classification

* no-mistakes(review): Filter legacy operational inputs canonically

* no-mistakes(document): Clarify captain request mirroring boundary

* no-mistakes(ci): Fixed the CI time-boundary failure in tests/fm-public-followup.test.sh by pinning its clock, including context-registry setup. This prevents follow-up fixtures from expiring based on wall time. Verified the full regression suite passes, project-owned lint passes, and git diff checks are clean

* no-mistakes(document): Clarify captain-visible supervision outcome documentation

* feat(bin): add concurrent bounded remote transport lanes (#3210)

* feat(bin): per-home remote transport lanes with cancellation, bounded send, and closed stdin

All remote commands for every home on one host used to serialize through one
single-job-at-a-time worker on one shared queue: a timed-out caller abandoned a
staged job that kept running, retries convoyed behind it, fm-send's remote leg
had no time bound, and staging captured the caller's stdin to EOF so any
fm-on.sh caller with an open stdin wedged staging indefinitely.

- The worker now serves one lane per staged home: same-home jobs run strictly
  FIFO in a new staging-sequence order while different homes run concurrently,
  each lane as its own top-level worker process (a backgrounded subshell does
  not reliably reap dead children, so a zombie group leader kept a finished
  command's process group signalable). Long-poll preemption is lane-scoped.
- A caller that disconnects or times out cancels its job: the entrypoint marks
  the record on any post-staging exit and probes its parent so a dead ssh
  channel cancels without a signal; the worker skips cancelled queued jobs,
  terminates a running cancelled job's process group, and reaps the record.
- fm-send's remote leg is bounded by FM_SEND_REMOTE_BUDGET (default 30s) and a
  bound hit exits through the existing unconfirmed-delivery contract, which
  stays idempotent because the remote enqueue deduplicates.
- fm-on.sh defaults the remote command's stdin to /dev/null; the three payload
  callers pass the new --stdin flag. Abandoned .stage.* litter is age-reaped.
- The job execution deadline no longer loses up to a second to clock
  truncation.

* no-mistakes(review): Protect live stages and validate send budgets early

* no-mistakes(review): Preserve sequence lock ownership during stale recovery

* no-mistakes(review): Allocate job sequences at publication boundary

* no-mistakes(review): Bound remote keys and extend stale lock recovery

* no-mistakes(document): Document bounded remote transport behavior

* no-mistakes(lint): Suppress intentional deferred-expansion lint warning

* no-mistakes(ci): Fixed stale sequence-lock recovery by reconciling the counter against published job records before allocating the next sequence, preventing duplicate sequences and same-home FIFO violations. Added a behavioral regression test reproducing displacement after publication and verifying execution order. Passed fm-remote-transport-lanes.test.sh, fm-remote-job.test.sh, fm-lint.sh, and git diff --check

* no-mistakes(review): Use atomic sequence claims and lossless lane keys

* no-mistakes(review): Recover regressed sequence hints and rate-limit claim reaping

* no-mistakes(review): Restrict worker heartbeats to serving loop

* no-mistakes(review): Verify supervisor identity before lane recovery signals

* no-mistakes(review): Verify tracked lane and claim owner identities

* no-mistakes(document): Clarify remote lane and transport contracts

* no-mistakes(ci): Fixed the CI time-boundary failure by pinning fm-public-followup tests to a deterministic clock, including context-registry setup. Verified tests/fm-public-followup.test.sh, tests/fm-remote-transport-lanes.test.sh, shellcheck, and git diff --check

* no-mistakes(review): Preserve assigned lane ownership of queued jobs

* no-mistakes(review): Reserve homes owned by foreign queued lanes

* no-mistakes(review): Preserve completed results during crash recovery

* no-mistakes(review): Harden claim cleanup, expiry, and cancellation races

* no-mistakes(review): Verify process groups and reap abandoned results

* no-mistakes(review): Stop leaderless groups and reap cancelled publications

* no-mistakes(document): Correct remote transport lifecycle documentation

* no-mistakes(lint): Quote done state comparisons for ShellCheck

* fix(bin): accelerate and bound changed test runs (#3250)

* fix(tests): make the changed-file map select per script and stabilize a budget flake

The changed-file map's bin/ fallback resolved a direct test reference to that
test's whole FAMILY. bin/fm-push-transition-lib.sh is named by exactly one
real-Herdr E2E, so a one-line change to it selected all 12 real-herdr-gated
scripts, including a 341s presentation E2E with no dependency on it.

Resolve direct test references per script, and keep resolving consumer bin/
scripts through the curated map so recorded family-level coupling survives.

Also fix a load-sensitive flake: the tool-update budget deadline is whole-second
granular, so a test budget of 1 left headroom anywhere in (0, 1] seconds and the
first budget check could already read as exhausted.

* feat(bin): make suite wall clock a result and let a family's concurrency be proven

--max-wall-ms fails a run whose wall clock exceeds the caller's budget, after
reporting the per-script results. A suite that stays green while outgrowing its
caller's invocation budget is the regression that got an agent killed mid-run
and retried invisibly, so duration has to be a result rather than a log note.

--pool on the isolation-proof harness runs the same concurrent proof over a
whole family, so 'is this family safe to parallelize?' is answered by a command
instead of a guess. Measured watcher-wake-lock and refused it: 3 of 18 scripts
fail under concurrency on wall-clock assertions about reaching the next poll.

* perf(bin): schedule the changed suite concurrently, longest first

The watcher-wake-lock family is proven concurrent-safe (two clean runs, 18
candidates, 0 failures at 4 workers; docs/fm-test-isolation-proof.md), so
--changed now schedules its proven-concurrent scripts with bounded parallelism
and runs any unproven remainder serially afterwards, never beside them.

Concurrent runs are ordered longest-hint-first. Workers are handed scripts in
order, so alphabetical order started the 193s fm-watch-triage last and stranded
it running alone: 395s wall against a 205s balanced four-worker sum.

An explicit --jobs keeps its strict refusal, so every CI lane is unchanged.

* fix(bin): bound a hung test instead of letting it hang the suite

tests/fm-calm-pi-extension.test.sh was observed running 17+ minutes against a
464ms recorded hint, and the suite had no per-script bound to stop it. An
unbounded suite is precisely what silently outruns a caller's invocation budget,
and --max-wall-ms is evaluated after the run so it cannot end one that never
finishes.

--per-script-timeout-secs terminates a script that outruns it and records exit
124, so the run still completes, accounts for the script, and fails. The
auto-concurrent --changed path applies 900s, far above the slowest real script
(the 341s Herdr presentation E2E), so it only ever converts a hang.

* no-mistakes(review): Enforce safe concurrency and descendant timeouts

* no-mistakes(review): Validate empty runs and isolation proof pools

* no-mistakes(review): Measure selection time in wall budget

* no-mistakes(review): Reap interrupted workers and bound finalization

* no-mistakes(review): Contain shutdown descendants and watchdog finalization

* no-mistakes(review): Honor remaining budget and close launch races

* no-mistakes(review): Restore timeout helper and simplify runner cleanup

* no-mistakes(review): Record isolation pool admission metadata

* no-mistakes(review): Bound Chrome reap and scope proof admission

* no-mistakes(review): Align proof scheduling and preserve budget summaries

* no-mistakes(review): Remove unreliable finalization watchdog

* no-mistakes(review): Freeze budget duration and enforce admission caps

* no-mistakes(document): Refresh test runner concurrency documentation

* no-mistakes(lint): Fix ShellCheck findings in test runner scripts

* no-mistakes(ci): Fixed Greptile’s concurrency-consent finding. `--changed` now remains serial by default; `--changed --jobs auto` explicitly opts into bounded concurrency and the automatic hang timeout. Updated documentation and added behavioral coverage proving serial default behavior, explicit concurrent scheduling, and refusal of `--jobs auto` outside `--changed`. Verified with `bash tests/fm-test-run.test.sh`, `bin/fm-lint.sh`, and `git diff --check`

* no-mistakes(review): Restore automatic changed-suite concurrency and timeout

* no-mistakes(review): Correct changed-suite contributor guidance

* no-mistakes(review): Reject gate-skipped isolation proofs

* no-mistakes(review): Correct automatic concurrency evidence

* no-mistakes(review): Isolate nested runner process groups

* no-mistakes(review): Remove unreliable signal cleanup machinery

* no-mistakes(test): Narrow changed-suite selection to executable contract owners

* no-mistakes(document): Document isolation proof skip and artifact semantics

* no-mistakes(ci): Fixed Greptile’s concurrency-consent finding. `--changed` now remains serial by default; bounded concurrency requires explicit `--jobs auto`. Updated behavioral coverage, contributor guidance, and isolation-proof commands accordingly. Verified with `tests/fm-test-run.test.sh`, `bin/fm-doc-audience-check.sh`, `bin/fm-lint.sh`, Bash syntax checks, and `git diff --check`; all passed

* no-mistakes(review): Restore plain changed-suite automatic concurrency

* no-mistakes(review): Record resolved changed-suite worker count

* fix(bin): keep a runner change selecting its whole curated family

A pipeline fix round narrowed the curated changed-file map so bin/fm-test-run.sh
and bin/fm-test-isolation-proof.sh selected only their own two contract tests,
and the documentation surfaces only the audience test. That cut this branch's
own changed selection from 33 scripts to 5.

The runner executes every pure-contract-unit script, so its contract test
passing proves its logic is right, not that the suite it drives still runs.
Narrowing it also makes any wall-clock claim about the changed suite trivially
true by not running the work.

Only the unmapped bin/* grep fallback resolves per script; curated mappings keep
their recorded family coupling.

* perf(bin): admit the pure-contract-unit family to bounded concurrency

A runner-file change selects pure-contract-unit, so that family decides the
changed suite's wall clock. With only watcher-wake-lock admitted, 14 of its 33
selected scripts fell to the serial tail and the selection measured 327.3s
against a 300s budget: the concurrent group was 19 scripts totalling 273.4s
while the tail alone was 215.7s.

bin/fm-test-isolation-proof.sh --pool pure-contract-unit --jobs 4 passes twice,
32 candidates, 0 failures, so the family is admitted on recorded evidence.

Full 33-script plain --changed: 327.3s -> 181.8s / 178.5s / 172.7s, 0 failures,
inside a 300000ms budget. Also states the per-script guard's derivation.

* no-mistakes(review): Align contract-unit concurrency cap with recorded proof

* no-mistakes(document): Record final changed-suite performance evidence

* fix(bin): keep an empty changed selection clean on stock macOS Bash

Under set -u, bash 3.2 treats "${arr[@]}" on an EMPTY array as an
unbound-variable error, while bash 4.4+ makes it a harmless no-op. The
concurrency work removed the early exit for an empty selection, so execution
fell through to the unguarded existence loop: on stock /bin/bash 3.2.57 a
contributor who changes only documentation and runs --changed got

  bin/fm-test-run.sh: line 1713: SCRIPTS[@]: unbound variable

with exit 1 and no summary, instead of a clean total=0 pass.

Restore the early exit, and guard every remaining array expansion reachable
with an empty selection. The reported duration is real elapsed invocation
time rather than a hardcoded zero, so a selection phase that outran
--max-wall-ms still fails.

Verified on this host with /bin/bash 3.2.57: exit 1 with the unbound-variable
error before, exit 0 with FM_TEST_SUMMARY total=0 after.

* no-mistakes(document): Document shell-bound changed-suite performance

---------

Co-authored-by: Kun Chen <kun-1@kunchenguid.com>

* feat(bin): publish per-home summary ledgers (#3222)

* feat(bin): publish per-home summary ledger

* no-mistakes(review): Bound and schedule home summary publication

* no-mistakes(review): Prove recurring watcher summary refresh cadence

* no-mistakes(review): Bound refresh workers and publish durable spawns

* no-mistakes(review): Fix atomic kill process-group coverage

* no-mistakes(review): Bound state initialization within refresh timeout

* no-mistakes(document): Document recurring bounded home-summary publication

* no-mistakes(review): Bound and log all best-effort refresh failures

* no-mistakes(review): Harden cadence and timeout regression coverage

* no-mistakes(document): Document home-summary runtime tuning

* no-mistakes(lint): Fix direct exit-code check in refresh test

* no-mistakes(ci): Fixed remote secondmate retirement recreating the deleted home: teardown now skips side-band summary refresh when its overridden state directory was removed. Verified with remote lifecycle E2E, teardown tests, home-summary tests, ShellCheck, and git diff checks

* no-mistakes(document): Clarify atomic home-summary publication guarantee

* fix(pi): gate first provider call on startup context (#3158)

* fix(pi): gate first call on startup context

* no-mistakes(document): Correct Pi startup prerequisite verification date

* no-mistakes(review): Captain, fix startup process-group retirement after leader exit

* no-mistakes(review): Captain, release reload exit listeners on shutdown

* no-mistakes(review): Captain, complete startup exit lifecycle ownership

* no-mistakes(review): Captain, release empty startup process-group ownership promptly

* no-mistakes(review): Captain, supervise startup ownership and restore failure fallback

* no-mistakes(review): Captain, restore live Pi supervisor execution

* no-mistakes(document): docs: clarify Pi startup prerequisite delivery

* fix(pi): restore Pi 0.84.4 renderer compatibility (#3261)

* fix(pi): restore 0.84.4 adapter compatibility

* no-mistakes(review): Restore Pi collapsed and expanded outcome parity

* no-mistakes(review): Preserve Pi stock previews through capability probing

* no-mistakes(document): Document Pi 0.84.4 renderer compatibility

* fix(bin): keep home-summary publication from starving supervision (#3273)

* fix(bin): keep home-summary publication bounded and off the watcher beat

A home whose tasks had accumulated ordinary status history could not publish
state/home-summary.json at all, and every attempt starved the watcher's
liveness beacon while it failed silently.

The producer's per-task open-decision fold spent tens of milliseconds per
status line on a bash 3.2 global bracket-class substitution used only as a
blank-line guard. On a real home that made the whole ledger producer take
minutes, so publication burned its full FM_HOME_SUMMARY_TIMEOUT on every
attempt and never completed. Replace that guard with an equivalent case glob
in the one fold owner, which both the whole-file and cursor-backed folds use.

Bound each per-task current-state read in the snapshot with
FM_SNAPSHOT_CREW_STATE_TIMEOUT. For a remote secondmate that read crosses ssh,
whose dead-peer detection deliberately never kills a slow-but-alive remote
command, so nothing else bounded it.

Detach the watcher's two publication triggers from the poll loop. The loop
owns the beacon that fm-guard.sh reads as proof supervision is alive, and an
inline publication put up to a full publication deadline between two beacon
touches. A single in-flight publication is tracked so a slow one cannot
accumulate clones.

Report a repeatedly failing publication at session start. Publication stays
deliberately non-fatal to its caller, so the existing bounded home-local
failure record is now surfaced as a HOME_SUMMARY bootstrap line once the
ledger is absent or stale and failures have been recorded since.

* no-mistakes(review): Preserve home-summary failure attempt ordering

* no-mistakes(review): Enforce durable home-summary single-flight and ordering

* no-mistakes(review): Derive failure ordering from publication boundaries

* no-mistakes(review): Restore best-effort failure logging and publication scoping

* no-mistakes(review): Make ordering regression sensitive to one failure

* no-mistakes(document): Correct HOME_SUMMARY diagnostic guidance

* fix(bin): prevent routine updates from hiding actionable status (#3268)

* fix(supervision): classify the appended status span, not the last line

An actionable project update could be classified as routine and absorbed, so
a worker that raised a decision, hit a blocker, failed, or finished stalled
silently with the captain never told.

Trigger, mask, symptom. A worker appends a captain-relevant event
(`needs-decision`, `blocked`, `failed`, `done`). Any later routine append -
a `working:` progress note - lands before the supervisor classifies the
batch; the watcher's 30s signal-grace linger exists precisely to coalesce a
status write with the same turn's turn-end, so this window is ordinary
rather than rare. Both supervisors then asked "is the LAST line
captain-relevant?", read the routine line, and absorbed the wake. The
`.seen-*` suppressor advanced either way, so nothing ever re-read the event.
When the crew was also provably working, the no-verb fallback absorbed it
too, which is why the event disappeared completely instead of surfacing late.

Reproduced end to end against a real watcher before any change: with the
trailing `working:` append the watcher never exits and the wake queue stays
empty; with that one line removed - the smallest counterfactual - the same
`needs-decision` surfaces and queues. The away-mode daemon's `classify_signal`
returns `self|routine signal` for a `blocked:` event under the same mask,
which is the worse case because no captain is present to notice.

The proven path was already in the tree: `status_open_decisions` fixed this
exact masking for the durable decision fold, and its header states the rule -
reading an append-only event log last-event-wins cannot represent an earlier
event that a later unrelated line moved past. The classification path was
never migrated to that read model. That is the earliest divergence, and the
fix is to migrate it rather than to special-case the symptom.

`status_span_first_actionable` in bin/fm-classify-lib.sh is the new single
owner: it reads the bytes at or after a caller-supplied position and returns
the first still-live captain-relevant event. Each supervisor supplies its own
position, because the always-on watcher and the away-mode daemon classify the
same stream independently and must not share one cursor: the watcher reads
the size already recorded in its `.seen-*` signature (no new state) and its
`.hb-surfaced-<task>` backstop marker, and the daemon its
`.subsuper-seen-status-<task>` marker. Those two markers held the escalated
line and now hold the escalated-through byte offset, which also removes a
second defect in the same code - content dedup silently swallowed a genuinely
new event whose text repeated an older one. An absent, malformed, or
past-the-end position reads the whole log, so uncertainty surfaces events
rather than losing them, and a marker an older build wrote as a status line
reads that way too. Status logs are only ever appended to, including across a
reused task id, so a recorded position keeps its meaning.

A `needs-decision`/`blocked` event in the span is retired only when the
whole-file fold proves its key closed; `status_open_decisions` stays the sole
owner of that rule, so same-key reopening and reserved-key namespaces need no
second implementation here. Every other captain-relevant event is terminal
and always actionable.

Both backstops now walk every status log instead of only those whose last
line looks captain-relevant, because the event a backstop most needs to catch
is exactly one a later append has moved past. That leaves
`scan_captain_relevant_statuses` with no callers, and it is removed rather
than left as a working copy of the defective read model.

Regression coverage exercises the classifier and both supervisors through
their own interfaces: the masked decision, the captain-reported
release/install completion followed by cleanup chatter, and the away-mode
blocker all surface; a routine append after an already-classified event stays
absorbed, so the fix does not convert ordinary progress into wakes; and the
heartbeat backstop catches a masked event the per-wake path missed. The
end-to-end watcher tests drive a real fm-watch.sh with the crew reported as
provably working, which is the configuration that made the original stall
silent.

Two further claims in the supplied RCA are deliberately not patched here.
"Repeated operational recoveries produced all-clear replies despite known
actions" is downstream of this same cause, not an independent contributor: an
all-clear reply is the documented response when the specific event needs no
action, so a classification that wrongly reported "no action" produces it, and
correcting the classification removes it. "The project was subjected to
validation requirements outside its accepted path" is delivery-mode selection,
which AGENTS.md section 7 owns; no code changed here touches it, so it is out
of scope.

Harness and backend axes were inspected rather than assumed: nothing in this
path reads a vendor-emitted signal. The status log's format and append
protocol are Firstmate's own and identical for every harness, and no runtime
backend reads or writes `.status` files (`bin/backends/*` contain no reference
to them). The surrounding triage's only backend touchpoints - pane capture and
the authoritative crew-state read - are unchanged. No live-harness guard
applies and no per-harness verification record changes.

Verified with `bin/fm-lint.sh`, `bin/fm-doc-audience-check.sh`, and
`bin/fm-test-run.sh --changed --base origin/main`.

* no-mistakes(review): Prevent status races and surface classification failures

* no-mistakes(review): Surface unreadable signals and preserve AFK endpoints

* no-mistakes(review): Route stale wakes through captured span verdicts

* no-mistakes(review): Retire supervision offsets with reused task state

* no-mistakes(review): Bind status offsets and preserve live decision origins

* no-mistakes(review): Strengthen status identity with verified birth time

* no-mistakes(review): Skip turn-end markers during status classification

* no-mistakes(review): Preserve status presentation with platform-strength identities

* no-mistakes(review): Retain failed wakes and advance routine checkpoints

* no-mistakes(review): Surface all events and retain unreadable wakes

* no-mistakes(review): Treat absent status logs as successful empty spans

* no-mistakes(review): Bound repeated classification failures with durable receipts

* revert(supervision): drop the failure-receipt and durable-retry machinery

Captain-authorized revert to the minimal fix. Review rounds added a durable
failure-receipt store and wake-retention-on-failure to bound repeated
classification failures. That machinery grew larger than the fix it protected
and kept producing its own defects: an unreadable log still looped forever
because the always-on watcher never consulted the receipt, and the receipt was
persisted before its diagnostic was durably queued, so a crash in between
swallowed the alarm outright. Those two defects go away with the code that
contained them rather than being repaired.

Removed: the failure-receipt path, fingerprint, record and clear helpers and
their retirement bookkeeping; the retention of a durable wake when
classification fails; and the error-propagation plumbing in both supervisors
that existed only to drive them.

Kept, because it is the accepted fix rather than the declined machinery: span
classification of the events appended since a supervisor last looked, in both
supervisors and both backstops; reporting every actionable event in a span and
committing a position only through what was reported; naming the live opening of
a reopened decision; treating an absent log as ordinary and an unreadable one as
worth reporting; the non-.status filter; and the platform-strength identity that
guards a position commit without failing a read.

Replacement behavior for a log that cannot be classified: report it once, do NOT
advance the classification position so the content is classified from where it
stopped once readable, and DO advance the wake signature so the report is
bounded to one per distinct file state. Reporting and reading are different acts:
telling the captain about a log is not the same as having read it, and only the
latter may move a classification position.

The residual risk is explicit and accepted: there is no guaranteed automatic
retry inside a crash-mid-read window, and the locked session-start replay of the
durable queue covers it. That rationale is recorded at mark_escalated_seen so a
future reader does not reintroduce the retry as a "missing" guarantee.

Also fixes lint failures that arrived with the review-fix commits and were never
caught because the run never reached its lint step: an unfollowable conditional
source directive, a second unquoted-expansion site left after a call was split
across lines, cleanup of the file being read inside its own read loop (restructured
to one post-loop teardown rather than three in-loop copies), stub functions in
tests that are invoked indirectly, and a test local left unused when its
assignment was replaced by a helper. bin/fm-lint.sh passes on the default branch,
so these were introduced here.

Verified with `bin/fm-lint.sh`, the end-to-end masked-decision and away-mode
reproductions, and `bin/fm-test-run.sh` over the supervision, wake-queue,
wake-drain, watch-arm and inactive-reconcile suites (6 scripts, 0 failures).

* no-mistakes(review): Correct classification failure contract documentation

* no-mistakes(review): Bound unreadable status reports without skipping classification

* no-mistakes(review): Preserve escalation markers when buffering fails

* no-mistakes(review): Detect permission recovery without advancing classification

* no-mistakes(document): Document status span classification contract

* no-mistakes(ci): Fixed CI failures by lazily loading classification helpers in fm-wake-lib, preserving minimal recovery/remote fixtures; added a public current-status marker helper and updated behavioral fixtures to use the v2 marker contract; resolved ShellCheck variable collisions in fm-control and fm-public-followup-lib. Verified fm-lint, bash syntax, fm-control, public-followup, wake-queue, send-resolve-key, captain-hold, pending-reply, remote-reply, remote-backlog-handoff, turnend-guard, and Claude autoarm tests. The Pi branch suite reached a separate local stock-render mismatch under Node 24; its CI-reported missing-classifier failure path is fixed

* no-mistakes(review): Escalate blockers while preserving declared-wait cadence

* no-mistakes(review): Clarify actionable events override wait self-handling

* no-mistakes(review): Surface rejected decisions and dangling status links

* no-mistakes(document): Document reserved-key reconciliation classification

* no-mistakes(ci): Fixed the flaky portable serial CI test by modeling the retained staging directory as genuinely owned by a live process and aging both fixtures deterministically. This removes scheduler-timing dependence while verifying the worker reaps abandoned staging and preserves live staging. Verified with fm-remote-transport-lanes.test.sh, bin/fm-lint.sh, bash syntax, and git diff --check

* no-mistakes(document): Correct away-mode classification documentation

* docs(skills): split harness adapter operations reference (#3289)

* docs: split harness adapter operations reference

* no-mistakes(review): Fix harness adapter routing and ownership contracts

* no-mistakes(review): Prune duplicate harness adapter ownership prose

* no-mistakes(review): Fix default effort routing and Grok max semantics

* no-mistakes(review): Remove source-only routing test and duplicate semantics

* no-mistakes(review): Add local harness adapter instruction evaluation

* no-mistakes(review): Fix harness evaluation gating and change mapping

* no-mistakes(test): Captain, require explicit harness instruction evaluator model

* no-mistakes(document): Fix harness adapter documentation references

* test: centralize shared shell fixtures (#3296)

* test(fixtures): share fake-toolchain and spawn-world builders

Future tests can start from tests/fixtures.sh instead of copying stubs, and a
no-mistakes version-floor bump is one constant rather than a multi-file edit.

Migrated this round: fm-busy-adapter-wiring, fm-spawn-pool-base-freshen,
fm-grok-harness, fm-tangle-guard, fm-gate-refuse, fm-spawn-dispatch-profile.
Left for opportunistic migration: remaining make_spawn_fakebin copies
(trace-context, kimi, muse, backend), the make_stubs send cluster, and the
fake no-mistakes version banners in bootstrap/session-start/secondmate suites.
Did not touch tests/fm-pr-check-security.test.sh.

* no-mistakes(review): Prevent fake SSH test from blocking on stdin

* no-mistakes(document): Clarify shared fixture documentation

* no-mistakes(ci): Fixed the flaky watcher triage test by extending its startup-sensitive timer-repair wait from 3s to 10s, matching existing loaded-runner budgets. Verified with the full tests/fm-watch-triage.test.sh suite, bash syntax validation, and git diff checks

* no-mistakes(ci): Fixed portable serial shard 4 by updating the inactive-reconcile fixture to prime status through the public fm_wake_status_mark_current API, ensuring classifier helpers load correctly and preventing the idle watcher from exiting. Verified the test three consecutive times, ran fm-test-fixtures, ShellCheck, bash syntax checks, and git diff checks. The outer no-mistakes executor can now bind a fresh attestation to the new head

* no-mistakes(ci): Added behavioral coverage proving the shared spawn tmux fixture defaults an unset FM_FAKE_PANE_PATH to empty. Verified the fixture suite, ShellCheck, syntax/diff checks, and all six migrated test suites; all passed. The outer executor can now bind a fresh no-mistakes attestation to the updated head

* refactor: retire legacy PR-check migration machinery (#3299)

* feat(bin): retire completed PR-check migration machinery

Every registered home already carried both completion markers, and no
installer still creates pre-migration checks. Remove the one-time migrate
script, its bootstrap/watch/teardown/docs surface, and migration-path tests
without weakening live check-trust or PR-poll authentication.

* no-mistakes(review): Restore live PR-check security coverage

* no-mistakes(document): Refresh retired PR-check documentation

* no-mistakes(ci): Fixed both failing CI checks. Updated inactive-reconcile setup to use the public status-marking interface, preventing false watcher exits. Made remote-job shutdown deterministic by stopping the complete worker tree before tampering. Verified both affected test suites, repeated inactive reconciliation, shell syntax, and git diff checks

* feat(bin): add trusted process-event extension bindings (#3247)

* feat(extensions): bind trusted external process-event adapters

* no-mistakes(review): Enforce owner and remote-home conformance

* no-mistakes(review): Enforce serialized remote extension package lifecycle

* no-mistakes(review): Enforce identity-conditional extension retirement

* no-mistakes(review): Serialize extension retirement and recover crash cuts

* no-mistakes(review): Unify retirement worker and lifecycle lock ownership

* no-mistakes(review): Harden extension lifecycle retirement serialization

* no-mistakes(review): Unify extension registration and overridden-state lifecycle boundaries

* no-mistakes(document): Clarify built-in-only captain answer routing

* no-mistakes(lint): Captain: fix extension binding ShellCheck findings

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes(review): Use isolated UID mapping for owner conformance

* no-mistakes(review): Captain: remove forbidden CI ownership wrapper

* no-mistakes(review): Serialize extension binding publication

* no-mistakes(review): Document ordinary CI owner-fixture exclusion

* no-mistakes(review): Quarantine orphaned handshake descendants

* no-mistakes(test): Fix orphan attribution

* no-mistakes(test): Harden process tracker baseline

* no-mistakes(test): Harden detached descendant attribution

* no-mistakes(test): Use exact invocation-group cleanup

* no-mistakes(test): Bound remote conformance transport crossings

* no-mistakes(test): Parallelize isolated extension conformance tests

* no-mistakes(test): Lifecycle suite still exceeds deadline

* feat(extensions): bind trusted external process-event adapters

* no-mistakes(review): Enforce owner and remote-home conformance

* no-mistakes(review): Enforce serialized remote extension package lifecycle

* no-mistakes(review): Enforce identity-conditional extension retirement

* no-mistakes(review): Serialize extension retirement and recover crash cuts

* no-mistakes(review): Unify retirement worker and lifecycle lock ownership

* no-mistakes(review): Harden extension lifecycle retirement serialization

* no-mistakes(review): Unify extension registration and overridden-state lifecycle boundaries

* no-mistakes(document): Clarify built-in-only captain answer routing

* no-mistakes(lint): Captain: fix extension binding ShellCheck findings

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes(review): Use isolated UID mapping for owner conformance

* no-mistakes(review): Captain: remove forbidden CI ownership wrapper

* no-mistakes(review): Serialize extension binding publication

* no-mistakes(review): Document ordinary CI owner-fixture exclusion

* no-mistakes(review): Quarantine orphaned handshake descendants

* no-mistakes(test): Fix orphan attribution

* no-mistakes(test): Harden process tracker baseline

* no-mistakes(test): Harden detached descendant attribution

* no-mistakes(test): Use exact invocation-group cleanup

* no-mistakes(test): Bound remote conformance transport crossings

* no-mistakes(test): Parallelize isolated extension conformance tests

* no-mistakes(test): Lifecycle suite still exceeds deadline

* no-mistakes(review): Split extension conformance and forward remote transfer input

* no-mistakes(review): Forward malformed remote payloads through fm-on

* no-mistakes(review): Bound extension coordinator failure cleanup

* no-mistakes(test): Skip repeated orphan sweep in coordinator children

* no-mistakes(test): Queue isolated extension sections through bounded workers

* no-mistakes(test): Bound extension coordinator lane cleanup

* no-mistakes(test): Split remote lifecycle coordinator sections

* no-mistakes(test): Coordinator probes pass; aggregate deadline remains

* no-mistakes(test): Launch extension sections concurrently

* no-mistakes(test): Fix coordinator marker publication

* no-mistakes(test): Stabilize extension binding coordinator timing

* no-mistakes(lint): Fix extension binding ShellCheck warnings

* fix(extensions): prove invocation cleanup before retirement

* no-mistakes(review): Harden process-event inbox confinement

* no-mistakes(review): Preserve legacy capture parity

* no-mistakes(review): Protect external registry staging

* no-mistakes(test): Stabilize bounded extension conformance aggregate

* no-mistakes(document): Document external evidence confinement

* no-mistakes(ci): CI phase fixed. The failure was a flaky fixture in `tests/fm-remote-transport-lanes.test.sh`: its “fresh/in-use” staging directory had no live owner identity, so the real worker correctly reaped it once the 1-second age boundary elapsed on slower CI. The fixture now records the active test shell’s exact PID/start identity and cleans those records before removal. Verified: `bash tests/fm-remote-transport-lanes.test.sh` exits 0 with all checks passing; `git diff --check` passes. Provider check retrieval was also retried successfully, resolving the selected manual CI finding. Changed file: `tests/fm-remote-transport-lanes.test.sh`

* no-mistakes(review): Harden extension staging and lifecycle reservation

* no-mistakes(review): Harden external staging and lifecycle reservations

* no-mistakes(review): Wire capture helper into remote conformance

* no-mistakes(review): Pin external capture handoff and signal failures

* no-mistakes(review): Bind pinned capture authority to inherited descriptor

* no-mistakes(review): Harden descriptor-bound capture authority

* no-mistakes(review): Harden core capture reservation authority

* no-mistakes(review): Harden capture reservation boundaries

* no-mistakes(review): Harden capture reservations and cleanup

* no-mistakes(review): Harden capture handoff and reservation cleanup

* no-mistakes(review): Bind capture handoff to claim descriptors

* no-mistakes(review): Release lifecycle locks after host crashes

* no-mistakes(review): Pin reservation recovery to recorded state roots

* no-mistakes(review): Reject control bytes in claim state roots

* no-mistakes(test): Stabilize extension capture descriptor handoff

* no-mistakes(document): Document extension capture authority boundary

* no-mistakes(lint): Fix ShellCheck extension binding warnings

* no-mistakes(ci): CI phase result: fixed `bin/fm-procevent.sh` by initializing the shared `capture_state` sentinel for built-in adapters under `set -u`. This prevents normal built-in captures from aborting before publication. Verified: `bash -n bin/fm-procevent.sh` and `git diff --check` pass. The focused process-event suite was run locally but stopped earlier at a local detached-runner claim failure (`reconcile never claimed the registered source`), before the CI-reported post-capture path; CI evidence confirms the fixed unset-variable failure affected the failing remote, board, watcher, and process-event checks

* no-mistakes(document): Correct extension namespace creation timing

* no-mistakes(lint): Initialize capture locals for ShellCheck

* fix(bin): deliver safety rules to promoted workers (#3269)

* fix(bin): deliver the real definition of done to a promoted scout, and ban --yes

A promoted scout used to receive a free-form placeholder instead of the
mode-specific Definition of done a briefed ship worker gets, so it never
saw the ask-user escalation rule or the --yes prohibition. That gap is the
concrete reason one incident's worker drove validation with --yes and
answered its own ask-user findings.

- Add bin/fm-dod-lib.sh as the single owner of a ship task's mode-specific
  Definition of done, rendered by both bin/fm-brief.sh and bin/fm-promote.sh
  so the two contracts cannot drift.
- bin/fm-promote.sh now writes data/<id>/ship-instructions.md carrying the
  scratch inventory, clean base, ship branch, and that Definition of done,
  and prints the fm-send.sh command that delivers it.
- State the --yes ban as a prohibition rather than a preference, without
  claiming an enforcement the tool does not provide.
- Cover both through the real promotion and brief paths in
  tests/fm-task-delivery.test.sh and tests/fm-brief.test.sh.

* no-mistakes(review): Publish promotion instructions before committing task state

* no-mistakes(review): Supersede conflicting scout delivery rules after promotion

* no-mistakes(review): Reject invalid promotion instruction destinations

* no-mistakes(document): Align documentation with promotion delivery contracts

* no-mistakes(ci): Fixed both CI findings. Promoted workers now receive an explicit worktree-isolation check before branch creation, with instructions to stop and escalate if they are in the primary checkout. Updated behavioral coverage to verify the delivered promotion payload, and aligned the ask-user authority test with the new fleet-wide --yes prohibition. Verified with bin/fm-lint.sh, tests/fm-brief.test.sh, tests/fm-ask-user-authority.test.sh, tests/fm-task-delivery.test.sh, and git diff --check

* no-mistakes(ci): Made tests/fm-ask-user-authority.test.sh executable so the modified colocated behavioral test runs directly like the surrounding test suite. Verified bin/fm-lint.sh, fm-brief, ask-user-authority, and task-delivery tests; all pass. git diff --check is clean

* no-mistakes(ci): Strengthened tests/fm-task-delivery.test.sh to behaviorally verify that real promotion and brief generation deliver byte-identical Definition-of-done blocks for all three modes. Verified tests/fm-task-delivery.test.sh, tests/fm-brief.test.sh, bin/fm-lint.sh, and git diff --check. The outer pipeline can now commit and attest the updated head

* no-mistakes(ci): Fixed promotion isolation instructions so any checkout other than the launched disposable worktree requires escalation, including another non-primary worktree. Updated behavioral coverage against the delivered promotion payload. Verified fm-task-delivery, fm-brief, fm-ask-user-authority, full fm-lint/ShellCheck, workflow lint, and git diff checks

* fix(bin): present Lavish feedback as structured output (#3321)

* fix(bin): present complete Lavish board feedback as structured output

Give the Lavish adapter a read-only presentation so a handler sees every
annotation and the session-ending tag=message as its own field, instead of
grepping a truncated raw capture.

* no-mistakes(review): Preserve unquoted messages and prioritize captain prose

* no-mistakes(document): Document structured Lavish result reads

* no-mistakes(ci): Fixed Lavish `read` completeness: rows missing declared fields are excluded from presented items, counted as malformed, and force `complete: no`. Added behavioral regression coverage through the adapter interface. `bin/fm-lint.sh`, syntax checks, and focused valid/malformed read checks passed. The portable-serial failure was an unrelated secondmate cooldown timing flake

* fix: keep task records and backlog transitions atomic (#3322)

* fix(records): pair backlog transitions with the record that moves

Dispatch and completion each moved a task's physical record and its
backlog row as two independently timed steps, so a crash or a forgotten
follow-up could leave the two disagreeing: a record with no in-flight
row, an in-flight row with no owner, or a finished task still shown in
flight.

Fold each backlog transition into the script that performs the physical
change, under the per-task lock it already holds and before it reports
success. Dispatch moves the item to In flight after publishing the task
record and fails loudly, removing its provisional record, when that
transition cannot land. Completion records an authoritative close and
performs it before removing the record, so an interrupted cleanup can be
finished later, and its closing message now confirms what already
happened rather than instructing a future step.

Add a same-home reconciliation sweep to session start so a home that was
interrupted mid-transition settles its own books on restart, replaying a
recorded close and restoring an in-flight row it already owns a worker
for. It never reads or writes another home; the fleet snapshot and the
cross-home nudge stay as backstops.

Close records are validated before they are trusted: the file is read as
raw bytes and rejected outright when it carries a NUL or other control
byte, every field must be well formed and non-duplicated, the id must
match the record it was found under, the data location must resolve
inside this home, and each close argument must carry a permitted,
well-formed value. Writer and reader share one validator so a record
this home publishes always remains replayable, independent of locale.

Homes configured for a manual backlog, and homes with no backlog at all,
stay exempt and are unaffected.

* no-mistakes(review): Remove stale bootstrap migration helper invocation

* no-mistakes(review): Preserve pending closes and narrow signal deferral

* no-mistakes(review): Record close before destructive teardown

* no-mistakes(review): Refuse pending closes before creating resources

* no-mistakes(review): Guard relaunches and preserve cleanup warnings

* no-mistakes(review): Reject symlinked records and clarify cleanup guidance

* no-mistakes(review): Align dispatch eligibility and protect close replay

* no-mistakes(review): Unify exact task incarnation parsing

* no-mistakes(review): Render resolved configured backlog path

* no-mistakes(review): Harden transition path boundaries against symlinks

* no-mistakes(review): Validate lifecycle state before resource actions

* no-mistakes(review): Enforce transition tooling and continuous state locks

* no-mistakes(review): Consolidate same-home lifecycle file boundaries

* no-mistakes(review): Enforce canonical lifecycle containment and tooling contracts

* no-mistakes(review): Reject final-component lifecycle record symlinks

* no-mistakes(document): Document lifecycle record path boundaries

* no-mistakes(lint): Quote literal done tokens in atomicity tests

* no-mistakes(ci): Fixed all PR-caused CI failures: bootstrap now treats an absent state directory as an empty fresh home while retaining unsafe-state checks; nested remote secondmate retirement accepts records already removed with the retired home; teardown fixtures now provide valid data/manual-backend configuration; and the manual reminder assertion checks the configured absolute backlog path. Verified the reported tests, remote lifecycle E2E, backlog atomicity suite, Bash syntax, diff checks, and ShellCheck. The documented pre-existing captain-hold failure was intentionally untouched

* no-mistakes(ci): Fixed Behavior portable serial 3 by adding `od` to the teardown test’s lsof-free PATH fixture. The new close-record validator legitimately requires `od`; its omission caused teardown to fail before process-group cleanup and stall the shard. Verified the full `tests/fm-teardown.test.sh` suite passes, plus Bash syntax, ShellCheck, and `git diff --check`

* no-mistakes(ci): Fixed close replay to durably retain incomplete-cleanup evidence before removing task metadata. Subsequent retries now emit the reconciliation warning even after a backlog probe or close failure. Updated the behavioral regression and verified the full atomicity suite under stock macOS Bash 3.2, plus shellcheck and diff checks

* fix(records): validate record bytes without an uncurated tool

The byte validation added for close records and directory paths shelled
out to od. The spawn and teardown lifecycle runs under a curated command
set that deliberately excludes it, so on any restricted PATH the check
could not run, the data directory read as unresolvable, and dispatch and
cleanup refused - wedging the lifecycle rather than protecting it.

An earlier attempt made the failing test pass by adding od to that
curated set. That fixed the test to agree with the defect and quietly
widened the contract the fixture exists to pin, so it is reverted here.

Inspect the bytes with perl instead, which is already in the curated set
and already used in this repo for the same portability reason. The
emitted values are identical to od's, so the rejection semantics are
unchanged: NUL and other control bytes are still refused, legitimate
paths containing spaces or non-ASCII characters still round-trip, and
the check stays independent of the process locale.

The restricted-PATH teardown case now passes because the validator no
longer needs od, not because the fixture was loosened.

* no-mistakes(review): Enforce dispatch eligibility and atomic remote record publication

* no-mistakes(document): Document dispatch eligibility and cleanup alerts

* fix(bin): contain promote and Relay metadata publishing (#3342)

* fix: publish promote and Relay meta rewrites through contained replace

Bare mv still rewrote live task records in place, so a symlink meta could
be followed to a target outside state/. Route those field rewrites through
the shared publisher and drop the unused library aliases.

Co-authored-by: Cursor <cursoragent@cursor.com>

* no-mistakes(review): Refuse dangling symlinks during X metadata clear

* no-mistakes(review): Refuse unsafe metadata before follow-up and promotion side effects

* no-mistakes(review): Exercise dangling symlink refusal through clear helper

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(bin): absorb turn-end wakes during bounded pane churn (#2877)

* fix(watch): absorb a turn-end whose pane churned since the previous poll

The watcher's "absorb a benign turn-end when the crew is provably working"
triage was structurally unreachable for any harness whose semantic busy state
has no verified source. crew_absorb_class only reports working for an actively
running no-mistakes step or an exact busy verdict, and bin/fm-crew-state.sh can
only answer unknown for such an adapter, so codex crewmates surfaced a signal
wake at every turn boundary with nothing to act on - a full supervisor drain,
inspect and acknowledge turn per worker turn, scaling with the number of workers
in flight and drowning the wakes that matter in identical noise.

Widen the proof rather than bound the wake rate. A wake carrying only bare
turn-ended markers is now also benign when the task's pane content changed since
the previous poll, compared against the same state/.hash-* marker the staleness
backbone already records and already trusts as liveness. That evidence claims no
harness semantics, so it fabricates no busy verdict an adapter has not earned,
and it needs no adapter cooperation.

Absorb stays evidence-driven in both directions. A wake naming any status file
keeps the strict proof, every captain-relevant verb still surfaces immediately,
and an unresolvable task, a missing prior hash, a failed or empty capture, or an
unchanged pane all surface exactly as before. The absorb defers rather than
swallows: a crew that has stopped renders nothing further, so its now-static pane
surfaces through the staleness backbone within a poll or two. Bounding the
surfacing rate instead would have suppressed genuinely stopped workers.

The derivation lives with the .hash-* marker format in bin/fm-watch.sh, which
owns it, and costs one bounded capture reached only for a no-verb turn-end whose
crew is not already provably working.

* no-mistakes(review): Captain, guard pane-churn absorption from collisions and secondmates

* no-mistakes(review): Captain, make watcher marker identities injective

* no-mistakes(review): Captain, isolate ambiguous legacy markers and restore Herdr sourcing

* no-mistakes(review): Captain, localize pane-churn collision guard

* no-mistakes(review): Captain, reject malformed pane-churn hashes

* no-mistakes(document): Document pane-churn turn-end evidence

* no-mistakes: apply CI fixes

* fix(watch): gate and bound the pane-churn turn-end absorb

Make the pane-churn form of positive work evidence opt-in per home and
bound how long it may defer one endpoint's bare turn-ends.

Absorbing a bare turn-end on pane churn is now reached only when the home
creates config/turnend-churn-absorb. The other two proofs read a verdict
the harness itself vouches for, while this one infers execution from
rendered bytes, so widening the absorb is a home's choice rather than a
default every fleet inherits. With the flag absent the predicate returns
on its first line and triage is unchanged.

Churn and pane staleness read the same pane, so neither can be the
other's only backstop. A pane that renders continuously never presents
the two consecutive identical hashes the staleness backbone needs, so an
unbounded churn absorb left a worker that had genuinely stopped behind
such a renderer with no path to surface at all. One endpoint's turn-ends
may now ride churn evidence for at most FM_TURNEND_CHURN_ABSORB_SECS,
tracked in state/.churn-since-*, after which the wake surfaces and the
window restarts. The bound is evaluated before any .stale- state is
touched, so a wake that surfaces there leaves the staleness backbone's
own classification alone.

Covers both with behavioral tests: the same churning fixture that absorbs
with the flag surfaces and queues without it, and a spent deferral window
surfaces and restarts. The four existing safety guards now run with the
flag enabled so they keep proving their specific guard.

* no-mistakes(review): Fail closed on invalid churn deferral state

* no-mistakes(review): Validate persisted churn deadlines before arithmetic

* no-mistakes(review): Make churn deadlines transactional and bounds safe

* no-mistakes(review): Compose turn-end evidence per task from one snapshot

* no-mistakes(review): Restore strict turn-end fallback guards

* no-mistakes(document): Clarify pane-churn supervision documentation

* no-mistakes(lint): Fix watcher arithmetic lint issues

* no-mistakes: apply CI fixes

* no-mistakes(document): Clarify p…
@greptile-apps

greptile-apps Bot commented Sep 4, 2026 •

Copy link
Copy Markdown

Confidence Score: 4/5

The PR is not yet safe to merge because Pi secondmates can still retain stale effective-model information after changing models.

The repeated-probe and synchronization fixes cover ordinary Claude and Pi tasks, but secondmate Pi launches do not install an extension that invokes model synchronization after runtime model changes.

Files Needing Attention: bin/fm-spawn.sh

Reviews (5): Last reviewed commit: "Merge remote-tracking branch 'fork/main'..." | Re-trigger Greptile

}

function classifyPosition(position) {
if (position.wrappers.some((wrapper) => FORKING_WRAPPERS.has(wrapper))) return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Wrappers bypass mutation guard

When a protected-copy mutation is prefixed with env, sudo, nohup, timeout, gtimeout, or exec, this early return allows the command before the underlying mutation is classified, so a worker can modify or destroy /home/vsole/uebernahme-arbeits-pc without captain authorization. How this was verified: The parser retains the underlying command, but this branch returns solely because a listed wrapper is present and the hook integrations proceed on that result.

Suggested change
if (position.wrappers.some((wrapper) => FORKING_WRAPPERS.has(wrapper))) return null;

Context Used: If there is a VISION.md file at the root of the re... (source)

Knowledge Base Used: Supervision policies and guards

Comment thread bin/fm-model-sync.sh Outdated
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate:

Reviewed HEAD a403d19acb2b1f13b2ce55f47df2d18901d408ce vs main a5c64a0b6791b7643024436af9f737059d15cef2. Whole thread + full file list / Greptile review + inline findings read. Compare: 25 ahead / 63 behind, CONFLICTING/DIRTY. 75 files / ~7900 additions spanning model-display plus Klartext-Uebernahme personal guards (/home/vsole/...), work-loop, Pi-primary restart, German supervision, sessionstart, capacity, and more — not a focused model-display PR.

Attestation: MISSING (no no-mistakes-pipeline-attestation:v1).

Contract-class: cannot-tell. Claimed Herdr requested-vs-effective model surface touches inspectability (VISION: quota/model/effort stay inspectable), but the tip also lands always-on personal-path guards and multiple unrelated default-path features in one diverged fork tip; class cannot be adjudicated cleanly until the PR is rebased to a minimal attested tip.

VISION (per rule):

  • One captain, one interface — mixed / cannot tell (model inspectability helps; massive unrelated surface adds noise).
  • Authority is explicit — does not align while personal Klartext guards and other always-on paths ship without a narrow captain grant on the shared surface.
  • Scripts own the mechanics — mixed (model probe/sync are scripted; scope sprawl).
  • A restart is a non-event — cannot tell on this tip.
  • Delegation with a spine — cannot tell.
  • The fleet outlives any vendor — mixed (Herdr metadata is adapter-shaped; personal /home/vsole coupling is not).
  • Scope — does not align: personal captain paths and a multi-theme fork dump into the shared distro.

Security: YES — Greptile P1 security on bin/fm-klartext-uebernahme-command-policy.mjs: transparent wrappers (env/sudo/nohup/timeout/exec) can short-circuit classification so protected-copy mutations against the Uebernahme tree are allowed. Second Greptile P1: bin/fm-model-sync.sh stops refreshing effective model after first exact probe. Per charter: no first-time fork CI approval while security risk is open.

CI / NM: no CI / Require no-mistakes workflow runs on this HEAD (only Greptile Review FAIL). workflow-approved this pass: no (security).

Mergeable: CONFLICTING / DIRTY. Merge-eligible: NO.

Waiting on author (not captain): (1) drop personal Klartext//home/vsole and unrelated themes; (2) fix wrapper-bypass + stale effective-model sync; (3) rebase onto current main as a minimal model-display tip; (4) git push no-mistakes so attestation MATCHES HEAD; (5) re-green CI. Firstmate flag: yes — security (wrapper bypass of protected-copy guard).

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: captain word on the security close card — skip is not close and not approve. Leave this open. Not waiting on the author for a close.

HEAD still a403d19acb2b1f13b2ce55f47df2d18901d408ce (CONFLICTING/DIRTY). Class cannot-tell / security hold unchanged: Klartext protected-copy mutation-guard wrapper bypass risk + large personal dump. Fork CI stays withheld. Do not merge. Do not close. No blocked-authors / flagged-malicious banner.

Firstmate will only re-flag if the tip/security surface changes or this becomes otherwise-ready again.

Valentino-Sole and others added 3 commits September 4, 2026 21:23
Track spawn-config requested_model separately from runtime-verified
effective_model, probe Claude/Pi transcripts for exact API ids, push
compact display metadata to Herdr, and preserve verified models across
relaunch/compaction hooks without inferring aliases as truth.
fm-model-sync.sh only probed for the runtime-verified effective model
while it was still pending/UNKNOWN, so a session that later switched
models (manual switch, provider fallback) kept displaying the first
verified model forever and never appended a fallback-history entry.
Probe unconditionally instead; fm_model_record_effective already
no-ops when the probed value is unchanged, so this stays cheap.

Addresses the Greptile P1 finding on PR kunchenguid#3705's fm-model-sync.sh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwfFjeYQcz9cZ3vEZohmpm
…ude in the display

Kapitänskorrektur: harness alone conflated Cursor-hosted Grok models
(cursor-grok-4.6-*) and direct xAI Grok models (xai/grok-4.6) under
one generic label, and displayed Anthropic Claude without naming the
provider. Add fm_model_source_label, pattern-matched on the verified
exact model id, so the compact display always reads Cursor · Grok,
xAI · Grok, or Anthropic · Claude with the exact model id appended.
Falls back to the existing harness label for every other model. No
routing change: this only affects display strings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwfFjeYQcz9cZ3vEZohmpm
Comment thread bin/fm-model-sync.sh
Comment on lines +39 to +42
# and fm_model_record_effective already no-ops when the probed value is
# unchanged, so this stays cheap while still catching later changes.
pane=$(fm_model_meta_get "$META" herdr_pane_id)
if probe_out=$("$SCRIPT_DIR/fm-model-probe.sh" "$STATE" "$ID" "$pane" 2>/dev/null); then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Pi model refresh never runs

When a running Pi session changes models after recording an exact effective model, no Pi lifecycle hook invokes this reprobe path, causing Herdr to keep displaying the previous model and omit the fallback-history entry.

fm-model-sync.sh was only invoked from Claude's SessionStart/
UserPromptSubmit/Stop hooks; the Pi harness's own extension
(state/<id>.pi-ext.ts) never called it, so a Pi-hosted session (e.g.
a pi/xai-grok crewmate) never refreshed its effective model after the
first probe and Herdr kept showing the stale value with no
fallback-history entry. Call fm-model-sync.sh from the same
agent_start/turn_end boundaries Pi already uses for busy-state and the
turn-end notification touch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwfFjeYQcz9cZ3vEZohmpm
Comment thread bin/fm-spawn.sh
Comment on lines +2855 to +2858
pi.on("agent_start", () => {
busyEvent("busy", "agent-start");
modelSync();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Pi model refreshes race

If Pi lifecycle events overlap, these handlers launch modelSync() without returning or awaiting its promise, so multiple processes perform unlocked read-modify-write updates to the same metadata. Their interleaved model/source rewrites and history appends can restore an older effective model, mismatch its source, or duplicate history, leaving the Herdr display stale despite the new refresh hooks.

Valentino-Sole and others added 2 commits September 4, 2026 21:45
Overlapping lifecycle events (Pi's agent_start/turn_end, Claude's
SessionStart/UserPromptSubmit/Stop) can invoke fm-model-sync.sh
concurrently for the same task. The unlocked read-probe-write let
interleaved runs revert a newer effective model, mismatch its
source, or duplicate a model-history entry. Serialize the critical
section through the same per-task meta lock fm-spawn.sh already uses
(fm_meta_lock_path + fm_lock_acquire_wait/fm_lock_release), released
before every exit path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwfFjeYQcz9cZ3vEZohmpm
Comment thread bin/fm-spawn.sh
Comment on lines +2855 to +2866
pi.on("agent_start", () => {
busyEvent("busy", "agent-start");
modelSync();
});
pi.on("agent_settled", (_event: any, ctx: any) => {
if (ctx && typeof ctx.isIdle === "function" && !ctx.isIdle()) return;
return busyEvent("idle", "agent-settled");
});
pi.on("turn_end", () => execFile("touch", ["$TURNEND"]));
pi.on("turn_end", () => {
execFile("touch", ["$TURNEND"]);
modelSync();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Secondmate Pi models stay stale

When a Pi secondmate running through Herdr changes models after its initial spawn-time probe, its launch uses the separate watch and turn-end extensions rather than this extension containing the modelSync calls, causing the pane to keep showing the previous effective model and omit the fallback-history transition.

Valentino-Sole added a commit to Valentino-Sole/firstmate that referenced this pull request Sep 4, 2026
* fix: start a fresh supervision branch for every main session (kunchenguid#3600)

* fix(pi): start a new supervision branch conversation per main session

The supervision branch reopened one recorded conversation forever, so
every main session start reloaded the current generated prompt and then
weeks of accumulated thread, where a superseded rule could still outweigh
today's.

The branch conversation is now scoped to one main session: the session
generation owns the recorded conversation, so a cold start, /new,
/resume, /fork, or a reload always builds a new one, while a rebuild
inside one session (a model or effort change) still continues that
session's own conversation.

The dialog mirror re-anchors with it. Its durable cursor records what the
previous branch conversation received, so a /resume or reload - which
keeps main's own session file - would otherwise leave the new branch
blind to dialog main itself still has. The reset is bounded by the
current main session, and the cursor keeps advancing incrementally within
it. The durable outcome store and its processed marker are untouched, so
unacknowledged captain-facing outcomes still re-present on the new main
session.

* no-mistakes(document): Document fresh Pi supervision conversations

* no-mistakes(ci): Fixed the flaky concurrent inbox failure. Lock acquisition now retries when a competing lock disappears between a failed claim and inspection. Added a behavioral regression covering that race. Verified the full inbox test four times, project lint, and git diff checks

* feat: restart second mates after instruction updates (kunchenguid#3614)

* feat(update): restart second mates whose instructions changed

/updatefirstmate pulled new bytes onto disk and then asked each advanced
second mate to re-read them. A running agent holds AGENTS.md and every
loaded skill frozen from launch and no verified harness offers a reload,
so that steer could not reach a loaded skill at all and left the mate
holding two contradictory copies of its own job description.

An eligible mate is now restarted instead, in the same home and endpoint,
through the existing transactional relaunch. The restart is gated on the
mate first writing down the open work it holds only in conversation - the
open-record half of /stow, never its memory sweeps - so an unregistered
captain call is flushed before the conversation is spent. Anything that
leaves the reload unprovable falls back to the old re-read message and is
reported as exactly that, never as a clean reload.

Remote mates take the same path: fm-remote-secondmate-control.sh gains a
relaunch verb whose host-local leg runs that same control plane, since the
mate is an ordinary local secondmate from its host's point of view. The
primary resolves the profile and passes it explicitly, because
config/secondmate-harness is not inherited and the file on that host
belongs to a different home.

fm-update.sh now splits its advanced live mates into a restart set and a
nudge residual, and both sets require a changed instruction surface, which
also closes the over-nudge against the session-start sweep. Restart is
stricter still: a bin/-only advance reloads itself on the next call, so it
never costs a conversation.

Colocated tests cover the gating, the persist-then-restart order, the
task-subset persist request, each unsafe fallback, the remote hop, and the
remote sync's new instruction-surface report.

* no-mistakes(review): Fix restart correlation, concurrent waits, and lifecycle reporting

* no-mistakes(review): Parallelize relaunches and classify replacement incarnations

* no-mistakes(review): Gate restart actions on live agent state

* no-mistakes(review): Handle failed restart workers without hanging

* no-mistakes(review): Nudge legacy remotes and preserve persist recovery

* no-mistakes(review): Document one-time secondmate restart rollout

* no-mistakes(review): Honor arrived replies and refresh remote profiles

* no-mistakes(review): Revert remote parent profile reconciliation

* no-mistakes(review): Reset remote profile defaults and honor published results

* no-mistakes(review): Preserve fallback nudges for unverifiable secondmates

* no-mistakes(document): Document second-mate restart update flow

* no-mistakes(lint): Fix ShellCheck warnings in restart scripts

* perf: accelerate local validation with bounded concurrency (kunchenguid#3644)

* perf(tests): route gate verification through the bounded concurrent runner

Local validation was the pipeline's dominant cost: across 67 recorded
no-mistakes agent sessions on this repo, 99.3% of command execution was
`bash tests/*.test.sh`, run strictly one script at a time, and 2% of those
calls were killed by an agent-guessed timeout and paid for twice.

Three changes, each measured:

- `.no-mistakes.yaml` pins `commands.test` to
  `bin/fm-test-run.sh --changed --exclude-family real-herdr-gated`. The runner
  already owns changed-file selection, bounded concurrency, the refusal of
  unproven scripts, and a generous automatic per-script bound, so the gate's
  baseline is neither a serial chain nor a guessed timeout. It stays
  intent-targeted - the Test step still runs its evidence agent on top - and
  excludes the live-Herdr family the required Herdr lane owns.

- `bin/fm-test-run.sh` gives a plain list of script paths the same bounded
  automatic scheduler and automatic bound that `--changed` gets. Naming several
  subjects is how a verification round asks for exactly those scripts. The
  curated selections are untouched: `--lane` still composes CI shards whose
  serial lane must stay serial, `--family` is what the required Herdr lane runs,
  and `--all` stays a deliberate complete regression.

- `pr-forge` is admitted to the concurrent-safe family registry on two
  consecutive clean proofs. `docs/fm-test-isolation-proof.md` records those,
  and records `secondmate` and `session-bootstrap` as refused with the exact
  script and reason each failed on, so the refusals are actionable rather than
  silent.

Measured on this host, 0 failures on both sides:

  verification round, 4 scripts   448s chained -> 231s through the runner (-48%)
  pr-forge family                 409.2s at 1 worker -> 237.9s at 4 (1.72x)
  watcher-wake-lock family        1311.1s at 1 worker -> 539.3s at 4 (2.43x)

A fourth lever was implemented and then removed because the measurement
refused it: raising the bounded-wait sample interval from 0.1s to 0.5s made
`fm-watch-triage.test.sh` slower, 435s and 440s against 390s and 393s
unchanged, back to back. Those sleeps are not overhead added to the clock -
they are how a test waits for a subject moving on fm-watch.sh's own one-second
cadence - so sampling less often only delays detection. It also broke
`fm-watcher-lock.test.sh`, which catches a transient rather than waiting for a
settled condition. CONTRIBUTING.md records that result so the experiment is not
repeated.

* no-mistakes(review): Separate concurrent runs by isolation proof family

* no-mistakes(review): Limit automatic timeouts to changed-file validation

* no-mistakes(document): Clarify validation concurrency documentation

* fix: copy PR URLs from durable records (kunchenguid#3648)

* fix: copy PR URLs from records or abstain, never assemble them

Supervision reported a plausible but dead PR link three times because its
prompt demanded a full https:// URL at a moment when only a PR number was
observable, so the model assembled an owner/repository from memory, and the PR
check then accepted that URL and wrote it into the task record, after which the
model kept defending its own tool-endorsed guess over the worker's real link.

Three changes close that chain without any live forge lookup, so private
forges are treated exactly like public ones:

- bin/fm-branch-prompt.sh no longer mandates a URL. Its new "PR identity: copy
  or abstain" section requires a URL to be copied verbatim from a durable
  record (the done: PR <url> status line, pr= metadata, or the backlog note),
  forbids assembling owner, repository, host, or number from memory, and has
  the branch report only the identifier it actually holds when no record names
  the URL yet, leaving the PR check unarmed until the worker's ready line
  arrives. AGENTS.md section 7 and 9 carry the same copy-or-abstain rule for
  main in place of the bare full-URL mandate.

- Worker briefs (bin/fm-brief.sh, ship and scout rules) require the full
  https:// URL wherever a PR is mentioned - status line, terminal, or summary -
  never a bare "PR 108", so the link is in view as early as the number is.

- bin/fm-pr-check.sh refuses, offline and before any side effect, a URL that
  the task's own done lines contradict, printing both spellings; a log naming
  no URL still records the argument as before. fm_pr_status_ready_urls in
  bin/fm-pr-lib.sh owns reading those lines. The refusal also reaches
  bin/fm-pr-merge.sh, so nothing merges under a contradicted URL.

Tests cover the offline refusal with zero side effects, the recorded spelling
being accepted, markdown-wrapped and punctuated URLs, working lines not
counting, the merge wrapper propagation, a self-hosted merge request with no
forge call, the prompt carrying the rule, and the brief carrying the worker
rule.

* no-mistakes(review): Remove stale PR URL enforcement

* no-mistakes(ci): Removed backlog notes as an accepted PR identity source. PR URLs may now be copied only from the task’s `done: PR <url>` status or canonical `pr=` metadata; otherwise supervision reports only the known identifier and leaves PR checking unarmed. Updated related guidance/docs and verified with branch-supervision tests, brief tests, ShellCheck, and `git diff --check`

* fix(bin): disable Claude feedback drafts for fleet launches (kunchenguid#3661)

* fix(bin): disable Claude's feedback-draft flow for fleet-launched agents

Scope --settings '{"feedbackDrafts":"off"}' to every Firstmate-launched
Claude crewmate and secondmate, so /bug and /feedback never queue or
submit a bug report on the captain's behalf. feedbackDrafts is the
documented settings key (Claude Code changelog 2.1.247); the
per-launch CLI flag never touches the captain's global settings.json.

Claude-Session: https://claude.ai/code/session_01XYAXXzr4oZx9NjZb1veeE3

* no-mistakes(review): Prevent managed settings from re-enabling Claude feedback drafts

* no-mistakes(document): Fix Claude feedback documentation formatting

* fix(bin): layer both feedback-draft controls for defense in depth

The prior --settings-only fix can be overridden by a managed Claude
settings policy (feedbackDrafts precedence). Keep CLAUDE_CODE_SEND_FEEDBACK=0
alongside --settings '{"feedbackDrafts":"off"}': either control alone
disables the SendFeedback tool, so a managed override of one still
leaves the other in force.

Claude-Session: https://claude.ai/code/session_01XYAXXzr4oZx9NjZb1veeE3

* no-mistakes(document): Document Claude feedback-draft suppression ownership

* feat(tests): run three more validation families concurrently (kunchenguid#3662)

* perf(tests): admit three more families to concurrent validation

The three families that `docs/fm-test-isolation-proof.md` recorded as refused
were not refused for concurrency. Each blocker was a test that decided a
property by wall clock, or a script filed where it cannot run. Fixing those
three things admits all three families and recovers 28.6 minutes of local
validation with no assertion removed or weakened.

- `tests/fm-backlog-handoff.test.sh` injected its pre-move crash by killing the
  handoff, sleeping a fixed second, then delegating the move to the real
  binary. Nothing ever killed the fake, so on a host slow enough for the case's
  next assertions to take longer than a second, the orphan woke and completed
  the very move the case requires left undone, and recovery then failed with
  `Task "pre-move-crash" not found in this backlog`. Watching the two backlogs
  during the injected crash showed exactly that, the item moving one second
  after the crash. All four crash injections in the file now go through a new
  `fm_fake_crash_injector` shim that signals the target and returns only once
  it is observably gone, and the pre-move fake never delegates the move at all.

- `tests/fm-session-start.test.sh` proved the startup digest does not block on
  a slow current-state read by timing the whole digest against a fixed
  eight-second sleep, which a loaded host exceeds without the property being
  violated. It now holds that read open until the case releases it and asserts,
  the moment the digest returns, that the read has not finished. A digest that
  waited would wait indefinitely rather than for an interval a slow host can
  out-run, so the assertion is stronger than the bound it replaces. Its scan
  budget moves to the maximum, because the old value left two seconds of margin
  over the fixed sleep and measured the host rather than the deadline that
  `tests/fm-inactive-reconcile.test.sh` owns.

- `fm-backend-herdr-focus-flash-e2e` was filed in the family map's catch-all,
  which put it in the portable serial lane, where Linux CI gate-skips it: that
  real-Herdr regression was running nowhere. It moves to `real-herdr-gated` and
  the required Herdr lane. `fm-claude-stop-autoarm-live-e2e` gate-skips on its
  opt-in variable and moves to `live-harness-optin`.

The 28 remaining ungrouped scripts become an enumerated `standalone` family
instead of admitting `unclassified` itself. `unclassified` is the family map's
`*)` arm, so admitting it would silently grant concurrency to every test added
afterwards, which is exactly the population with no proof. A new test still
lands in `unclassified` and stays serial, and `tests/fm-test-run.test.sh`
covers that split behaviorally.

Each family passes two consecutive four-worker proofs with zero failures. On
the production runner, `secondmate` goes 1233.1s to 453.4s, `session-bootstrap`
756.4s to 286.4s, and `standalone` 724.6s to 261.1s: 2.71x overall and 1713.2s
recovered. The whole suite runs 177 scripts in 52.6 minutes of wall clock
against 121 minutes of summed script time.

* no-mistakes(document): Refresh concurrent validation and shard documentation

* no-mistakes(ci): Fixed the real-Herdr focus-flash E2E race exposed by reclassification. Part C now starts its persistent child atomically via `pane run` and verifies stable child identity through Herdr’s public `process-info` interface, avoiding the racy send-text/send-keys sequence and platform-specific `ps` matching. Verified with bash syntax checking, ShellCheck, git diff checks, and the complete E2E test on Herdr 0.8.2

* feat: structure no-mistakes ask-user escalations (kunchenguid#3670)

* feat(brief): structure no-mistakes ask-user escalation as event + snapshot file

Crewmates escalating a no-mistakes ask-user gate now report one status
event naming every finding id plus a snapshot file holding the gate's
axi finding records verbatim (id, severity, file, line, description,
authority), using the same shape even for a single finding. The status
line never paraphrases. The format is defined once in fm-dod-lib.sh and
rendered into both the scout and ship rule 6 in fm-brief.sh, so a
promoted scout - whose rule 6 fm-promote.sh preserves unchanged - gets
the identical contract as a freshly-spawned no-mistakes ship worker.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PpiWaDerbYavTLPPtEjQei

* no-mistakes(review): Preserve ask-user escalation output contract

* no-mistakes(review): Align escalation format test expectation

* no-mistakes(review): Scope ask-user escalation instructions correctly

* no-mistakes(review): Remove ask-user from generic decision rules

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* fix(bin): require self-sufficient no-mistakes intent (kunchenguid#3671)

* fix(bin): require a self-sufficient no-mistakes intent

A no-mistakes worker's --intent is only as useful as the string it
passes. PR kunchenguid#3604 shipped with an intent that was only "do 1, 2, 3, 7
from the report": the real contract lived in a private scout report and
never reached --intent, so nobody holding that string plus the codebase
could have derived the specification.

This is pure instruction at the contract's one owner; no spawn-side or
promotion-side check is added.

- bin/fm-dod-lib.sh: the generated no-mistakes Definition of done now
  states that the --intent string must be self-sufficient (the string
  plus the codebase reconstructs roughly the same specification) and
  tells the worker to write the substance of any report, decision, or
  PR the captain's intent refers to into --intent rather than the
  pointer, while Firstmate build instructions and the worker's own
  decisions still stay out. The spawn-time overlay points back at that
  rule so its "supersedes" wording cannot cancel it, and the header's
  owner statement carries the rule.
- AGENTS.md section 11 and bin/fm-brief.sh's header ask Firstmate to
  include the substance of referenced material when filling
  ## Captain's intent, and section 11 points at the owner of the rule.
- tests/fm-brief.test.sh and tests/fm-task-delivery.test.sh assert the
  rendered brief and launch contract carry the rule.

Claude-Session: https://claude.ai/code/session_01YMhEe42q7BAAoN6RxNuzim

* no-mistakes(document): Replace incident-specific intent test commentary

* fix: accelerate local Bearings snapshot composition (kunchenguid#3499)

* Speed local fleet snapshot composition

* no-mistakes(review): Stabilize task inventory during concurrent snapshot composition

* no-mistakes(document): Document local snapshot observation concurrency

* no-mistakes(ci): Fixed CI failures by making empty task manifests compatible with stock macOS Bash 3.2, snapshotting task metadata before concurrent observations to prevent generation drift, strengthening the behavioral race regression, and updating the stock-Bash Bearings test count to 45. Verified fleet snapshot tests (15), Bearings tests (45), workflow lint tests, project lint, Bash 3.2 parsing, and diff checks

* no-mistakes(ci): Fixed the Linux CI failure caused by passing large backlog/task JSON through jq command-line arguments, which exceeded the per-argument size limit. Both inventory projections now stream large JSON inputs through stdin. Verified with fm-bearings-snapshot.test.sh (45 tests), fm-fleet-snapshot-view.test.sh (15 tests), Bash syntax, and git diff checks

* no-mistakes(ci): Fixed concurrent task teardown during metadata capture: vanished metadata is now omitted while genuine copy failures remain fatal. Added a deterministic public Bearings regression test and updated CI’s expected test count. Verified with the full Bearings suite, workflow-lint suite, Bash syntax checks, and git diff checks

* no-mistakes(ci): Fixed PR-caused CI and review issues: streamed large fleet JSON through jq stdin to avoid Linux argument limits, kept crew-state reads bound to captured metadata generations, and strengthened the behavioral race test. Bearings (46 tests), fleet snapshot (15 tests), crew-state, backend, lint, Bash syntax, and diff checks pass locally. Serial shard 5’s unrelated task-inbox segmentation fault appears infrastructural/flaky

* no-mistakes(ci): Fixed endpoint-state generation crossing by validating captured spawn_gen before and after local endpoint probes, falling back to exact metadata identity for legacy tasks. Stale probe results now become unknown instead of false unhealthy state. Added a behavioral relaunch-race regression test. Verified the full Bearings snapshot suite, shellcheck, bash syntax, and git diff checks

* fix(snapshot): keep live observations generation-coherent

* no-mistakes(review): Keep secondmate observations generation-bound without copying reports

* no-mistakes(document): Document generation-coherent snapshot observations

* test(bearings): measure local read overlap instead of wall-clock budget

The large-local-snapshot regression asserted that a whole snapshot
composed in under five seconds. That bound measures how loaded the host
is, not whether the per-task reads actually overlap, so it failed
intermittently on a contended machine: one run in six on a box at load
16-20, landing exactly on the five second boundary.

Time a serialized run and a concurrent run of the same workload instead
and require the concurrent one to save at least two seconds. Both runs
pay the same composition overhead, so the difference isolates the
overlap this change delivers. Five one-second reads serialize into five
seconds and overlap into about one, and re-serializing the reads
collapses the saving to roughly zero, so the assertion still fails
loudly if the concurrency regresses.

Also bump the pinned Bearings test count to 48, since rebasing onto the
current default branch picked up its captain-hold test.

* no-mistakes(review): Restore JSON-derived decision flags

* no-mistakes(review): Unify status-derived snapshot observations

* no-mistakes(ci): Updated the stock macOS Bash CI check’s Bearings test count from 48 to 49. Verified the full Bearings suite passes and emits exactly 49 TAP successes; git diff checks pass

* fix: prevent stale supervision wake loops (kunchenguid#3672)

* fix(bin): stop the supervision branch's stale-ack and ghost-report loops

Clean-slate implementation of the four authorized recommendations from the
supervision-ghost-retrigger analysis (items 1, 2, 3, and 7), in their minimal
form, superseding PR kunchenguid#3604:

- fm_branch_report refuses a task the wake being handled never named. The
  extension fixes the reportable task set from the eligible rows before each
  prompt (signal and stale rows resolve to their tasks, a heartbeat allows any
  task with a live record, fleet is always allowed), so a report typed from
  memory about a task whose records teardown already removed is never stored
  or delivered.
- An acknowledgement that consumes nothing says "nothing was acknowledged
  through N" and prints the exact --ack-through / --recovery-generation
  command for the current presented wake, instead of "re-run the drain",
  which re-fed the same stale acknowledgement in a loop.
- bin/fm-guard.sh no longer tells the branch actor to drain queued wakes
  while it is handling them; it names the granted rows instead.
- Teardown removes state/.<task>.branch-outcome-index for ordinary tasks and
  descendants; the index rebuild and the append-side index write both skip a
  task with neither a live record nor a status log, so the branch's report of
  a teardown it just performed is stored without recreating the index.

No new locking, no spawn-generation binding, and no retired-task refusal: the
branch can still report the outcome of a task it just tore down, and the
teardown test now proves that path end to end.

* fix(bin): narrow the branch report scope and guard silence to the minimal form

Apply the four review decisions on the clean-slate branch:

- A signal or stale prompt may report only the tasks its own rows resolve
  to; fleet is refused there too. A heartbeat review is not scoped by task
  at all, so the extension no longer tracks live task records and refuses
  nothing by task id during a fleet review.
- The outcome-index rebuild no longer skips retired tasks; the append-side
  skip alone keeps a torn-down task's index from being recreated.
- bin/fm-guard.sh keeps the queued-wakes warning silent for the branch actor
  instead of printing a replacement note.

* no-mistakes(document): Align supervision docs with scoped wake handling

* fix(bin): avoid fleet snapshot argument limits (kunchenguid#3677)

* Fix fleet snapshot large JSON transport

* no-mistakes(review): Captain: file-back fleet snapshot transport safely

* no-mistakes(review): Captain: file-back parent summary aggregation

* no-mistakes(ci): Rebased the PR's three commits onto f4d7875 and resolved the fleet snapshot conflict while preserving the base's task-observation lifecycle. Fixed Greptile's valid finding by recursively removing the private mktemp transport directory, so future transport files cannot cause cleanup to fail. Verified with tests/fm-home-summary-refresh.test.sh, bin/fm-lint.sh, git diff --check, and ancestry checks. All passed; the fix remains as an uncommitted worktree change for the outer executor

* fix(bin): attribute active runs with unfetched pipeline heads (kunchenguid#3681)

* fix(bin): recognize active pipeline fix rounds with unfetched run heads

A no-mistakes fix round advances the run head beyond the submitted head,
and the pipeline commits in its own checkout, so the task copy never
receives the new commit object. fm-crew-state's strict head rule rejected
the active row, the coarse runs-list scan skipped it and matched the
older failed row at the submitted head, and an active validation read as
failed (observed on model-routing-benchmark-hardening: active head
ac61c64 vs task copy at fb47636d).

fm_nm_runs_status_for_worktree in bin/fm-nm-run-lib.sh now owns
runs-ledger attribution: the branch's newest row alone decides, and a
newest row whose head cannot resolve locally is recognized only as a
provable pipeline-owned continuation - active (running) and anchored by
the immediately older row for the same branch having ended at exactly
this worktree's HEAD. The reader keeps the axi TOON as full detail for
that proven same-branch run. Unanchored, ancestor-anchored, and terminal
unresolvable rows stay unattributed, so branch-name coincidence and other
tasks' runs never match, and fm_nm_head_matches_worktree keeps its exact
prior semantics for teardown (verified by the full teardown suite).

Tests: reproduction regression for the unfetched active fix head (reads
working via full run-step detail), coarse-path continuation when axi
answers another branch, and negative controls for the unanchored active
row and the unresolvable terminal row with the historical fallback
preserved.

Ported onto upstream/main f4d7875, where kunchenguid#3194 independently added the
branch_sync custody exemption on the full axi-status path: both mechanisms
now coexist, each owning one surface (TOON custody on the full path, the
runs ledger on the coarse path). The port deletes the superseded coarse
scan-and-skip (nm_runs_status_for_branch) and its now caller-less helpers
(fm_nm_head_resolvable, nm_coarse_head_matches_worktree), renames the
exemption comment's "the one exemption" phrasing now that a second
complementary exemption exists, and points the stale
FM_CREW_STATE_RUNS_LIMIT comment at fm_nm_runs_status_for_worktree
(judge follow-up #1). The parent coarse-guard test's fixture is the
ledger-anchored continuation shape, so its expectation flips to the fixed
behavior (working via run-step, never the older failed row); a new
mismatched-anchor coarse negative control preserves that guard's original
no-anchor protection (pane answers, never the older row).

* no-mistakes(document): Clarify pipeline attribution documentation

* fix(bin): pre-register claude workspace trust at spawn time (kunchenguid#3663)

* fix(bin): pre-register claude workspace trust for task worktrees

A claude crewmate launched into a fresh task worktree met Claude Code's
interactive workspace-trust dialog before it ever read its brief, and firstmate
could not answer it: the key plane carries only Enter, Escape, and C-c with no
arrow navigation, and the dialog's selection starts on "No, exit", so the
documented Enter recipe ended the session instead of accepting it. Two workers
wedged this way and were unblocked only by hand-seeding the trust store per
path.

--dangerously-skip-permissions does not cover that gate. `claude --help`
records the dialog as skipped only in non-interactive mode, through -p or a
non-TTY stdout, and a crewmate pane is interactive, so there is no launch flag
to reach for.

fm-spawn now pre-registers the worktree through bin/fm-claude-trust.sh in the
existing claude branch, before the project settings that the same gate would
otherwise block, and refuses the spawn when that write fails rather than
launching a worker that would wedge.

The scope test is the safety property and is structural rather than a path
policy: the path must be a linked git worktree, sharing the spawning project's
common dir, whose top level is exactly the resolved argument. Git is the ground
truth, so the argument is never trusted on its own word, and a primary
checkout, an unrelated repo, a worktree subdirectory, a plain directory, and a
home directory are each refused rather than warned about or skipped. A
treehouse or orca path prefix was deliberately avoided because treehouse's root
is configurable, which would make a prefix both wrong and a new policy surface.
One structural test covers both worktree providers.

tests/fm-claude-trust.test.sh pins both halves, including a case where HOME is
itself a valid linked worktree so the home guard is proven load-bearing rather
than passing vacuously, plus the spawn-level proof that a claude spawn trusts
its worktree and launches with the brief pointed at the same store.

The adapter reference no longer tells a firstmate to press Enter on that
dialog, and the shared trust reference now names every harness surface: which
harnesses gate, which suppress at launch, which dodge the gate, which now
pre-registers, and that a claude secondmate is excluded by design.

The spawn fixture runs each spawn against a throwaway HOME so the suite cannot
write the developer's real store, isolating through HOME rather than
CLAUDE_CONFIG_DIR because the spawn forwards a set CLAUDE_CONFIG_DIR onto the
launch command that launch-shape assertions read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HNEN2GLnew27HFyfi4ms4v

* fix(bin): create the staged trust store exclusively

The staged store was written to a predictable pid-based path with a plain
write, which follows a symlink. Where the Claude config directory is writable
by another local account, that account could pre-create the path as a symlink
and redirect the write into another file the launching user owns.

The staged name now carries random bytes and is created with an exclusive
"wx" open, so an existing path is refused outright instead of followed. The
happy-path test also asserts no staged store survives the rename.

The durability comment now states the residual window plainly: the readback
proves the entry landed, not that it survives, because a vendor session that
rewrites the whole store afterwards can still drop it and no lock closes that
window when the writer is Claude itself. The worker then meets the dialog and
stalls, which reaches firstmate as the ordinary stale wake rather than as
silent success.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HNEN2GLnew27HFyfi4ms4v

* no-mistakes(review): neutralise CDPATH in claude trust scope guard

* no-mistakes(review): sandbox HOME in spawn tests, drop out-of-scope artifacts

* no-mistakes(review): refuse unresolvable git dir, compact store, fix secondmate doc

* no-mistakes(review): clear git env overrides, resolve symlinked store target

* no-mistakes(review): degrade without node, fix Pi gate claim, record trust proof

* no-mistakes(review): refuse without node, pin CLAUDE_CONFIG_DIR in spawn tests

* no-mistakes(review): refuse relative config dir and concurrent store modification

* no-mistakes(review): correct orca worktree claim, clean staged store on failure

* no-mistakes(review): restore pretty-printed store, correct trust dialog docs

* no-mistakes(review): arm trust gate before busy state to avoid orphans

* no-mistakes(document): record claude trust pre-registration in its owner docs

* no-mistakes(document): note orca limit for claude trust pre-registration

* no-mistakes(ci): Fixed the Greptile P1 on bin/fm-spawn.sh by moving the Claude trust gate earlier rather than adding cleanup machinery. Diagnosis: Greptile reported that when Claude trust registration fails on tmux/Zellij/cmux/non-projected Herdr, the exit runs after the backend endpoint and /tmp/fm-<id> were created, and the abort trap cleans neither. The endpoint half is pre-existing, deliberate architecture — the two refusals immediately above the gate (the 60s `treehouse get` timeout at fm-spawn.sh:2550 and `validate_spawn_worktree` at :2487) also exit with the endpoint live and direct the operator with "inspect window $T"; spawn_abort_cleanup only reclaims orca endpoints (already covered via ORCA_ABORT_CLEANUP) and herdr projections. The temp-root half was genuinely introduced by this PR: the gate was placed beside the busy-state arm, ~30 lines after `mkdir -p "$TASK_TMP/gotmp"`, and fm-teardown can only find that root through `tasktmp=` in a meta record a refused spawn never publishes. Root-cause fix (smallest correct change, no new subsystem): - bin/fm-spawn.sh — moved the `claude*` trust gate from inside the busy-arm block up to the first point $WT is known, immediately after the `freshen_spawn_worktree_base` block and before TASK_TMP creation, the STATE setup, and the relaunch `clear_relaunch_harness_wiring` retirement. A refusal now leaves no temp root, no retired relaunch wiring, and no busy record; only the endpoint remains, in the same class as the two refusals just above it. - bin/fm-spawn.sh — the refusal message now ends with "inspect window $T", matching the existing convention so control/teardown can identify the endpoint. $T is set for every backend on the non-secondmate path. - bin/fm-spawn.sh:196 — header note corrected from "before any state is armed" to "before any per-task state exists". - tests/fm-claude-trust.test.sh — the existing refused-spawn test's own comment claimed "before any task state exists" but only asserted busy state. Renamed to test_refused_spawn_leaves_no_task_state and added an assertion that /tmp/fm-<id> is absent, with the task id suffixed by the test process pid so the assertion reads only this run's path (a stale /tmp/fm-refusedspawn from the fixed-id version was in fact present on this box). No assertions on implementation source bytes. Verification run locally: - The new assertion fails against the pre-fix bin/fm-spawn.sh ("not ok - a refused spawn stranded a temp root no teardown can find") and passes after — a real before/after regression proof. - tests/fm-claude-trust.test.sh: 20/20 ok. - tests/fm-backend.test.sh, fm-backend-orca, fm-control-relaunch, fm-spawn-dispatch-profile, fm-trace-context-spawn, fm-gotmp: all pass. - tests/fm-backlog-atomicity.test.sh: rc=0, 79 assertions ok. - bin/fm-lint.sh (repo's single lint owner, pinned ShellCheck 0.11.0 + actionlint 1.7.12): clean. - No /tmp/fm-refusedspawn* leftovers after the runs. Scope respected: no trust subsystem, no policy layer, no config surface, no endpoint-cleanup mechanism added; the change is an ordering move plus one error-message clause and the test that pins it. Adapter references and docs made no ordering claim, so none needed updating. Changes are left uncommitted in the worktree for the outer executor

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* fix: restart every live second mate after updates (kunchenguid#3690)

* feat(update): restart every live second mate after a successful update

/updatefirstmate only restarted a second mate when that pass advanced its
AGENTS.md or .agents/skills. An already-current home was skipped entirely, a
bin/-only advance was steered instead, and a remote host that could not report
its instruction diff was downgraded to a re-read. A running agent also freezes
its launch-time wiring - turn-end hooks, harness flags, per-harness feature
switches - and none of that is derivable from a file diff, so an unchanged
tracked surface is not evidence the agent is already on the current behavior.

Restart is now unconditional on a successful update of that home. Every live
second mate the pass leaves on the target commit is restarted, whether it
advanced or was already there.

The safety contract is unchanged: open records are persisted before the agent is
replaced, nothing is forced, stashed, or discarded, a home the pass had to skip
is not restarted at all, and a mate whose runtime cannot prove a restart keeps
the honest re-read path and is never reported as reloaded.

bin/fm-ff-lib.sh gains a settled-state hook that fires for a home left at the
base whether it advanced or was already there, and never for a skipped one; the
instruction-gated hook the session-start convergence sweep uses is untouched.

Regressions: fm-update pins the already-current mate into the restart set and
the unprovable one into the nudge set, and fm-secondmate-restart drives both
real commands end to end - an already-current home is named, persisted, and
genuinely replaced with its checkout untouched, while the unprovable one keeps
its running agent.

* no-mistakes(document): Document unconditional secondmate restarts

* fix(bin): close pending-reply decisions via resolve-key (kunchenguid#3696)

* fix(bin): close reserved pending-reply keys via fm-send --resolve-key

fm-send wrote answered: notes that the reserved-key fold ignores, so
operator closes exited 0 while OPEN DECISIONS kept the decision open.
Speak the owning library's close vocabulary on that path, and refuse
when a reserved close cannot take effect.

* no-mistakes(review): Safely quote manual decision-close recovery commands

* no-mistakes(review): Reject unclosable overlong decision keys before sending

* no-mistakes(review): Remove contract suffix from open decisions hint

* no-mistakes(document): Document resolve-key line-cap refusal

* fix(bin): prevent false missed-reply escalations (kunchenguid#3697)

* fix(bin): stop false missed-reply escalations for same-basename self-home answers

A healthy secondmate that wrote corr= to its own state/<id>.status never matched the parent channel, so recovery confirmed and the record escalated as pending-reply-missed. Make the report helper resolve the parent channel itself, skip parent-replies.status as wrong-home, put a readable sighting path on the missed line, and restatement-copy only that same-basename self-home file onto the parent channel.

* no-mistakes(review): Resolve late replies before recovery escalation

* no-mistakes(review): Tighten reply routing and regression coverage

* no-mistakes(review): Preserve reply paths and require explicit home

* no-mistakes(review): Encode wrong-home paths before persistence

* no-mistakes(document): Document corrected secondmate reply routing

* no-mistakes(lint): Fix pending-reply ShellCheck warnings

* feat: add verified Gemini crewmate runtime (kunchenguid#3695)

* feat(harness): verify gemini as a crewmate runtime adapter

Adds Gemini CLI as a fourth dispatch target alongside claude, codex, and
grok, scoped to crewmate and scout work only. Every axis was proven against
gemini-cli 0.58.0 rather than inferred; docs/verification/runtime-backends.md
carries the dated evidence and names what stayed unverified.

Busy state is semantic, not rendered: BeforeAgent opens a turn and AfterAgent
and SessionEnd close it. AfterAgent also fires on a manual interrupt, so a
cancelled turn closes its own record.

Three findings shaped the wiring rather than a config line:

- --skip-trust and GEMINI_CLI_TRUST_WORKSPACE=true are presented by the CLI
  as equivalents and are not. A controlled A/B showed --skip-trust leaves
  project configuration unloaded, so workspace skills never load.
- The worktree's .gemini/settings.json is the PROJECT's committed settings
  file, unlike claude's settings.local.json. Firstmate's hooks therefore go
  to a firstmate-owned state/<id>.gemini-settings.json reached through
  GEMINI_CLI_SYSTEM_SETTINGS_PATH, which also works untrusted and merges
  with a project's own hooks instead of replacing them.
- The shipped CLI is a node bundle whose live process reports comm as
  MainThread, so ancestry cannot see it. GEMINI_CLI=1 is load-bearing and is
  tested before an inherited CLAUDECODE, and pane liveness identifies gemini
  from the script argument through the new bin/fm-gemini-lib.sh.

Gemini is refused for secondmates: it has no primary supervision protocol.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYdQkKfSEQrFUxtcTXZ66L

* test: clear gemini's marker in launch and detection expectations

Every non-gemini launch now clears GEMINI_CLI the way it already clears
cursor's markers, so the two tests that pin the exact launch prefix are
updated to match. The harness-detection tests that scrub foreign markers
before probing ancestry scrub GEMINI_CLI too, so running the suite from
inside a gemini session cannot produce a false verdict.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYdQkKfSEQrFUxtcTXZ66L

* docs: classify the gemini harness reference

The documentation inventory is the single classification owner for maintained
prose surfaces, and every surface must appear in it exactly once. The new
harness reference is agent-runtime, matching its siblings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYdQkKfSEQrFUxtcTXZ66L

* no-mistakes(review): Narrow Gemini ancestry detection

* no-mistakes(review): Restrict Gemini hooks to canonical launches

* no-mistakes(document): Document Gemini adapter support boundaries

* no-mistakes(ci): Fixed Gemini process identity when interpreter or script paths contain whitespace. Tmux liveness now uses NUL-delimited /proc argv on Linux, with the existing flattened ps fallback elsewhere. Added a real-process regression test. Verified with the Gemini harness test suite, full fm-lint, ShellCheck, and git diff --check. The CI and Require no-mistakes runs were action_required/attestation outcomes rather than code failures

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(teardown): conclude parked runs advanced past task copy (kunchenguid#3704)

* conclude parked runs the pipeline advanced past the task copy

A no-mistakes fix round commits in the daemon's own gate-repo clone, so a
run parked at a gate can carry a head whose object the task copy never
received. Teardown's strict object-local identity rule then declined to
conclude the run, and cleanup left it parked forever holding a fleet slot
(observed 2026-09-03; the same masking condition PR 3681 fixed on the
read path, now closing the teardown half its scope boundary deferred).

task_status_is_own_parked_run now falls back - only when the reported
head resolves to no local object - to the one shared runs-ledger
attribution rule fm_nm_runs_status_for_worktree (bin/fm-nm-run-lib.sh),
whose anchored continuation proof binds the branch's newest active row
to this worktree's exact submitted head. Foreign branches, stale
history, terminal rows, ancestor-only anchors, diverged newer rows, and
ambiguous multi-row shapes all still refuse, and runs that are actively
running, fixing, or in CI remain untouched: only the parked-at-a-gate
determination ever reaches the abort. No sqlite access, no fetches into
another task copy, no custody changes, no duplicated matching logic.

* tighten the parked-run ledger fallback and pin both judge corrections

The teardown ledger fallback now authorizes concluding this task's parked
run only when the shared runs-ledger rule's proved answer is the explicitly
active word (running): a terminal newest row - even anchored at exactly the
worktree's head - is finished history and never an abort authorization.
The read path may classify the same owner's answer; teardown's abort must
never fire for a run that already ended.

Two bounded pre-validation corrections from the implementation review:
- a fetched-object counterfactual pins the strict-rule path: a pipeline fix
  head fetched into the task copy aborts through object-local identity
  alone, with an empty ledger and a proof the runs query never fired;
- a negative fixture pins the tightened boundary: an unresolvable reported
  head with a terminal newest same-branch row anchored at the worktree head
  engages the ledger fallback and still refuses, so the refusal is the
  terminal-word boundary and not an earlier guard.

* no-mistakes(review): Bind teardown ledger fallback to validated run heads

* no-mistakes(review): Restore validated advanced-head ledger continuation

* no-mistakes(review): Reject invalid ledger dates and terminal statuses

* no-mistakes(document): Document teardown ledger scan limit

* feat(bin): show requested vs effective model in Herdr agent view

Track spawn-config requested_model separately from runtime-verified
effective_model, probe Claude/Pi transcripts for exact API ids, push
compact display metadata to Herdr, and preserve verified models across
relaunch/compaction hooks without inferring aliases as truth.

* fix(bin): keep re-probing effective model after first exact reading

fm-model-sync.sh only probed for the runtime-verified effective model
while it was still pending/UNKNOWN, so a session that later switched
models (manual switch, provider fallback) kept displaying the first
verified model forever and never appended a fallback-history entry.
Probe unconditionally instead; fm_model_record_effective already
no-ops when the probed value is unchanged, so this stays cheap.

Addresses the Greptile P1 finding on PR kunchenguid#3705's fm-model-sync.sh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwfFjeYQcz9cZ3vEZohmpm

* fix(bin): distinguish Cursor Grok, direct xAI Grok, and Anthropic Claude in the display

Kapitänskorrektur: harness alone conflated Cursor-hosted Grok models
(cursor-grok-4.6-*) and direct xAI Grok models (xai/grok-4.6) under
one generic label, and displayed Anthropic Claude without naming the
provider. Add fm_model_source_label, pattern-matched on the verified
exact model id, so the compact display always reads Cursor · Grok,
xAI · Grok, or Anthropic · Claude with the exact model id appended.
Falls back to the existing harness label for every other model. No
routing change: this only affects display strings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwfFjeYQcz9cZ3vEZohmpm

* fix(bin): wire model-sync into the Pi extension's turn lifecycle

fm-model-sync.sh was only invoked from Claude's SessionStart/
UserPromptSubmit/Stop hooks; the Pi harness's own extension
(state/<id>.pi-ext.ts) never called it, so a Pi-hosted session (e.g.
a pi/xai-grok crewmate) never refreshed its effective model after the
first probe and Herdr kept showing the stale value with no
fallback-history entry. Call fm-model-sync.sh from the same
agent_start/turn_end boundaries Pi already uses for busy-state and the
turn-end notification touch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwfFjeYQcz9cZ3vEZohmpm

* fix(bin): serialize fm-model-sync.sh's meta read-probe-write

Overlapping lifecycle events (Pi's agent_start/turn_end, Claude's
SessionStart/UserPromptSubmit/Stop) can invoke fm-model-sync.sh
concurrently for the same task. The unlocked read-probe-write let
interleaved runs revert a newer effective model, mismatch its
source, or duplicate a model-history entry. Serialize the critical
section through the same per-task meta lock fm-spawn.sh already uses
(fm_meta_lock_path + fm_lock_acquire_wait/fm_lock_release), released
before every exit path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwfFjeYQcz9cZ3vEZohmpm

---------

Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Arthur Haro <38157909+haroarthur@users.noreply.github.com>
Co-authored-by: Nicolas Payette <nicolas.payette@specira.ai>
Co-authored-by: Jon Roosevelt <rooseveltadvisors@gmail.com>
Co-authored-by: att430 <41454889+att430@users.noreply.github.com>
Co-authored-by: Valentino-Sole <171032438+Valentino-Sole@users.noreply.github.com>
Valentino-Sole added a commit to Valentino-Sole/firstmate that referenced this pull request Sep 8, 2026
* fix: start a fresh supervision branch for every main session (kunchenguid#3600)

* fix(pi): start a new supervision branch conversation per main session

The supervision branch reopened one recorded conversation forever, so
every main session start reloaded the current generated prompt and then
weeks of accumulated thread, where a superseded rule could still outweigh
today's.

The branch conversation is now scoped to one main session: the session
generation owns the recorded conversation, so a cold start, /new,
/resume, /fork, or a reload always builds a new one, while a rebuild
inside one session (a model or effort change) still continues that
session's own conversation.

The dialog mirror re-anchors with it. Its durable cursor records what the
previous branch conversation received, so a /resume or reload - which
keeps main's own session file - would otherwise leave the new branch
blind to dialog main itself still has. The reset is bounded by the
current main session, and the cursor keeps advancing incrementally within
it. The durable outcome store and its processed marker are untouched, so
unacknowledged captain-facing outcomes still re-present on the new main
session.

* no-mistakes(document): Document fresh Pi supervision conversations

* no-mistakes(ci): Fixed the flaky concurrent inbox failure. Lock acquisition now retries when a competing lock disappears between a failed claim and inspection. Added a behavioral regression covering that race. Verified the full inbox test four times, project lint, and git diff checks

* feat: restart second mates after instruction updates (kunchenguid#3614)

* feat(update): restart second mates whose instructions changed

/updatefirstmate pulled new bytes onto disk and then asked each advanced
second mate to re-read them. A running agent holds AGENTS.md and every
loaded skill frozen from launch and no verified harness offers a reload,
so that steer could not reach a loaded skill at all and left the mate
holding two contradictory copies of its own job description.

An eligible mate is now restarted instead, in the same home and endpoint,
through the existing transactional relaunch. The restart is gated on the
mate first writing down the open work it holds only in conversation - the
open-record half of /stow, never its memory sweeps - so an unregistered
captain call is flushed before the conversation is spent. Anything that
leaves the reload unprovable falls back to the old re-read message and is
reported as exactly that, never as a clean reload.

Remote mates take the same path: fm-remote-secondmate-control.sh gains a
relaunch verb whose host-local leg runs that same control plane, since the
mate is an ordinary local secondmate from its host's point of view. The
primary resolves the profile and passes it explicitly, because
config/secondmate-harness is not inherited and the file on that host
belongs to a different home.

fm-update.sh now splits its advanced live mates into a restart set and a
nudge residual, and both sets require a changed instruction surface, which
also closes the over-nudge against the session-start sweep. Restart is
stricter still: a bin/-only advance reloads itself on the next call, so it
never costs a conversation.

Colocated tests cover the gating, the persist-then-restart order, the
task-subset persist request, each unsafe fallback, the remote hop, and the
remote sync's new instruction-surface report.

* no-mistakes(review): Fix restart correlation, concurrent waits, and lifecycle reporting

* no-mistakes(review): Parallelize relaunches and classify replacement incarnations

* no-mistakes(review): Gate restart actions on live agent state

* no-mistakes(review): Handle failed restart workers without hanging

* no-mistakes(review): Nudge legacy remotes and preserve persist recovery

* no-mistakes(review): Document one-time secondmate restart rollout

* no-mistakes(review): Honor arrived replies and refresh remote profiles

* no-mistakes(review): Revert remote parent profile reconciliation

* no-mistakes(review): Reset remote profile defaults and honor published results

* no-mistakes(review): Preserve fallback nudges for unverifiable secondmates

* no-mistakes(document): Document second-mate restart update flow

* no-mistakes(lint): Fix ShellCheck warnings in restart scripts

* perf: accelerate local validation with bounded concurrency (kunchenguid#3644)

* perf(tests): route gate verification through the bounded concurrent runner

Local validation was the pipeline's dominant cost: across 67 recorded
no-mistakes agent sessions on this repo, 99.3% of command execution was
`bash tests/*.test.sh`, run strictly one script at a time, and 2% of those
calls were killed by an agent-guessed timeout and paid for twice.

Three changes, each measured:

- `.no-mistakes.yaml` pins `commands.test` to
  `bin/fm-test-run.sh --changed --exclude-family real-herdr-gated`. The runner
  already owns changed-file selection, bounded concurrency, the refusal of
  unproven scripts, and a generous automatic per-script bound, so the gate's
  baseline is neither a serial chain nor a guessed timeout. It stays
  intent-targeted - the Test step still runs its evidence agent on top - and
  excludes the live-Herdr family the required Herdr lane owns.

- `bin/fm-test-run.sh` gives a plain list of script paths the same bounded
  automatic scheduler and automatic bound that `--changed` gets. Naming several
  subjects is how a verification round asks for exactly those scripts. The
  curated selections are untouched: `--lane` still composes CI shards whose
  serial lane must stay serial, `--family` is what the required Herdr lane runs,
  and `--all` stays a deliberate complete regression.

- `pr-forge` is admitted to the concurrent-safe family registry on two
  consecutive clean proofs. `docs/fm-test-isolation-proof.md` records those,
  and records `secondmate` and `session-bootstrap` as refused with the exact
  script and reason each failed on, so the refusals are actionable rather than
  silent.

Measured on this host, 0 failures on both sides:

  verification round, 4 scripts   448s chained -> 231s through the runner (-48%)
  pr-forge family                 409.2s at 1 worker -> 237.9s at 4 (1.72x)
  watcher-wake-lock family        1311.1s at 1 worker -> 539.3s at 4 (2.43x)

A fourth lever was implemented and then removed because the measurement
refused it: raising the bounded-wait sample interval from 0.1s to 0.5s made
`fm-watch-triage.test.sh` slower, 435s and 440s against 390s and 393s
unchanged, back to back. Those sleeps are not overhead added to the clock -
they are how a test waits for a subject moving on fm-watch.sh's own one-second
cadence - so sampling less often only delays detection. It also broke
`fm-watcher-lock.test.sh`, which catches a transient rather than waiting for a
settled condition. CONTRIBUTING.md records that result so the experiment is not
repeated.

* no-mistakes(review): Separate concurrent runs by isolation proof family

* no-mistakes(review): Limit automatic timeouts to changed-file validation

* no-mistakes(document): Clarify validation concurrency documentation

* fix: copy PR URLs from durable records (kunchenguid#3648)

* fix: copy PR URLs from records or abstain, never assemble them

Supervision reported a plausible but dead PR link three times because its
prompt demanded a full https:// URL at a moment when only a PR number was
observable, so the model assembled an owner/repository from memory, and the PR
check then accepted that URL and wrote it into the task record, after which the
model kept defending its own tool-endorsed guess over the worker's real link.

Three changes close that chain without any live forge lookup, so private
forges are treated exactly like public ones:

- bin/fm-branch-prompt.sh no longer mandates a URL. Its new "PR identity: copy
  or abstain" section requires a URL to be copied verbatim from a durable
  record (the done: PR <url> status line, pr= metadata, or the backlog note),
  forbids assembling owner, repository, host, or number from memory, and has
  the branch report only the identifier it actually holds when no record names
  the URL yet, leaving the PR check unarmed until the worker's ready line
  arrives. AGENTS.md section 7 and 9 carry the same copy-or-abstain rule for
  main in place of the bare full-URL mandate.

- Worker briefs (bin/fm-brief.sh, ship and scout rules) require the full
  https:// URL wherever a PR is mentioned - status line, terminal, or summary -
  never a bare "PR 108", so the link is in view as early as the number is.

- bin/fm-pr-check.sh refuses, offline and before any side effect, a URL that
  the task's own done lines contradict, printing both spellings; a log naming
  no URL still records the argument as before. fm_pr_status_ready_urls in
  bin/fm-pr-lib.sh owns reading those lines. The refusal also reaches
  bin/fm-pr-merge.sh, so nothing merges under a contradicted URL.

Tests cover the offline refusal with zero side effects, the recorded spelling
being accepted, markdown-wrapped and punctuated URLs, working lines not
counting, the merge wrapper propagation, a self-hosted merge request with no
forge call, the prompt carrying the rule, and the brief carrying the worker
rule.

* no-mistakes(review): Remove stale PR URL enforcement

* no-mistakes(ci): Removed backlog notes as an accepted PR identity source. PR URLs may now be copied only from the task’s `done: PR <url>` status or canonical `pr=` metadata; otherwise supervision reports only the known identifier and leaves PR checking unarmed. Updated related guidance/docs and verified with branch-supervision tests, brief tests, ShellCheck, and `git diff --check`

* fix(bin): disable Claude feedback drafts for fleet launches (kunchenguid#3661)

* fix(bin): disable Claude's feedback-draft flow for fleet-launched agents

Scope --settings '{"feedbackDrafts":"off"}' to every Firstmate-launched
Claude crewmate and secondmate, so /bug and /feedback never queue or
submit a bug report on the captain's behalf. feedbackDrafts is the
documented settings key (Claude Code changelog 2.1.247); the
per-launch CLI flag never touches the captain's global settings.json.

Claude-Session: https://claude.ai/code/session_01XYAXXzr4oZx9NjZb1veeE3

* no-mistakes(review): Prevent managed settings from re-enabling Claude feedback drafts

* no-mistakes(document): Fix Claude feedback documentation formatting

* fix(bin): layer both feedback-draft controls for defense in depth

The prior --settings-only fix can be overridden by a managed Claude
settings policy (feedbackDrafts precedence). Keep CLAUDE_CODE_SEND_FEEDBACK=0
alongside --settings '{"feedbackDrafts":"off"}': either control alone
disables the SendFeedback tool, so a managed override of one still
leaves the other in force.

Claude-Session: https://claude.ai/code/session_01XYAXXzr4oZx9NjZb1veeE3

* no-mistakes(document): Document Claude feedback-draft suppression ownership

* feat(tests): run three more validation families concurrently (kunchenguid#3662)

* perf(tests): admit three more families to concurrent validation

The three families that `docs/fm-test-isolation-proof.md` recorded as refused
were not refused for concurrency. Each blocker was a test that decided a
property by wall clock, or a script filed where it cannot run. Fixing those
three things admits all three families and recovers 28.6 minutes of local
validation with no assertion removed or weakened.

- `tests/fm-backlog-handoff.test.sh` injected its pre-move crash by killing the
  handoff, sleeping a fixed second, then delegating the move to the real
  binary. Nothing ever killed the fake, so on a host slow enough for the case's
  next assertions to take longer than a second, the orphan woke and completed
  the very move the case requires left undone, and recovery then failed with
  `Task "pre-move-crash" not found in this backlog`. Watching the two backlogs
  during the injected crash showed exactly that, the item moving one second
  after the crash. All four crash injections in the file now go through a new
  `fm_fake_crash_injector` shim that signals the target and returns only once
  it is observably gone, and the pre-move fake never delegates the move at all.

- `tests/fm-session-start.test.sh` proved the startup digest does not block on
  a slow current-state read by timing the whole digest against a fixed
  eight-second sleep, which a loaded host exceeds without the property being
  violated. It now holds that read open until the case releases it and asserts,
  the moment the digest returns, that the read has not finished. A digest that
  waited would wait indefinitely rather than for an interval a slow host can
  out-run, so the assertion is stronger than the bound it replaces. Its scan
  budget moves to the maximum, because the old value left two seconds of margin
  over the fixed sleep and measured the host rather than the deadline that
  `tests/fm-inactive-reconcile.test.sh` owns.

- `fm-backend-herdr-focus-flash-e2e` was filed in the family map's catch-all,
  which put it in the portable serial lane, where Linux CI gate-skips it: that
  real-Herdr regression was running nowhere. It moves to `real-herdr-gated` and
  the required Herdr lane. `fm-claude-stop-autoarm-live-e2e` gate-skips on its
  opt-in variable and moves to `live-harness-optin`.

The 28 remaining ungrouped scripts become an enumerated `standalone` family
instead of admitting `unclassified` itself. `unclassified` is the family map's
`*)` arm, so admitting it would silently grant concurrency to every test added
afterwards, which is exactly the population with no proof. A new test still
lands in `unclassified` and stays serial, and `tests/fm-test-run.test.sh`
covers that split behaviorally.

Each family passes two consecutive four-worker proofs with zero failures. On
the production runner, `secondmate` goes 1233.1s to 453.4s, `session-bootstrap`
756.4s to 286.4s, and `standalone` 724.6s to 261.1s: 2.71x overall and 1713.2s
recovered. The whole suite runs 177 scripts in 52.6 minutes of wall clock
against 121 minutes of summed script time.

* no-mistakes(document): Refresh concurrent validation and shard documentation

* no-mistakes(ci): Fixed the real-Herdr focus-flash E2E race exposed by reclassification. Part C now starts its persistent child atomically via `pane run` and verifies stable child identity through Herdr’s public `process-info` interface, avoiding the racy send-text/send-keys sequence and platform-specific `ps` matching. Verified with bash syntax checking, ShellCheck, git diff checks, and the complete E2E test on Herdr 0.8.2

* feat: structure no-mistakes ask-user escalations (kunchenguid#3670)

* feat(brief): structure no-mistakes ask-user escalation as event + snapshot file

Crewmates escalating a no-mistakes ask-user gate now report one status
event naming every finding id plus a snapshot file holding the gate's
axi finding records verbatim (id, severity, file, line, description,
authority), using the same shape even for a single finding. The status
line never paraphrases. The format is defined once in fm-dod-lib.sh and
rendered into both the scout and ship rule 6 in fm-brief.sh, so a
promoted scout - whose rule 6 fm-promote.sh preserves unchanged - gets
the identical contract as a freshly-spawned no-mistakes ship worker.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PpiWaDerbYavTLPPtEjQei

* no-mistakes(review): Preserve ask-user escalation output contract

* no-mistakes(review): Align escalation format test expectation

* no-mistakes(review): Scope ask-user escalation instructions correctly

* no-mistakes(review): Remove ask-user from generic decision rules

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* fix(bin): require self-sufficient no-mistakes intent (kunchenguid#3671)

* fix(bin): require a self-sufficient no-mistakes intent

A no-mistakes worker's --intent is only as useful as the string it
passes. PR kunchenguid#3604 shipped with an intent that was only "do 1, 2, 3, 7
from the report": the real contract lived in a private scout report and
never reached --intent, so nobody holding that string plus the codebase
could have derived the specification.

This is pure instruction at the contract's one owner; no spawn-side or
promotion-side check is added.

- bin/fm-dod-lib.sh: the generated no-mistakes Definition of done now
  states that the --intent string must be self-sufficient (the string
  plus the codebase reconstructs roughly the same specification) and
  tells the worker to write the substance of any report, decision, or
  PR the captain's intent refers to into --intent rather than the
  pointer, while Firstmate build instructions and the worker's own
  decisions still stay out. The spawn-time overlay points back at that
  rule so its "supersedes" wording cannot cancel it, and the header's
  owner statement carries the rule.
- AGENTS.md section 11 and bin/fm-brief.sh's header ask Firstmate to
  include the substance of referenced material when filling
  ## Captain's intent, and section 11 points at the owner of the rule.
- tests/fm-brief.test.sh and tests/fm-task-delivery.test.sh assert the
  rendered brief and launch contract carry the rule.

Claude-Session: https://claude.ai/code/session_01YMhEe42q7BAAoN6RxNuzim

* no-mistakes(document): Replace incident-specific intent test commentary

* fix: accelerate local Bearings snapshot composition (kunchenguid#3499)

* Speed local fleet snapshot composition

* no-mistakes(review): Stabilize task inventory during concurrent snapshot composition

* no-mistakes(document): Document local snapshot observation concurrency

* no-mistakes(ci): Fixed CI failures by making empty task manifests compatible with stock macOS Bash 3.2, snapshotting task metadata before concurrent observations to prevent generation drift, strengthening the behavioral race regression, and updating the stock-Bash Bearings test count to 45. Verified fleet snapshot tests (15), Bearings tests (45), workflow lint tests, project lint, Bash 3.2 parsing, and diff checks

* no-mistakes(ci): Fixed the Linux CI failure caused by passing large backlog/task JSON through jq command-line arguments, which exceeded the per-argument size limit. Both inventory projections now stream large JSON inputs through stdin. Verified with fm-bearings-snapshot.test.sh (45 tests), fm-fleet-snapshot-view.test.sh (15 tests), Bash syntax, and git diff checks

* no-mistakes(ci): Fixed concurrent task teardown during metadata capture: vanished metadata is now omitted while genuine copy failures remain fatal. Added a deterministic public Bearings regression test and updated CI’s expected test count. Verified with the full Bearings suite, workflow-lint suite, Bash syntax checks, and git diff checks

* no-mistakes(ci): Fixed PR-caused CI and review issues: streamed large fleet JSON through jq stdin to avoid Linux argument limits, kept crew-state reads bound to captured metadata generations, and strengthened the behavioral race test. Bearings (46 tests), fleet snapshot (15 tests), crew-state, backend, lint, Bash syntax, and diff checks pass locally. Serial shard 5’s unrelated task-inbox segmentation fault appears infrastructural/flaky

* no-mistakes(ci): Fixed endpoint-state generation crossing by validating captured spawn_gen before and after local endpoint probes, falling back to exact metadata identity for legacy tasks. Stale probe results now become unknown instead of false unhealthy state. Added a behavioral relaunch-race regression test. Verified the full Bearings snapshot suite, shellcheck, bash syntax, and git diff checks

* fix(snapshot): keep live observations generation-coherent

* no-mistakes(review): Keep secondmate observations generation-bound without copying reports

* no-mistakes(document): Document generation-coherent snapshot observations

* test(bearings): measure local read overlap instead of wall-clock budget

The large-local-snapshot regression asserted that a whole snapshot
composed in under five seconds. That bound measures how loaded the host
is, not whether the per-task reads actually overlap, so it failed
intermittently on a contended machine: one run in six on a box at load
16-20, landing exactly on the five second boundary.

Time a serialized run and a concurrent run of the same workload instead
and require the concurrent one to save at least two seconds. Both runs
pay the same composition overhead, so the difference isolates the
overlap this change delivers. Five one-second reads serialize into five
seconds and overlap into about one, and re-serializing the reads
collapses the saving to roughly zero, so the assertion still fails
loudly if the concurrency regresses.

Also bump the pinned Bearings test count to 48, since rebasing onto the
current default branch picked up its captain-hold test.

* no-mistakes(review): Restore JSON-derived decision flags

* no-mistakes(review): Unify status-derived snapshot observations

* no-mistakes(ci): Updated the stock macOS Bash CI check’s Bearings test count from 48 to 49. Verified the full Bearings suite passes and emits exactly 49 TAP successes; git diff checks pass

* fix: prevent stale supervision wake loops (kunchenguid#3672)

* fix(bin): stop the supervision branch's stale-ack and ghost-report loops

Clean-slate implementation of the four authorized recommendations from the
supervision-ghost-retrigger analysis (items 1, 2, 3, and 7), in their minimal
form, superseding PR kunchenguid#3604:

- fm_branch_report refuses a task the wake being handled never named. The
  extension fixes the reportable task set from the eligible rows before each
  prompt (signal and stale rows resolve to their tasks, a heartbeat allows any
  task with a live record, fleet is always allowed), so a report typed from
  memory about a task whose records teardown already removed is never stored
  or delivered.
- An acknowledgement that consumes nothing says "nothing was acknowledged
  through N" and prints the exact --ack-through / --recovery-generation
  command for the current presented wake, instead of "re-run the drain",
  which re-fed the same stale acknowledgement in a loop.
- bin/fm-guard.sh no longer tells the branch actor to drain queued wakes
  while it is handling them; it names the granted rows instead.
- Teardown removes state/.<task>.branch-outcome-index for ordinary tasks and
  descendants; the index rebuild and the append-side index write both skip a
  task with neither a live record nor a status log, so the branch's report of
  a teardown it just performed is stored without recreating the index.

No new locking, no spawn-generation binding, and no retired-task refusal: the
branch can still report the outcome of a task it just tore down, and the
teardown test now proves that path end to end.

* fix(bin): narrow the branch report scope and guard silence to the minimal form

Apply the four review decisions on the clean-slate branch:

- A signal or stale prompt may report only the tasks its own rows resolve
  to; fleet is refused there too. A heartbeat review is not scoped by task
  at all, so the extension no longer tracks live task records and refuses
  nothing by task id during a fleet review.
- The outcome-index rebuild no longer skips retired tasks; the append-side
  skip alone keeps a torn-down task's index from being recreated.
- bin/fm-guard.sh keeps the queued-wakes warning silent for the branch actor
  instead of printing a replacement note.

* no-mistakes(document): Align supervision docs with scoped wake handling

* fix(bin): avoid fleet snapshot argument limits (kunchenguid#3677)

* Fix fleet snapshot large JSON transport

* no-mistakes(review): Captain: file-back fleet snapshot transport safely

* no-mistakes(review): Captain: file-back parent summary aggregation

* no-mistakes(ci): Rebased the PR's three commits onto f4d7875 and resolved the fleet snapshot conflict while preserving the base's task-observation lifecycle. Fixed Greptile's valid finding by recursively removing the private mktemp transport directory, so future transport files cannot cause cleanup to fail. Verified with tests/fm-home-summary-refresh.test.sh, bin/fm-lint.sh, git diff --check, and ancestry checks. All passed; the fix remains as an uncommitted worktree change for the outer executor

* fix(bin): attribute active runs with unfetched pipeline heads (kunchenguid#3681)

* fix(bin): recognize active pipeline fix rounds with unfetched run heads

A no-mistakes fix round advances the run head beyond the submitted head,
and the pipeline commits in its own checkout, so the task copy never
receives the new commit object. fm-crew-state's strict head rule rejected
the active row, the coarse runs-list scan skipped it and matched the
older failed row at the submitted head, and an active validation read as
failed (observed on model-routing-benchmark-hardening: active head
ac61c64 vs task copy at fb47636d).

fm_nm_runs_status_for_worktree in bin/fm-nm-run-lib.sh now owns
runs-ledger attribution: the branch's newest row alone decides, and a
newest row whose head cannot resolve locally is recognized only as a
provable pipeline-owned continuation - active (running) and anchored by
the immediately older row for the same branch having ended at exactly
this worktree's HEAD. The reader keeps the axi TOON as full detail for
that proven same-branch run. Unanchored, ancestor-anchored, and terminal
unresolvable rows stay unattributed, so branch-name coincidence and other
tasks' runs never match, and fm_nm_head_matches_worktree keeps its exact
prior semantics for teardown (verified by the full teardown suite).

Tests: reproduction regression for the unfetched active fix head (reads
working via full run-step detail), coarse-path continuation when axi
answers another branch, and negative controls for the unanchored active
row and the unresolvable terminal row with the historical fallback
preserved.

Ported onto upstream/main f4d7875, where kunchenguid#3194 independently added the
branch_sync custody exemption on the full axi-status path: both mechanisms
now coexist, each owning one surface (TOON custody on the full path, the
runs ledger on the coarse path). The port deletes the superseded coarse
scan-and-skip (nm_runs_status_for_branch) and its now caller-less helpers
(fm_nm_head_resolvable, nm_coarse_head_matches_worktree), renames the
exemption comment's "the one exemption" phrasing now that a second
complementary exemption exists, and points the stale
FM_CREW_STATE_RUNS_LIMIT comment at fm_nm_runs_status_for_worktree
(judge follow-up #1). The parent coarse-guard test's fixture is the
ledger-anchored continuation shape, so its expectation flips to the fixed
behavior (working via run-step, never the older failed row); a new
mismatched-anchor coarse negative control preserves that guard's original
no-anchor protection (pane answers, never the older row).

* no-mistakes(document): Clarify pipeline attribution documentation

* fix(bin): pre-register claude workspace trust at spawn time (kunchenguid#3663)

* fix(bin): pre-register claude workspace trust for task worktrees

A claude crewmate launched into a fresh task worktree met Claude Code's
interactive workspace-trust dialog before it ever read its brief, and firstmate
could not answer it: the key plane carries only Enter, Escape, and C-c with no
arrow navigation, and the dialog's selection starts on "No, exit", so the
documented Enter recipe ended the session instead of accepting it. Two workers
wedged this way and were unblocked only by hand-seeding the trust store per
path.

--dangerously-skip-permissions does not cover that gate. `claude --help`
records the dialog as skipped only in non-interactive mode, through -p or a
non-TTY stdout, and a crewmate pane is interactive, so there is no launch flag
to reach for.

fm-spawn now pre-registers the worktree through bin/fm-claude-trust.sh in the
existing claude branch, before the project settings that the same gate would
otherwise block, and refuses the spawn when that write fails rather than
launching a worker that would wedge.

The scope test is the safety property and is structural rather than a path
policy: the path must be a linked git worktree, sharing the spawning project's
common dir, whose top level is exactly the resolved argument. Git is the ground
truth, so the argument is never trusted on its own word, and a primary
checkout, an unrelated repo, a worktree subdirectory, a plain directory, and a
home directory are each refused rather than warned about or skipped. A
treehouse or orca path prefix was deliberately avoided because treehouse's root
is configurable, which would make a prefix both wrong and a new policy surface.
One structural test covers both worktree providers.

tests/fm-claude-trust.test.sh pins both halves, including a case where HOME is
itself a valid linked worktree so the home guard is proven load-bearing rather
than passing vacuously, plus the spawn-level proof that a claude spawn trusts
its worktree and launches with the brief pointed at the same store.

The adapter reference no longer tells a firstmate to press Enter on that
dialog, and the shared trust reference now names every harness surface: which
harnesses gate, which suppress at launch, which dodge the gate, which now
pre-registers, and that a claude secondmate is excluded by design.

The spawn fixture runs each spawn against a throwaway HOME so the suite cannot
write the developer's real store, isolating through HOME rather than
CLAUDE_CONFIG_DIR because the spawn forwards a set CLAUDE_CONFIG_DIR onto the
launch command that launch-shape assertions read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HNEN2GLnew27HFyfi4ms4v

* fix(bin): create the staged trust store exclusively

The staged store was written to a predictable pid-based path with a plain
write, which follows a symlink. Where the Claude config directory is writable
by another local account, that account could pre-create the path as a symlink
and redirect the write into another file the launching user owns.

The staged name now carries random bytes and is created with an exclusive
"wx" open, so an existing path is refused outright instead of followed. The
happy-path test also asserts no staged store survives the rename.

The durability comment now states the residual window plainly: the readback
proves the entry landed, not that it survives, because a vendor session that
rewrites the whole store afterwards can still drop it and no lock closes that
window when the writer is Claude itself. The worker then meets the dialog and
stalls, which reaches firstmate as the ordinary stale wake rather than as
silent success.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HNEN2GLnew27HFyfi4ms4v

* no-mistakes(review): neutralise CDPATH in claude trust scope guard

* no-mistakes(review): sandbox HOME in spawn tests, drop out-of-scope artifacts

* no-mistakes(review): refuse unresolvable git dir, compact store, fix secondmate doc

* no-mistakes(review): clear git env overrides, resolve symlinked store target

* no-mistakes(review): degrade without node, fix Pi gate claim, record trust proof

* no-mistakes(review): refuse without node, pin CLAUDE_CONFIG_DIR in spawn tests

* no-mistakes(review): refuse relative config dir and concurrent store modification

* no-mistakes(review): correct orca worktree claim, clean staged store on failure

* no-mistakes(review): restore pretty-printed store, correct trust dialog docs

* no-mistakes(review): arm trust gate before busy state to avoid orphans

* no-mistakes(document): record claude trust pre-registration in its owner docs

* no-mistakes(document): note orca limit for claude trust pre-registration

* no-mistakes(ci): Fixed the Greptile P1 on bin/fm-spawn.sh by moving the Claude trust gate earlier rather than adding cleanup machinery. Diagnosis: Greptile reported that when Claude trust registration fails on tmux/Zellij/cmux/non-projected Herdr, the exit runs after the backend endpoint and /tmp/fm-<id> were created, and the abort trap cleans neither. The endpoint half is pre-existing, deliberate architecture — the two refusals immediately above the gate (the 60s `treehouse get` timeout at fm-spawn.sh:2550 and `validate_spawn_worktree` at :2487) also exit with the endpoint live and direct the operator with "inspect window $T"; spawn_abort_cleanup only reclaims orca endpoints (already covered via ORCA_ABORT_CLEANUP) and herdr projections. The temp-root half was genuinely introduced by this PR: the gate was placed beside the busy-state arm, ~30 lines after `mkdir -p "$TASK_TMP/gotmp"`, and fm-teardown can only find that root through `tasktmp=` in a meta record a refused spawn never publishes. Root-cause fix (smallest correct change, no new subsystem): - bin/fm-spawn.sh — moved the `claude*` trust gate from inside the busy-arm block up to the first point $WT is known, immediately after the `freshen_spawn_worktree_base` block and before TASK_TMP creation, the STATE setup, and the relaunch `clear_relaunch_harness_wiring` retirement. A refusal now leaves no temp root, no retired relaunch wiring, and no busy record; only the endpoint remains, in the same class as the two refusals just above it. - bin/fm-spawn.sh — the refusal message now ends with "inspect window $T", matching the existing convention so control/teardown can identify the endpoint. $T is set for every backend on the non-secondmate path. - bin/fm-spawn.sh:196 — header note corrected from "before any state is armed" to "before any per-task state exists". - tests/fm-claude-trust.test.sh — the existing refused-spawn test's own comment claimed "before any task state exists" but only asserted busy state. Renamed to test_refused_spawn_leaves_no_task_state and added an assertion that /tmp/fm-<id> is absent, with the task id suffixed by the test process pid so the assertion reads only this run's path (a stale /tmp/fm-refusedspawn from the fixed-id version was in fact present on this box). No assertions on implementation source bytes. Verification run locally: - The new assertion fails against the pre-fix bin/fm-spawn.sh ("not ok - a refused spawn stranded a temp root no teardown can find") and passes after — a real before/after regression proof. - tests/fm-claude-trust.test.sh: 20/20 ok. - tests/fm-backend.test.sh, fm-backend-orca, fm-control-relaunch, fm-spawn-dispatch-profile, fm-trace-context-spawn, fm-gotmp: all pass. - tests/fm-backlog-atomicity.test.sh: rc=0, 79 assertions ok. - bin/fm-lint.sh (repo's single lint owner, pinned ShellCheck 0.11.0 + actionlint 1.7.12): clean. - No /tmp/fm-refusedspawn* leftovers after the runs. Scope respected: no trust subsystem, no policy layer, no config surface, no endpoint-cleanup mechanism added; the change is an ordering move plus one error-message clause and the test that pins it. Adapter references and docs made no ordering claim, so none needed updating. Changes are left uncommitted in the worktree for the outer executor

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* fix: restart every live second mate after updates (kunchenguid#3690)

* feat(update): restart every live second mate after a successful update

/updatefirstmate only restarted a second mate when that pass advanced its
AGENTS.md or .agents/skills. An already-current home was skipped entirely, a
bin/-only advance was steered instead, and a remote host that could not report
its instruction diff was downgraded to a re-read. A running agent also freezes
its launch-time wiring - turn-end hooks, harness flags, per-harness feature
switches - and none of that is derivable from a file diff, so an unchanged
tracked surface is not evidence the agent is already on the current behavior.

Restart is now unconditional on a successful update of that home. Every live
second mate the pass leaves on the target commit is restarted, whether it
advanced or was already there.

The safety contract is unchanged: open records are persisted before the agent is
replaced, nothing is forced, stashed, or discarded, a home the pass had to skip
is not restarted at all, and a mate whose runtime cannot prove a restart keeps
the honest re-read path and is never reported as reloaded.

bin/fm-ff-lib.sh gains a settled-state hook that fires for a home left at the
base whether it advanced or was already there, and never for a skipped one; the
instruction-gated hook the session-start convergence sweep uses is untouched.

Regressions: fm-update pins the already-current mate into the restart set and
the unprovable one into the nudge set, and fm-secondmate-restart drives both
real commands end to end - an already-current home is named, persisted, and
genuinely replaced with its checkout untouched, while the unprovable one keeps
its running agent.

* no-mistakes(document): Document unconditional secondmate restarts

* fix(bin): close pending-reply decisions via resolve-key (kunchenguid#3696)

* fix(bin): close reserved pending-reply keys via fm-send --resolve-key

fm-send wrote answered: notes that the reserved-key fold ignores, so
operator closes exited 0 while OPEN DECISIONS kept the decision open.
Speak the owning library's close vocabulary on that path, and refuse
when a reserved close cannot take effect.

* no-mistakes(review): Safely quote manual decision-close recovery commands

* no-mistakes(review): Reject unclosable overlong decision keys before sending

* no-mistakes(review): Remove contract suffix from open decisions hint

* no-mistakes(document): Document resolve-key line-cap refusal

* fix(bin): prevent false missed-reply escalations (kunchenguid#3697)

* fix(bin): stop false missed-reply escalations for same-basename self-home answers

A healthy secondmate that wrote corr= to its own state/<id>.status never matched the parent channel, so recovery confirmed and the record escalated as pending-reply-missed. Make the report helper resolve the parent channel itself, skip parent-replies.status as wrong-home, put a readable sighting path on the missed line, and restatement-copy only that same-basename self-home file onto the parent channel.

* no-mistakes(review): Resolve late replies before recovery escalation

* no-mistakes(review): Tighten reply routing and regression coverage

* no-mistakes(review): Preserve reply paths and require explicit home

* no-mistakes(review): Encode wrong-home paths before persistence

* no-mistakes(document): Document corrected secondmate reply routing

* no-mistakes(lint): Fix pending-reply ShellCheck warnings

* feat: add verified Gemini crewmate runtime (kunchenguid#3695)

* feat(harness): verify gemini as a crewmate runtime adapter

Adds Gemini CLI as a fourth dispatch target alongside claude, codex, and
grok, scoped to crewmate and scout work only. Every axis was proven against
gemini-cli 0.58.0 rather than inferred; docs/verification/runtime-backends.md
carries the dated evidence and names what stayed unverified.

Busy state is semantic, not rendered: BeforeAgent opens a turn and AfterAgent
and SessionEnd close it. AfterAgent also fires on a manual interrupt, so a
cancelled turn closes its own record.

Three findings shaped the wiring rather than a config line:

- --skip-trust and GEMINI_CLI_TRUST_WORKSPACE=true are presented by the CLI
  as equivalents and are not. A controlled A/B showed --skip-trust leaves
  project configuration unloaded, so workspace skills never load.
- The worktree's .gemini/settings.json is the PROJECT's committed settings
  file, unlike claude's settings.local.json. Firstmate's hooks therefore go
  to a firstmate-owned state/<id>.gemini-settings.json reached through
  GEMINI_CLI_SYSTEM_SETTINGS_PATH, which also works untrusted and merges
  with a project's own hooks instead of replacing them.
- The shipped CLI is a node bundle whose live process reports comm as
  MainThread, so ancestry cannot see it. GEMINI_CLI=1 is load-bearing and is
  tested before an inherited CLAUDECODE, and pane liveness identifies gemini
  from the script argument through the new bin/fm-gemini-lib.sh.

Gemini is refused for secondmates: it has no primary supervision protocol.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYdQkKfSEQrFUxtcTXZ66L

* test: clear gemini's marker in launch and detection expectations

Every non-gemini launch now clears GEMINI_CLI the way it already clears
cursor's markers, so the two tests that pin the exact launch prefix are
updated to match. The harness-detection tests that scrub foreign markers
before probing ancestry scrub GEMINI_CLI too, so running the suite from
inside a gemini session cannot produce a false verdict.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYdQkKfSEQrFUxtcTXZ66L

* docs: classify the gemini harness reference

The documentation inventory is the single classification owner for maintained
prose surfaces, and every surface must appear in it exactly once. The new
harness reference is agent-runtime, matching its siblings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYdQkKfSEQrFUxtcTXZ66L

* no-mistakes(review): Narrow Gemini ancestry detection

* no-mistakes(review): Restrict Gemini hooks to canonical launches

* no-mistakes(document): Document Gemini adapter support boundaries

* no-mistakes(ci): Fixed Gemini process identity when interpreter or script paths contain whitespace. Tmux liveness now uses NUL-delimited /proc argv on Linux, with the existing flattened ps fallback elsewhere. Added a real-process regression test. Verified with the Gemini harness test suite, full fm-lint, ShellCheck, and git diff --check. The CI and Require no-mistakes runs were action_required/attestation outcomes rather than code failures

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(teardown): conclude parked runs advanced past task copy (kunchenguid#3704)

* conclude parked runs the pipeline advanced past the task copy

A no-mistakes fix round commits in the daemon's own gate-repo clone, so a
run parked at a gate can carry a head whose object the task copy never
received. Teardown's strict object-local identity rule then declined to
conclude the run, and cleanup left it parked forever holding a fleet slot
(observed 2026-09-03; the same masking condition PR 3681 fixed on the
read path, now closing the teardown half its scope boundary deferred).

task_status_is_own_parked_run now falls back - only when the reported
head resolves to no local object - to the one shared runs-ledger
attribution rule fm_nm_runs_status_for_worktree (bin/fm-nm-run-lib.sh),
whose anchored continuation proof binds the branch's newest active row
to this worktree's exact submitted head. Foreign branches, stale
history, terminal rows, ancestor-only anchors, diverged newer rows, and
ambiguous multi-row shapes all still refuse, and runs that are actively
running, fixing, or in CI remain untouched: only the parked-at-a-gate
determination ever reaches the abort. No sqlite access, no fetches into
another task copy, no custody changes, no duplicated matching logic.

* tighten the parked-run ledger fallback and pin both judge corrections

The teardown ledger fallback now authorizes concluding this task's parked
run only when the shared runs-ledger rule's proved answer is the explicitly
active word (running): a terminal newest row - even anchored at exactly the
worktree's head - is finished history and never an abort authorization.
The read path may classify the same owner's answer; teardown's abort must
never fire for a run that already ended.

Two bounded pre-validation corrections from the implementation review:
- a fetched-object counterfactual pins the strict-rule path: a pipeline fix
  head fetched into the task copy aborts through object-local identity
  alone, with an empty ledger and a proof the runs query never fired;
- a negative fixture pins the tightened boundary: an unresolvable reported
  head with a terminal newest same-branch row anchored at the worktree head
  engages the ledger fallback and still refuses, so the refusal is the
  terminal-word boundary and not an earlier guard.

* no-mistakes(review): Bind teardown ledger fallback to validated run heads

* no-mistakes(review): Restore validated advanced-head ledger continuation

* no-mistakes(review): Reject invalid ledger dates and terminal statuses

* no-mistakes(document): Document teardown ledger scan limit

* feat(bin): show requested vs effective model in Herdr agent view

Track spawn-config requested_model separately from runtime-verified
effective_model, probe Claude/Pi transcripts for exact API ids, push
compact display metadata to Herdr, and preserve verified models across
relaunch/compaction hooks without inferring aliases as truth.

* fix(bin): keep re-probing effective model after first exact reading

fm-model-sync.sh only probed for the runtime-verified effective model
while it was still pending/UNKNOWN, so a session that later switched
models (manual switch, provider fallback) kept displaying the first
verified model forever and never appended a fallback-history entry.
Probe unconditionally instead; fm_model_record_effective already
no-ops when the probed value is unchanged, so this stays cheap.

Addresses the Greptile P1 finding on PR kunchenguid#3705's fm-model-sync.sh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwfFjeYQcz9cZ3vEZohmpm

* fix(bin): distinguish Cursor Grok, direct xAI Grok, and Anthropic Claude in the display

Kapitänskorrektur: harness alone conflated Cursor-hosted Grok models
(cursor-grok-4.6-*) and direct xAI Grok models (xai/grok-4.6) under
one generic label, and displayed Anthropic Claude without naming the
provider. Add fm_model_source_label, pattern-matched on the verified
exact model id, so the compact display always reads Cursor · Grok,
xAI · Grok, or Anthropic · Claude with the exact model id appended.
Falls back to the existing harness label for every other model. No
routing change: this only affects display strings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwfFjeYQcz9cZ3vEZohmpm

* fix(bin): wire model-sync into the Pi extension's turn lifecycle

fm-model-sync.sh was only invoked from Claude's SessionStart/
UserPromptSubmit/Stop hooks; the Pi harness's own extension
(state/<id>.pi-ext.ts) never called it, so a Pi-hosted session (e.g.
a pi/xai-grok crewmate) never refreshed its effective model after the
first probe and Herdr kept showing the stale value with no
fallback-history entry. Call fm-model-sync.sh from the same
agent_start/turn_end boundaries Pi already uses for busy-state and the
turn-end notification touch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwfFjeYQcz9cZ3vEZohmpm

* fix(bin): serialize fm-model-sync.sh's meta read-probe-write

Overlapping lifecycle events (Pi's agent_start/turn_end, Claude's
SessionStart/UserPromptSubmit/Stop) can invoke fm-model-sync.sh
concurrently for the same task. The unlocked read-probe-write let
interleaved runs revert a newer effective model, mismatch its
source, or duplicate a model-history entry. Serialize the critical
section through the same per-task meta lock fm-spawn.sh already uses
(fm_meta_lock_path + fm_lock_acquire_wait/fm_lock_release), released
before every exit path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwfFjeYQcz9cZ3vEZohmpm

---------

Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Arthur Haro <38157909+haroarthur@users.noreply.github.com>
Co-authored-by: Nicolas Payette <nicolas.payette@specira.ai>
Co-authored-by: Jon Roosevelt <rooseveltadvisors@gmail.com>
Co-authored-by: att430 <41454889+att430@users.noreply.github.com>
Co-authored-by: Valentino-Sole <171032438+Valentino-Sole@users.noreply.github.com>
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.

2 participants