Skip to content

feat: integrate upstream supervision and fleet lifecycle updates - #26

Closed
BohnBawerick wants to merge 162 commits into
mainfrom
fm/fm-upstream-reconcile
Closed

BohnBawerick wants to merge 162 commits into
mainfrom
fm/fm-upstream-reconcile

Conversation

@BohnBawerick

@BohnBawerick BohnBawerick commented Sep 14, 2026 •

Copy link
Copy Markdown
Owner

Intent

Task

Load the firstmate-coding-guidelines skill before editing any code or docs.

Reconcile this firstmate fork with upstream (kunchenguid/firstmate): merge upstream/main into our branch, resolve all merge conflicts, verify the test suite, and open the PR through no-mistakes.

Background and Context

  • Remote upstream is https://github.com/kunchenguid/firstmate.git.
  • Remote origin is https://github.com/BohnBawerick/firstmate.git.
  • Our fork (origin/main) and upstream (upstream/main) diverged from merge base f66be0f8f7f56f78d09458d909bdd555b9dbcf76.
  • Upstream has 145 new commits bringing fixes and new features.
  • Our fork has 152 commits containing our custom additions.

What to keep from our fork (Protected Areas)

  1. Curated memory architecture: bin/fm-memory-compile.sh, bin/fm-memory-migrate.sh, the atomic notes layout under data/memory/notes/, and docs/curated-memory.md.
  2. Quality gate and hardened posture: bin/fm-quality.sh, bin/fm-quality-*.sh, CRAP scoring, and quality test suites.
  3. Local landing workflow: bin/fm-merge-local.sh, and local default-branch authority conventions.
  4. Antigravity (agy) adapter support: Ensure agy integration and Gemini model support remain intact.
  5. Herdr and Pi watch continuous supervision enhancements: Session-lock gate on fleet-mutation entry points, ternary run-head identity, and proven watcher continuity logic.

What to adopt from upstream

  1. Quiet supervision mode for present captain: feat(afk) improvements and return handling.
  2. Guarded foreground viewer for live validation: feat(herdr).
  3. Claude permission mode configuration: config/claude-permission-mode launch options.
  4. Live-head merge gates and away task grants.
  5. Upstream bug fixes and hardening: Process-level agent liveness verification, teardown slot collision fixes, watcher hardening, procevent trust re-binding, etc.

Decision protocol (Captain's requirement)

  • If a conflict represents a genuine design divergence, architectural conflict, or user-facing behavioral trade-off between upstream and our fork, DO NOT guess or silently drop functionality.
  • Create an interactive Lavish review artifact (using lavish-axi) or structured report providing the full usability context: what each version does, user impact, trade-offs, and clear numbered options with a recommendation.
  • Append needs-decision [key=<slug>]: <summary> to your status log and stop. Firstmate will present the options to the captain and steer you with his ruling.

Verification

  • Run bin/fm-lint.sh to ensure shellcheck and actionlint pass.
  • Run bin/fm-test-run.sh on affected test shards to verify test coverage passes.
  • Adhere strictly to repo style rules: one sentence per line in markdown, plain hyphens only (never em dashes), no agent co-author in commits.

Herdr isolation - HARD SAFETY CONTRACT

This brief was explicitly scaffolded with --herdr-lab because the task will drive Herdr lifecycle behavior.
On Herdr 0.7.3 the API socket is not relocatable by HERDR_CONFIG_PATH, XDG_CONFIG_HOME, or HOME.
A named non-default session plus a trailing --session <name> on every call is the only viable local isolation.

  1. Set HERDR_LAB_HELPER='~/labs/axi-sandbox/firstmate/bin/fm-herdr-lab.sh' and generate the session name with HERDR_LAB_SESSION=$("$HERDR_LAB_HELPER" name fm-upstream-reconcile).
    Install trap '"$HERDR_LAB_HELPER" teardown "$HERDR_LAB_SESSION"' EXIT before provisioning, then provision only with "$HERDR_LAB_HELPER" provision "$HERDR_LAB_SESSION".
  2. Run every task-specific non-lifecycle Herdr command through "$HERDR_LAB_HELPER" run "$HERDR_LAB_SESSION" <arguments...>.
    The helper appends the required trailing --session "$HERDR_LAB_SESSION"; HERDR_SESSION alone is never accepted as isolation.
  3. Teardown only through "$HERDR_LAB_HELPER" teardown "$HERDR_LAB_SESSION".
    It re-checks refuse-default immediately before stop and again immediately before delete, and fails closed on ambiguity.
  4. If an experiment requires a deliberate mid-run session stop, use only "$HERDR_LAB_HELPER" stop "$HERDR_LAB_SESSION"; it performs the same immediate refuse-default check.
  5. Forbidden commands: direct herdr server stop, every other server-global operation such as herdr server live-handoff or reload/update operations, direct herdr session stop, direct herdr session delete, and any Herdr call scoped only by ambient or inline HERDR_SESSION.
  6. The helper records the live default session before provisioning and verifies the identical fleet state after teardown.
    A missing, stopped, or changed default session is a hard tripwire failure, never a cleanup warning to ignore.

Never bypass the helper, even for a read-only lifecycle probe or cleanup after failure.
The captain fleet uses the running default session.

Accepted Firstmate steering and captain rulings

These requirements supplement the task above and preserve the provenance of accepted decisions.

  1. Adopt upstream continuous native Pi supervision through AFK and return, retire the separate Pi away daemon, and preserve session locks and watcher safety.
  2. Preserve fork-conservative no-mistakes run attribution for both reporting and cleanup.
    An unfetched head requires explicit submitted-head or active custody proof; never infer ownership from neighboring coarse ledger rows.
  3. Future no-mistakes intent must preserve the complete accepted specification, including Firstmate requirements and accepted steering, with clear provenance.
    Keep upstream captain-intent headings, but never attribute Firstmate-written constraints as the captain's literal words.
  4. Report unreadable consistently across harnesses when process evidence is inconclusive.
    Strictly preserve the fork's departure proof: never declare dead or attempt recovery without positive proof of exit, and protect live shell tools.
  5. Verify the affected shards and portable CI suites.
    Keep live Herdr lifecycle tests in the Herdr lane, and use the guarded lab contract above for any live Herdr validation.

The requested upstream merge must remain in the delivered branch history, with upstream/main as an ancestor.

Accepted Firstmate steering on watcher-triage-cadence: the declaration-bound .paused-resurfaced- throttle is the single owner of the established re-surface cadence.
Declaration age only feeds first-sight timing and throttle creation; it must neither bypass a fresh throttle nor delay an expired one.
A replacement declaration with a new status signature starts its own reminder window.
Preserve the upstream bounded-recheck window length and all other passing watcher behavior.

What Changed

  • Add quiet supervision, durable AFK records and return briefs, continuous native Pi supervision, OMP support, and configurable Claude permission modes.
  • Add IMAP/SMTP mail, trusted external process-event adapters, quota-based dispatch selection, and a guarded Herdr foreground viewer.
  • Reconcile fleet lifecycle handling with atomic backlog transitions, secondmate outcome delivery and restart gates, live-head merge checks, and away task grants; preserve conservative run attribution and strengthen liveness, teardown, and watcher recovery, with expanded regression coverage, CI shards, and documentation.

Risk Assessment

⚠️ Medium: The broad merge changes lifecycle, supervision, and ownership behavior, but no remaining material defect was substantiated.

Testing

Live coverage not exercised

  • Native Pi supervision continuity through AFK and return with a real captain.
  • Parent and child teardown retry after real endpoint failure.
  • Remote home update across SSH, because no disposable remote home exists.
  • No-mistakes cleanup on a disposable pipeline, because no disposable target exists.

The user accepted fixture and targeted regression evidence for these four gaps.
The outer executor must include this section in the PR description.

Reviewed prior evidence, re-drove startup uncertainty, steering, recovery refusal and guarded cleanup with a real agy worker under injected liveness-read failures, and passed the focused agy regression. Captured CLI and persisted-state evidence; four user-accepted live gaps remain.

  • Live validation: ✅ go - 4 of 8 scenarios driven live against the product
Scenario Result Live Evidence
Launch with an exhausted readiness window and preserve the real worker despite unconfirmed startup ✅ pass live Live Herdr transcript; herdr-recheck/herdr-live-spawn.log
Send steering while liveness reads fail and have the worker execute the saved message ✅ pass live Live Herdr transcript; herdr-recheck/herdr-live-inbox/handled/001.msg; herdr-recheck/herdr-live-steer-result.txt
Attempt recovery with unreadable liveness and preserve ownership and the running shell tool ✅ pass live Live Herdr transcript; herdr-recheck/herdr-live-recovery.log
Tear down the guarded lab and leave the default-session tripwire unchanged ✅ pass live herdr-recheck/herdr-default-baseline.json; herdr-recheck/herdr-default-after.json; guarded cleanup exit=0
Keep native Pi supervision continuous through AFK and return ⏸️ untested no Requires a real captain and an authorized native Pi supervision session. The user accepted this live coverage gap.
Retry parent and child teardown after real endpoint or state-retirement failure ⏸️ untested no Requires a disposable parent and child fleet with a safely induced real cleanup failure. The user accepted targeted regression evidence for this gap.
Update a remote home across SSH and preserve its registered action watch ⏸️ untested no No disposable remote home exists. Provide one with SSH access to exercise this accepted gap.
Clean up an attributed disposable pipeline while preserving ambiguous runs ⏸️ untested no No disposable pipeline target exists. Provide one and cleanup authority to exercise this accepted gap; the shared daemon remained untouched.
Evidence: Live Herdr transcript

Source: Live Herdr transcript

session=fm-lab-nm-live-liveness-117239-21382 pane=w1:p1 workspace=w1 tab=w1:t1
error: agy startup unconfirmed: agy did not start processing its brief in the pre-trusted worktree in window fm-lab-nm-live-liveness-117239-21382:w1:p1; endpoint and ownership records retained; inspect window fm-lab-nm-live-liveness-117239-21382:w1:p1
spawn exit=1
PASS: startup unconfirmed while real agent and shell tool remain live
fault-injected agent state=unreadable
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
●  WATCHER DOWN - SUPERVISION IS OFF
●  1 task(s) in flight, but no watcher has a fresh beacon (last beat: never, grace 300s).
●  Trust the emitted supervision protocol for this harness; do not use shell & for watcher repair.
●  This is a supervision warning only; the requested message WILL still be sent.
●  repair missing watcher supervision according to the session-start block for this harness; do not use shell &.
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
error: task liveness's endpoint reads 'unreadable' rather than a positively classified state; refusing to send a lifecycle command into an unattributed endpoint
error: relaunch of liveness failed while stopping the old agent and its state is 'unreadable'; the durable record and progress note were retained for recovery
PASS: uncertain recovery refused without killing the tool or changing ownership
PASS: live shell tool completed and worker acted on durable steering
guarded cleanup exit=0
Evidence: Verification results and limits

Source: Verification results and limits

{
  "verdict": "go within the accepted partial-validation scope",
  "live_command": "bash ~/.no-mistakes/evidence/01M2FSAP36PE8TM5G7KKWCZCFM/herdr-recheck/live-herdr.sh",
  "live_exit_code": 0,
  "regression_command": "TMPDIR=\"$PWD/.test-phase-herdr/regression-tmp\" bash tests/fm-agy-harness.test.sh",
  "regression_exit_code": 0,
  "observations": [
    "Production spawn returned startup unconfirmed with the readiness polling budget set to zero, retaining the endpoint and ownership records.",
    "A real agy worker ran a 90-second shell tool.",
    "Fault-injected missing registration and unavailable process-info reads produced unreadable liveness.",
    "Production fm-send persisted steering; production fm-control relaunch refused, preserving byte-identical metadata and the live shell process.",
    "The shell tool completed and the worker handled inbox/001.msg, writing 80235.",
    "Guarded teardown exited zero; the named lab session was absent afterward and the default-session snapshot matched the baseline."
  ],
  "limits": [
    "The live worker was agy, not Rovo. Only the liveness read failures were injected; the worker, backend, shell tool, steering and recovery commands were real.",
    "The helper measures default-session name, default flag, running state and socket path.",
    "Earlier targeted and live evidence remains in the parent evidence directory; this follow-up re-drove the required Herdr scenario and the changed agy regression."
  ]
}
Evidence: Accepted coverage gaps for PR description

Source: Accepted coverage gaps for PR description

The required live Herdr preservation scenario passed on the checked-out target.
The focused agy regression passed.
See result.json and transcript.log for fresh evidence and fault-injection limits.


- Outcome: 🔧 2 issues found → auto-fixed ✅ across 2 runs (46m33s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 5 issues found → auto-fixed (10) ✅
  • 🚨 bin/fm-remote-herdr-guard.sh:96 - The accepted requirement says "never declare dead or attempt recovery without positive proof of exit, and protect live shell tools". Here, missing lsof or inconclusive process evidence sets BIRTH=unknown, then the new guard executes server stop, closing a running session's panes. The RunAtLoad launch agent can trigger this unattended. This deliberate upstream takeover policy conflicts with the fork's protection. Obtain a ruling; preserving the accepted requirement means leaving unknown-origin sessions running and reporting the uncertainty.
  • 🚨 bin/fm-spawn.sh:4543 - After a fresh worker receives its launch command, two failed backlog commit attempts reach this rollback, which deletes its metadata and busy records without stopping its tmux or Herdr endpoint. The agent can continue working while fleet enumeration, steering, and ordinary teardown lose its ownership record. Preserve the published records once launch delivery begins, including through EXIT cleanup, and report the backlog failure. Existing bootstrap reconciliation can recover the missing backlog transition.
  • 🚨 bin/fm-pr-merge.sh:541 - Grouping checks solely by name lets unrelated checks suppress failures. If two workflows publish test on the same head, a failure started at 10:00 is discarded when the other workflow's successful check starts at 10:01. Preflight then reports green and can merge an unprotected repository despite the unwaived failure. Establish matching workflow/app identity before treating checks as reruns, and retain failures when that identity is unavailable.
  • 🚨 .agents/skills/quiet/SKILL.md:31 - The intent requires "Quiet supervision mode for present captain" while also requiring retirement of the separate Pi away daemon. This new instruction prescribes FM_AFK_MODE=quiet with start or start-native, but both commands now reject pi/pi-signed before writing the quiet flag. Following the updated AFK confirmation path only creates an away contract, so ordinary chat triggers return instead of preserving quiet mode. Reconcile native quiet entry and its mode representation while preserving Pi daemon retirement; the representation choice needs authorization.
  • ⚠️ bin/fm-mail.py:319 - Retry candidates ignore the current IMAP UNSEEN result. After a failed header fetch produces a degraded wake, the captain can read that message elsewhere. The next poll still fetches its retry UID and emits another wake when the header recovers, despite the documented rule that IMAP Seen messages never wake a poll. Filter retry eligibility against the current unseen set while preserving bounded retry progress.

🔧 Fix applied.
5 issues (2 errors, 3 warnings) still open:

  • 🚨 bin/fm-backlog-transition-lib.sh:1189 - Teardown writes a backlog-close marker before confirming its no-mistakes run stopped. If that confirmation fails, the endpoint remains running, but the next bootstrap replay deletes its matching metadata and closes the backlog row without verifying cleanup. The worker loses its supervision and teardown identity. Preserve matching metadata and the pending marker until lifecycle cleanup is confirmed.
  • 🚨 bin/fm-secondmate-restart.sh:338 - Any correlated reply releases the restart gate, including 'blocked: backlog is unwritable; work is not persisted'. The reply helper checks correlation, not successful persistence, so both restart paths can discard unsaved conversation state. Require positive persistence completion at the shared restart boundary; blocked or incomplete replies must leave the agent running.
  • ⚠️ bin/fm-bootstrap.sh:1125 - A dispatch configuration containing {"default":{"harness":"gemini"}} is reported as invalid with 'unverified harness: gemini', although spawn now supports the canonical Gemini crewmate/scout adapter. Include Gemini in this validator so startup does not direct operators to repair a valid profile.
  • ⚠️ tests/fm-harness-adapter-references.test.sh:26 - The unreadable-reference assertion runs inside a piped while subshell. Its failure exits only that subshell; the outer script ignores the pipeline status, prints pass, and exits successfully. Deleting a referenced resource therefore passes this check. Propagate the pipeline failure or execute the loop in the parent shell.
  • ⚠️ bin/fm-procevent.sh:1290 - The suppression marker is written before the warning reaches the wake queue. Interrupting reconcile while it waits for the queue lock leaves the marker without a warning, and subsequent reconciles suppress the same broken-source episode indefinitely. Publish the wake before committing its suppression marker so interruption cannot permanently silence the failure.

🔧 Fix applied.
6 issues (1 error, 5 warnings) still open:

  • 🚨 bin/fm-test-run.sh:592 - The criterion says “Keep live Herdr lifecycle tests in the Herdr lane”, but this test is assigned to live-harness-optin, which portable-serial includes. The test defaults on when its dependencies exist and starts and tears down a Herdr session. Move it to real-herdr-gated to satisfy the required isolation boundary.
  • ⚠️ bin/fm-pr-merge.sh:1077 - The wrapper still documents and accepts --method=merge and --method merge, but now forwards them unchanged to gh pr merge. That command accepts --merge, --squash, or --rebase, so these supported invocations fail. Normalize the method arguments before invoking gh; the current mocked test incorrectly expects the unsupported argument to succeed.
  • ⚠️ bin/fm-procevent-when.sh:535 - Rebinding publishes the spec and trust files through separate renames under a lock, but cmd_run's initial spec_load does not acquire that lock. A runner starting during rebind can read mismatched generations, emit rejected, and permanently retire a valid watch. Protect the initial read with the same source lock already used for the pre-action reload.
  • ⚠️ bin/fm-captain-hold.sh:1083 - Release a held task with decision D, hold it again, then release it with the same D. The digest match misclassifies the second answer as an interrupted retry and publishes resolution occurrence 1 again, leaving the parent's occurrence 2 unresolved. Bind each persisted resolution to its hold occurrence. The durable-state change required by this remedy, rather than the defect itself, needs authorization.
  • ⚠️ bin/fm-remote-doctor.sh:287 - The launch command now contains the checkout path but is interpolated into XML without escaping. A checkout such as ~/Work/R&D/firstmate produces an invalid plist, so doctor --fix installs a launch agent that cannot load. Apply the existing XML escape helper to the complete command before rendering it.
  • ⚠️ bin/fm-afk-contract.sh:825 - If an existing words file cannot be read, printf succeeds after cat fails, masking the failure. Proposal creation then proceeds with empty or partial captain words. Preserve the newline sentinel while propagating cat's failure, for example by running printf only after cat succeeds.

🔧 Fix applied.
6 issues (4 errors, 2 warnings) still open:

  • 🚨 bin/fm-remote-doctor.sh:682 - R1 requires preserving a running session when origin is unknown. Doctor still classifies unproven and unknown &lt;pid&gt; as fixable. With an existing launch-agent-owned server and inconclusive socket evidence, doctor --fix calls reload_launch_agent, whose unconditional launchctl bootout kills the server before the corrected guard runs. Enforce the preservation rule before every doctor reload.
  • 🚨 bin/fm-extension.mjs:1037 - Durable invocation recovery can terminate unrelated commands. After a host crash leaves an ownership record, the original group can exit and its PGID be reused. If the replacement leader exits while members survive, processGroupState returns 3 without verifying the saved identity; cleanup rejects only state 2 and sends TERM/KILL. Refuse leaderless groups during durable recovery unless ownership continuity is positively proved.
  • 🚨 .github/workflows/ci.yml:429 - The stock-Bash job requires 18 snapshot results, but fm-fleet-snapshot-view.test.sh now invokes 19 unconditional cases, each emitting one pass line. A successful suite therefore fails CI before the remaining checks run. Update the expected count to 19.
  • 🚨 tests/fm-herdr-pi-stale-registration-live-e2e.test.sh:59 - The isolation criterion requires a changed default session to be “a hard tripwire failure, never a cleanup warning to ignore.” Cleanup calls herdr_safe_stop_and_delete and then exits with the saved pre-cleanup status. If assertions pass but teardown detects a default-fleet change, the test still exits successfully. Propagate the helper failure into the final status.
  • ⚠️ bin/fm-watch.sh:1036 - The accepted cadence ruling says an unchanged declaration must never “bypass a fresh throttle.” Adding :due to its identity violates that rule: with a 240-second cadence and deadline at 250 seconds, the pause surfaces at 240 seconds and again ten seconds later. The same suffix appears at line 1328. Remove the deadline-dependent identity suffix while preserving immediate deadline handling before the first reminder.
  • ⚠️ bin/fm-remote-secondmate-control.sh:371 - Remote home synchronization fast-forwards the checkout without refreshing existing when-watch trust bindings. If an update changes a registered action under that home's bin directory, its next firing fails the action hash check and retires the watch. Local update paths already rebind these watches. Refresh bindings at the successful Firstmate-home advancement boundary shared by local and remote paths, using the target home's scope.

🔧 Fix applied.
5 issues (3 errors, 2 warnings) still open:

  • 🚨 bin/fm-promote.sh:200 - Promotion contradicts the requirement to “preserve the complete accepted specification, including Firstmate requirements and accepted steering.” The generated instruction says to treat the scout-time Firstmate spec as “not ... ship-time instructions,” while preserving only Captain's intent and generic rules. The added test explicitly discards “Ship the identity-check fix without adding a classifier.” Preserve applicable task-specific requirements with their provenance; supersede only investigation and delivery instructions that promotion actually replaces.
  • 🚨 bin/fm-spawn.sh:3456 - An agy readiness timeout calls rovo_endpoint_cleanup, which unconditionally kills the endpoint. On tmux, a live worker's busy marker can scroll out of capture during a long tool call; fm-busy-lib.sh explicitly classifies that case as unknown. After the polling window, this path kills the worker and its tools without proving exit. This contradicts “never declare dead or attempt recovery without positive proof of exit, and protect live shell tools.” Preserve the endpoint and report unreadable or unconfirmed startup when readiness evidence is inconclusive.
  • 🚨 bin/fm-wake-lib.sh:1474 - Slot ownership compares only task ID despite recording the owning home. If home A retains stale metadata for fix-api and an unregistered home B later claims that slot for its own fix-api, this returns mine. A's teardown --force then passes the ownership guard, kills B's worktree processes, and returns its working copy to the pool. The registered-home scan cannot catch B. Match both home and task at this shared boundary. Removing the deliberate moved-home exception requires a decision about how proven home migrations should be recognized.
  • ⚠️ bin/fm-supervise-daemon.sh:1218 - The daemon retains a deadline bypass after the watcher correction. With a 240-second cadence and deadline at 250 seconds, it reminds at 240 and resets the pause marker, but does not write the due marker. At 250, the due-file comparison is false, so it skips the fresh-throttle check and reminds again. This contradicts “must neither bypass a fresh throttle nor delay an expired one.” Remove the independent deadline bypass and preserve immediate deadline handling only before any reminder has been recorded.
  • ⚠️ bin/fm-bearings-board.sh:281 - Board cleanup can hide an unresolved secondmate decision because it checks an unrelated main-home task with the same key. The snapshot preserves remote task keys separately from their owners. If remote audit remains held while main audit is done, this lookup returns 1 and removes the remote card. The landed-ID comparison at line 305 also ignores ownership. Keep cards when ownership cannot be proved and remove unqualified task-based suppression. Extending the board contract and routing to support owner-aware suppression would require authorization.

🔧 Fix applied.
1 error still open:

  • 🚨 bin/fm-spawn.sh:3374 - Rovo still violates "never declare dead or attempt recovery without positive proof of exit, and protect live shell tools." After brief submission, rovo_wait_for_delivery can time out because the composer remains unreadable or its documented placeholder prevents an empty verdict. The worker may already be executing tools, but rovo_spawn_fail unconditionally calls rovo_endpoint_cleanup, which kills its endpoint. Apply the preservation rule at this shared Rovo failure boundary: retain the endpoint and ownership records and report unconfirmed startup unless exit is positively proved.

🔧 Fix applied.
1 error still open:

  • 🚨 bin/fm-spawn.sh:3375 - The new dead|missing branch reports agent exit confirmed, contradicting "never declare dead or attempt recovery without positive proof of exit." docs/verification/rovo.md:213-216 records Herdr returning dead during a live Rovo tool call because agent get returns agent_not_found. The shared classifier still returns no-agent before checking processes. Consequently, an inconclusive delivery timeout falsely confirms exit; steering is also refused by fm_task_inbox_ring, and recovery accepts the worker as already stopped. Apply process-level departure proof at the shared Herdr no-registration boundary, preserving unreadable when exit cannot be proved.

🔧 Fix applied.
1 warning still open:

  • ⚠️ .agents/skills/harness-adapters/references/harness/agy.md:31 - The operating guide says an unconfirmed agy startup closes the endpoint, but agy_spawn_fail now retains the endpoint and ownership records and reports unreadable. Update this guidance and docs/verification/agy.md:70-71 so operators do not mistake a startup timeout for confirmed worker exit.

🔧 Fix applied.
1 warning still open:

  • ⚠️ bin/fm-teardown.sh:3355 - Releasing the slot-owner claim here prevents retrying later cleanup failures. If Treehouse return succeeds but Herdr endpoint confirmation or parent-report delivery fails, teardown retains the task metadata and requests a retry. That retry reaches require_owned_worktree_slot_record, finds the deleted claim, and refuses even with --force. Defer claim release until task-record retirement succeeds under the existing project lock, preserving ownership evidence through retryable cleanup.

🔧 Fix applied.
1 warning still open:

  • ⚠️ bin/fm-teardown.sh:3026 - Child cleanup still releases slot ownership before retryable cleanup finishes. During forced secondmate teardown, Treehouse return can succeed, then retire_busy_state can time out on a recently orphaned hook lock. The child metadata remains, but its claim is gone, so the next teardown fails require_owned_worktree_slot_record even with --force. Defer this release until the child's metadata retirement succeeds under the existing project lock, matching the corrected top-level path.

🔧 Fix applied.
✅ Re-checked - no issues remain.

🔧 **Test** - 2 issues found → auto-fixed ✅
  • ⚠️ The complete lifecycle acceptance remains unproven. Real Herdr/Pi fleet operation, remote updates, and pipeline cleanup were not exercised. Their fixture coverage cannot replace live evidence. Complete validation requires an authorized guarded Herdr lab and disposable remote/pipeline targets, or a user decision to proceed with these gaps.
  • ⚠️ live validation verdict: inconclusive (9 of 14 scenarios were driven live against the product); untested: Preserve an unregistered or unreadable Herdr worker through uncertain startup, steering, and recovery, Keep native Pi fleet supervision running through AFK and return, Retry parent and child teardown after endpoint confirmation or state retirement fails, Advance a remote Firstmate home and keep its existing action watch operational, Clean up only a positively attributed no-mistakes run while preserving ambiguous runs
  • Live validation: ⚠️ inconclusive - 9 of 14 scenarios driven live against the product
Scenario Result Live Evidence
Migrate legacy memory, compile matching notes, and rerun without losing the archived original ✅ pass live Memory and Pi posture transcript
Enter quiet mode from Pi without granting away authority; preserve the separate away contract and refuse the retired daemon ✅ pass live Memory and Pi posture transcript
Cross a pause deadline without bypassing a fresh reminder throttle ✅ pass live Live reminder cadence: deadline crossed without another reminder; subsequent reminder arrived after 12 seconds with a 10-second cadence
Land a clean local-only branch and refuse a dirty checkout while preserving its work ✅ pass live Local landing and dirty-checkout refusal
Attempt landing from another harness while Pi holds the fleet lock ✅ pass live Fleet-lock refusal
Force teardown when another home owns the same task ID and preserve its running worker, checkout, and claim ✅ pass live Foreign slot and worker preservation
Rebind an updated action, fire it exactly once, and reject an unbound executable mutation ✅ pass live Watch rebinding and tamper rejection
Promote a scout while preserving requirements and steering provenance, and refuse unproven captain intent ✅ pass live Promotion and provenance refusal; Generated ship instructions
Open a board containing an unresolved remote decision beside a completed local task with the same ID ✅ pass live Rendered bearings board; Generated bearings board
Preserve an unregistered or unreadable Herdr worker through uncertain startup, steering, and recovery ⏸️ untested no The prescribed external lab helper and Herdr shared session registry fall outside the permitted workspace. Authorize the guarded lab exception and rerun the live Herdr checks.
Keep native Pi fleet supervision running through AFK and return ⏸️ untested no No guarded Herdr captain-and-worker fleet could be provisioned under the workspace boundary. Provide the lab exception and run the native Pi supervision checks there.
Retry parent and child teardown after endpoint confirmation or state retirement fails ⏸️ untested no These paths were tested with backend fixtures. Live endpoint-failure validation requires permission to provision the guarded Herdr lab outside the worktree.
Advance a remote Firstmate home and keep its existing action watch operational ⏸️ untested no No disposable remote secondmate target or authorized SSH access was supplied. Provide both to exercise the remote update boundary.
Clean up only a positively attributed no-mistakes run while preserving ambiguous runs ⏸️ untested no This phase explicitly forbids controlling pipelines. The outer executor must arrange an authorized disposable pipeline lifecycle test.
  • Inspected the base-to-target diff, merge commit 425591d, and unchanged protected memory and quality files.
  • TMPDIR="$PWD/.test-phase-tmp" bin/fm-test-run.sh --jobs 1 --script-timeout 360 --json ~/.no-mistakes/evidence/01M2FSAP36PE8TM5G7KKWCZCFM/targeted-results.json tests/fm-memory-compile.test.sh tests/fm-afk-contract.test.sh tests/fm-merge-local.test.sh tests/fm-teardown-endpoint-safety.test.sh tests/fm-teardown.test.sh tests/fm-agy-harness.test.sh tests/fm-rovo-harness.test.sh tests/fm-backend-herdr.test.sh tests/fm-watch-triage.test.sh tests/fm-procevent-when.test.sh
  • Reran tests/fm-teardown.test.sh diagnostically, then successfully with env -u TMPDIR bin/fm-test-run.sh --jobs 1 tests/fm-teardown.test.sh.
  • Corrected the agy fixture to use a real childless shell and complete process metadata; bin/fm-test-run.sh --jobs 1 tests/fm-agy-harness.test.sh passed.
  • Reran bin/fm-test-run.sh --jobs 1 tests/fm-watch-triage.test.sh with the repository's normal timeout; it passed.
  • Ran live-cli.sh through the real Pi 0.85.1 TUI shell, covering memory migration, compilation, quiet mode, away grants, archival, and refusal paths.
  • Executed evidence drivers live-landing.sh, live-slot.sh, live-watch.sh, live-cadence.sh, and live-promotion.sh against isolated real CLI/tmux environments.
  • Acquired an isolated fleet lock through Pi using bin/fm-lock.sh, then confirmed bin/fm-merge-local.sh landing refused the other harness.
  • Built the board with bin/fm-bearings-board.sh build, confirmed its real Lavish listener, rendered the generated HTML in headless Chromium at 1440x1200, and inspected the screenshot.
  • Collected final test results, closed test sessions, removed transient files with find .test-phase-tmp -depth -delete, and confirmed only tests/fm-agy-harness.test.sh remains modified.

🔧 Fix applied.
✅ Re-checked - no issues remain.

  • Live validation: ✅ go - 4 of 8 scenarios driven live against the product
Scenario Result Live Evidence
Launch with an exhausted readiness window and preserve the real worker despite unconfirmed startup ✅ pass live Live Herdr transcript; herdr-recheck/herdr-live-spawn.log
Send steering while liveness reads fail and have the worker execute the saved message ✅ pass live Live Herdr transcript; herdr-recheck/herdr-live-inbox/handled/001.msg; herdr-recheck/herdr-live-steer-result.txt
Attempt recovery with unreadable liveness and preserve ownership and the running shell tool ✅ pass live Live Herdr transcript; herdr-recheck/herdr-live-recovery.log
Tear down the guarded lab and leave the default-session tripwire unchanged ✅ pass live herdr-recheck/herdr-default-baseline.json; herdr-recheck/herdr-default-after.json; guarded cleanup exit=0
Keep native Pi supervision continuous through AFK and return ⏸️ untested no Requires a real captain and an authorized native Pi supervision session. The user accepted this live coverage gap.
Retry parent and child teardown after real endpoint or state-retirement failure ⏸️ untested no Requires a disposable parent and child fleet with a safely induced real cleanup failure. The user accepted targeted regression evidence for this gap.
Update a remote home across SSH and preserve its registered action watch ⏸️ untested no No disposable remote home exists. Provide one with SSH access to exercise this accepted gap.
Clean up an attributed disposable pipeline while preserving ambiguous runs ⏸️ untested no No disposable pipeline target exists. Provide one and cleanup authority to exercise this accepted gap; the shared daemon remained untouched.
  • Inspected the target diff, prior targeted results, and existing live evidence.
  • bash ~/.no-mistakes/evidence/01M2FSAP36PE8TM5G7KKWCZCFM/herdr-recheck/live-herdr.sh
  • TMPDIR="$PWD/.test-phase-herdr/regression-tmp" bash tests/fm-agy-harness.test.sh
  • Queried sessions through bin/fm-herdr-lab.sh run &lt;named-lab-session&gt; session list --json; confirmed lab removal and compared the default-session snapshot with its baseline.
  • Removed temporary testing data and confirmed git status --short was empty.
✅ **Document** - passed

✅ No issues found.

🔧 **Lint** - 1 issue found → auto-fixed ✅
  • ⚠️ linter found issues (exit code 1)

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ **Push** - passed

✅ No issues found.

kunchenguid and others added 30 commits August 29, 2026 10:34
…nchenguid#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
…henguid#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
…#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(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
* 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
…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 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 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(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
…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>
…d#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 pane-churn fail-closed documentation

* fix(bin): prioritize active pipeline-owned crew runs (kunchenguid#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

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

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

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

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

* no-mistakes(document): Align pane-churn watcher documentation

* no-mistakes(ci): Captain, fixed the flaky cooldown boundary test by freezing its executable clock. The failure reproduced before the fix and passed five consecutive full-suite runs afterward. Extended ShellCheck passed; full lint stopped because actionlint 1.7.12 is not installed

---------

Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
* fix(bin): add a safe owner for custom-check retirement

Agents were improvising rm of check files with unset STATE/ID, which wedges
headless panes. Unregister validates the id and state directory first.

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

* no-mistakes(review): Refuse explicitly empty custom-check state overrides

* no-mistakes(document): Document custom-check retirement safety contract

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
…o dedicated scripts (kunchenguid#3221)

* Add quota exhaustion detection and safe fallback helpers

- bin/fm-procevent-quota.sh: generic procevent adapter that arms a
  recurring quota-axi --json poll and wakes firstmate when a tracked
  provider's effectivePercentRemaining drops below a threshold or its
  runway.status becomes exhausted_now.
- bin/fm-quota-choose.sh: worker-side helper that picks the first ranked
  harness:model candidate with positive effectivePercentRemaining.
- AGENTS.md and .agents/skills/quota-array-dispatch/SKILL.md: document
  the new helpers and the mid-task quota-exhaustion wake path.
- tests/fm-quota-choose.test.sh: unit tests with a mocked quota-axi JSON
  source.

* no-mistakes(review): Fix quota polling and scope bounds

* no-mistakes(review): Enforce safe default quota selection

* no-mistakes(review): Handle decimal quota values safely

* no-mistakes(review): Fail closed on invalid quota inputs

* no-mistakes(review): Reject empty quota candidate segments

* no-mistakes(review): Harden quota parsing and timeout ownership

* no-mistakes(review): Reuse captured quota snapshots consistently

* no-mistakes(review): Match quota using explicit candidate providers

* no-mistakes(review): Centralize fail-closed quota schema validation

* no-mistakes(review): Reject out-of-range quota percentages

* no-mistakes(review): Validate quota runway status enum

* no-mistakes(review): Tighten quota scope and status contracts

* no-mistakes(review): Preserve unknown quota and exact product bounds

* no-mistakes(review): Preserve provider-level unknown quota

* no-mistakes(review): Reuse canonical verified harness validation

* no-mistakes(document): Document mid-task quota handling

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* fix(docs): restore default routing contract, keep quota helper optional

Restore the AGENTS.md section 4 always-loaded routing paragraph the PR
had deleted, so the standing TOON-first intake, spendPriority ranker,
every-candidate accounting, and load-trigger contract stay exactly as
before this PR. The mid-task quota wake is optional and must not alter
default routing.

Restore the quota-array-dispatch skill ownership line to section 4 as
the always-loaded intake boundary owner; keep the worker-side helper
section as an addition only, without rewiring ownership or load
triggers to section 13.

* fix(bin): use harness-keyed quota matching in optional helper

Revert fm-quota-choose.sh from harness:provider:model tuples back to
harness:model candidates with harness-keyed provider matching, per the
resolved ask-user finding. The helper is optional; authoritative
multi-provider routing (provider discovery from the harness catalog and
quota matching by that explicit provider) stays owned by AGENTS.md
section 4 and the quota-array-dispatch skill intake procedure, not the
helper.

Document the multi-provider limitation in the helper header and the
quota-array-dispatch skill: the helper maps each harness to one primary
provider family only, so a candidate whose established provider differs
from that primary family is checked against the wrong quota row. Use it
only when the brief fixed the candidate order and every candidate's
provider is the harness's primary family.

The helper still consumes one already-captured default-TOON or JSON
snapshot via stdin or --snapshot and never calls quota-axi itself, so
it selects from the same quota state as the intake.

* no-mistakes(review): Fix Muse quota mapping and helper contract docs

* no-mistakes(review): Reject known-empty quotas and map quota tests explicitly

* no-mistakes(review): Preserve unmeasured candidates and enforce snapshot reuse

* no-mistakes(review): Fix quota retirement and dependent regression coverage

* no-mistakes(review): Accept zero-row quota TOON snapshots

* no-mistakes(review): Enforce quota semantics status consistency

* no-mistakes(review): Veto dispatch on any exhausted applicable scope

* no-mistakes(review): Record exhausted quota scope in wake details

* no-mistakes(review): Fix quota help and control dependency coverage

* no-mistakes(review): Decode quoted TOON fields and document quota wakes

* no-mistakes(review): Validate zero-row TOON and map timeout coverage

* no-mistakes(review): Reject multi-value JSON and malformed TOON envelopes

* no-mistakes(review): Validate complete nonzero TOON envelopes

* no-mistakes(review): Accept producer-shaped quota TOON envelopes

* no-mistakes(review): Support empty quota arrays and validate counted rows

* no-mistakes(review): Harden TOON completion, scopes, and quoted fields

* no-mistakes(review): Preserve unknown-headroom exhaustion and reject trailing fields

* no-mistakes(review): Allow unknown headroom under known semantics

* no-mistakes(review): Reject noncanonical quota identities

* no-mistakes(review): Preserve empty quota polling and validate attention identities

* no-mistakes(review): Reject noncanonical provider watches

* no-mistakes(review): Validate all candidates before quota selection

* no-mistakes(document): Correct quota helper safety documentation

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes
* fix(bin): keep typed Lavish comments when an element is also annotated

read preferred element text over prompt, so an annotate-and-comment
item dropped the captain's words. Surface prompt as its own field.

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

* no-mistakes(review): Filter non-comment prompts from Lavish reader output

* no-mistakes(document): Clarify Lavish comment presentation contract

* no-mistakes(ci): Fixed Lavish reader comment provenance: non-choice prompts are now emitted even when identical to element text. Added observable regression coverage for identical selector+comment input while retaining pure annotation/message coverage. Reader cases, bash syntax, and diff checks pass. Full fm-procevent suite stops earlier at unrelated “reconcile never claimed” setup failure

* no-mistakes(ci): Fixed duplicate pure-annotation prompts by emitting `prompt:` only when it differs from captured element text. Updated behavioral coverage for selector+comment, pure annotation, and pure message cases. Focused reader regressions, syntax checks, and diff checks pass. Full suite remains blocked by the pre-existing “reconcile never claimed the registered source” failure

* fix(bin): always emit Lavish comments and use real annotation fixtures

Stop inferring comment provenance from prompt==text. Real pure
annotations have no prompt, so always-emit does not duplicate.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
…uid#3420)

* Fix public-followup register crashing on empty lock arrays under bash 3.2.

bash 3.2 with set -u treats "${arr[@]}" on an empty array as unbound, so the first register in a fresh home aborted before taking the registry lock.
The empty-lock regression also runs under the existing stock macOS Bash CI lane so pre-fix code would fail there.

* no-mistakes(document): Document stock Bash registration coverage

* no-mistakes(ci): Pinned the stock macOS Bash CI lane to tasks-axi@0.2.5, eliminating dependency drift. Verified workflow YAML parsing, git diff checks, and the focused regression under /bin/bash 3.2.57 with tasks-axi 0.2.5

* no-mistakes(ci): Fixed the flaky portable CI test: it treated exited zombie processes as live because `kill -0` succeeds for zombies. The watcher and descendant assertions now check process state and regard zombies as exited. Verified `tests/fm-pr-check-security.test.sh`, ShellCheck, `git diff --check`, and the focused Bash public-followup regression
* fix(herdr): isolate server launch environment

* no-mistakes(review): Clear inherited supervision model from Herdr launches

* no-mistakes(document): Document Herdr server launch environment isolation
* fix: surface inbound Relay attachments to the responding agent

A Discord support thread's screenshots were never seen by the agent
handling the mention. The relay delivered them and the poll stashed
them: the reporter's images arrived on the `thread_starter` entry of
`in_reply_to_chain` while the mention's own media list was empty. The
gap was in the responder's playbook, which enumerated a fixed field
list (`request_id`, `text`, `in_reply_to`, `in_reply_to_chain`) and so
made every other field, attachments included, invisible.

Fix it where the gap is, in prose:

- Read the complete payload object rather than a fixed field list, so
  media and later relay fields are never skipped again.
- Fetch and view attached media with the agent's own tools, on the
  mention and on every chain entry, and call out the common shape where
  only the thread starter carries the screenshots.
- Restrict those fetches to known-good platform media hosts over https
  (Discord: cdn.discordapp.com, media.discordapp.net,
  images-ext-1.discordapp.net, images-ext-2.discordapp.net; X:
  pbs.twimg.com, video.twimg.com), report a blocked host instead of
  working around it, and treat everything fetched as untrusted public
  input on the same terms as the surrounding thread text.

The poll stays out of it and downloads nothing, so no third-party bytes
are pulled on the polling path.

The new test pins the contract the playbook depends on: a mention in the
incident's shape, with an empty top-level media list and screenshots on
the thread starter, must reach the inbox with the payload intact and its
media URLs unfetched.

* no-mistakes(review): Preserve media authority and enforce poll-only fetching

* no-mistakes(document): Clarify Relay attachment safety prose
)

* Defer inactive startup reconciliation

* no-mistakes(review): Queue deferred inactive reconciliation diagnostics durably

* no-mistakes(review): Require worker phases to cover startup requests

* no-mistakes(review): Make diagnostic wakes safely acknowledgeable

* no-mistakes(document): Document deferred startup phase coverage
* fix: bound status presentation lock waits

* no-mistakes(review): Distinguish malformed presentation locks from live contention

* no-mistakes(review): Bound no-ack drain queue lock acquisition

* no-mistakes(document): Document bounded presentation-lock drain behavior

* no-mistakes(lint): Annotate bounded lock output global

* no-mistakes(ci): Added deterministic regression coverage for successful bounded-lock acquisition after live contention, verifying helper-to-caller PID ownership handoff and caller release. Verified with bash syntax checks, git diff checks, and the full fm-wake-queue test suite
* fix(relay): close a public loop whose work lives in a remote secondmate home

A public-followup loop bound to a REMOTE secondmate could never be closed.
`clear_public_followup_link` (bin/fm-public-followup.sh:701) required an
absolute recorded `work_home_path` for a `secondmate:*` work home, but a remote
route has no local path on this machine, so registration records that field
empty (bin/fm-public-followup.sh:291). Every close ran that clear first, so
`retire` died with "could not clear the legacy X link ... retained for
reconciliation" forever, and `deliver` posted the public reply and then stranded
the loop at `posted`. `--force` never covered that step.

The clear now goes to the remote home over that route's SSH transport, running
`fm-x-followup.sh --clear <work-id>` through `bin/fm-on.sh`. The route is decided
from `data/secondmates.md` before any local path is consulted, so a same-named
local directory can never stand in for a remote home, and registrations already
on disk retire without needing a new field. `fm-on.sh` passes ssh's status
through, so 255 stays the established "delivered but completion unknown" result
this codebase already reconciles: the close is refused, the registration and the
remote link are left exactly as they were, and the message names the unknown
completion instead of claiming a definite failure.

Local secondmate and `main` work homes are untouched, and `--force` still
governs only the unresolved-obligation refusal.

Three regression cases drive a remote route end to end, faking only the ssh
binary at the FM_SSH_BIN seam and then running the real remote entrypoint
against a local checkout, so the clear that must reach the remote home actually
happens there.

* no-mistakes(review): Guard remote link clears by request identity

* no-mistakes(review): Fail guarded clears on unreadable remote state

* no-mistakes(review): Reject guarded clears on non-writable remote state

* no-mistakes(review): Allow no-link retirement in non-writable remote state

* no-mistakes(document): Correct public-followup verification guarantee count

* no-mistakes(ci): Fixed the guarded link-clear race by ensuring absence is decided under the metadata lock whenever publication is possible. Added a behavioral concurrency regression test. Verified with fm-x-mode and fm-public-followup suites, Bash syntax checks, diff checks, and bin/fm-lint.sh

* no-mistakes(ci): Fixed the guarded link-clear race by refusing an unlocked absence decision when a publisher already owns the metadata lock in a non-writable directory. Added a behavioral concurrency regression test. Verified with fm-x-mode, fm-public-followup, syntax/diff checks, and fm-lint

* no-mistakes(ci): Fixed the guarded-clear race by refusing all guarded clears when the metadata parent is non-writable, including apparent link absence. Added a behavioral regression with a publisher waiting to create the lock, updated remote-retirement expectations and verification docs. Passed fm-x-mode, fm-public-followup, fm-lint, documentation audience, Bash syntax, and diff checks

* fix(relay): bound the guarded remote link clear so it refuses instead of hanging

The guarded clear checks that the remote state directory is writable before
taking the metadata lock, but that check cannot close the window: the parent can
turn non-writable between the check and lock creation, and a lock held by a live
holder is indistinguishable from that at the acquire. `fm_lock_acquire_wait` is
an unbounded `while ! try; do sleep 0.1; done`, so either case retried forever
and `deliver` or `retire` wedged with nothing reported, instead of returning the
retained-for-reconciliation refusal the guard exists to produce. This path runs
unattended over the secondmate transport, where a wedge is worse than either
outcome the guard defines.

The guarded clear now acquires through `fm_lock_acquire_wait_bounded`
(FMX_LINK_CLEAR_LOCK_TIMEOUT, default 10 seconds) and refuses on timeout through
the existing failure path. Unguarded local callers keep the ordinary unbounded
wait, so local behavior is unchanged.

The bounded primitive's header no longer claims presentation-only scope, since
this is a second authorized caller; nothing else in the shared lock
infrastructure changed.

The regression holds the metadata lock with a genuinely live process while
leaving the state directory writable, so the refusal can only come from the
bound and never from the writability precondition. Against the unbounded wait it
does not terminate at all; with the bound it refuses, retains the registration,
writes no receipt, and leaves the remote link untouched.

* no-mistakes(review): Harden lock-timeout regression with independent deadline

* no-mistakes(review): Restore no-op guarded clears on read-only state

* no-mistakes(document): Clarify remote public-followup cleanup contract
)

* fix(bin): resolve process-event state roots before validating them

The process-event module validated the caller's spelling of a home's state
root instead of the directory it operates on: it required the supplied path
to equal its own lexical normalization, which rejects any path reached
through a symlinked ancestor. On macOS both /tmp and $TMPDIR are symlinks,
so an operator home under either could never claim a source. Reconcile still
reported the runner started, while the detached runner died writing "cannot
claim source" to the discarded stderr, and the source silently never fired.

Resolve the state root to its physical directory once, then apply the
existing private-directory validation to that resolved directory and derive
every path, recorded claim identity, and later confinement check from it.
This keeps the confinement contract for the directory actually operated on
rather than only for callers that already spelled it physically, and removes
the window where an ancestor symlink could be repointed between check and
use. Homes already spelled physically behave identically.

This was the single cause of both deterministic macOS failures in
tests/fm-procevent.test.sh ("reconcile never claimed the registered source")
and tests/fm-procevent-when.test.sh ("the winning concurrent arm did not
produce an outcome"). The new case pins the behavior with an explicit
symlinked-ancestor home, so it fails without the fix on any platform rather
than only where the temp root happens to be a symlink.

* fix(bin): pin the external capture staging boundary to its physical path

The extension capture path pinned its registry staging boundary by comparing
`pwd -P` against the caller-spelled registry directory, so a home reached
through a symlinked ancestor still refused to start an extension-backed
source after the state root itself resolved correctly. That left such a home
half working: built-in sources ran while external ones failed.

The staging preparer now prints the physical registry directory it validated,
matching the inbox and reservation preparers beside it, and the start path
pins on that returned path. The new end-to-end case drives the shipped
file-signal package from a symlinked home spelling.

* no-mistakes(review): Propagate canonical process-event state roots

* no-mistakes(review): Propagate canonical state to process-event adapters

* no-mistakes(document): Document physical process-event state roots
…kunchenguid#3312)

* fix(pi): persist captain outcomes visibly

* no-mistakes(review): Recover captain outcomes after cold-start lock acquisition

* no-mistakes(document): Document cold-start captain-outcome recovery

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes(review): Prove immediate Pi captain-outcome transcript delivery

* 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

* fix(pi): process captain outcomes through a sequence-keyed turn

PR kunchenguid#3312 made every captain-facing supervision outcome a durable, exact-once
visible transcript entry with the read cursor advancing only after that entry
exists. That is the display half of the delivery contract. Left alone it turns
a probabilistic silent loss into a deterministic one: the captain sees an
anchor line, and firstmate never acts, because nothing opens a turn and
nothing records whether main ever processed the outcome.

The 2026-08-31 timeline showed the two shapes this must survive on the
previous hidden-turn path: seven delivered decision outcomes each answered by
an empty assistant message (cursor advanced, no retry, unanswered for close
to three hours), and two answered by an unrelated prior reply. Both happened
because delivery advanced the cursor at enqueue and accepted whatever the
next assistant message was.

Add the processing half on top of the persistence half:

- bin/fm-branch-outcome.sh keeps a processed marker separate from the read
  cursor (`unprocessed`, `mark-processed --through`, `processed-init`). It
  only advances through an explicit sequence-bound acknowledgement, never
  past the read cursor and never backwards; an absent marker reads as zero
  and `processed-init` migrates delivered history once so an upgraded home
  is not re-presented its past.
- After the visible entry for a captain outcome exists, the extension hands
  every still-unprocessed captain row to main as one hidden, typed
  `fm-branch-process` request listing each `[seq N] task: summary`, opening
  exactly one main turn. Main closes it only by calling the new
  `fm_branch_processed` tool with the highest sequence listed. An unrelated,
  empty, or paraphrased answer leaves the sequence open, and the same request
  is presented again at the end of the next main run and at session start.
  The first two presentations of a sequence set open a turn of their own;
  after that the request rides the captain's next prompt so an ignored
  request cannot loop, and a session replacement resets that budget.
  Routine outcomes stay turn-free.
- The regressions cover exactly those incident shapes against the real store
  scripts: an empty answer and an unrelated prior answer neither advance the
  marker nor stop re-presentation, the acknowledgement is refused beyond the
  read cursor and outside lock ownership, a partial acknowledgement keeps the
  newer sequence open, and kunchenguid#3312's own assertions now forbid an unkeyed turn
  rather than any turn. The store suite pins the marker's bounds and the
  migration; the real-SDK guard for appendEntry persistence and model
  exclusion is unchanged.

Docs move the protocol from "no model turn" to "one sequence-keyed processing
turn closed only by its acknowledgement", and the verification record carries
the dated run against Pi 0.84.4.

* no-mistakes(review): Harden outcome listing and sequence-bound acknowledgements

* no-mistakes(review): Harden outcome state validation and request pacing

* no-mistakes(review): Reject unsafe sidecars and unterminated outcome stores

* no-mistakes(review): Validate canonical mark-read cursor state

* no-mistakes(review): Guard cursor advancement against corrupt processed state

* no-mistakes(review): Bind acknowledgements to active processing requests

* no-mistakes(review): Reset pacing when processing sequence membership changes

* no-mistakes(review): Enforce silent outcome invariants at storage boundary

* no-mistakes(document): Document hardened captain outcome processing contracts

---------

Co-authored-by: kunchenguid <kun@kunchenguid.com>
…3481)

* feat: bound Bearings remote ledger collection

* no-mistakes(review): Clarify default remote-ledger collection behavior

* no-mistakes(review): Detach reconcile delivery from watcher loop

* no-mistakes(review): Enforce bounded snapshot and request captures

* no-mistakes(review): Bound legacy summary capture before parsing

* no-mistakes(review): Bound primary remote ledger captures

* no-mistakes(document): Correct snapshot and reconcile documentation

* no-mistakes(lint): Fix ShellCheck quoting in bounded collector

* no-mistakes(ci): Fixed all three CI failures: updated the macOS Bearings assertion to 44 tests, made the home-summary test deterministic and aligned with default ledger consumption, and increased the asynchronous reconcile retirement wait for loaded CI. Verified both focused suites, all 44 Bearings tests, ShellCheck, actionlint, Bash parsing, and git diff checks

* test: await reconcile request retirement

* no-mistakes(review): Avoid empty reconcile queue process churn

* no-mistakes(review): Read ledger summaries from immutable snapshots

* no-mistakes(review): Reject multi-document home ledger streams

* no-mistakes(review): Coalesce durable reconcile requests per target

* no-mistakes(review): Unify reconcile keys and reject snapshot streams

* no-mistakes(review): Key reconcile requests by stable target ID

* no-mistakes(document): Document per-target reconcile request coalescing

* no-mistakes(lint): Remove unused snapshot summary file variable

* no-mistakes(ci): Adjusted the concurrent collector regression’s end-to-end timing ceiling to account for stock macOS process/jq overhead outside the three-second remote collection budget, while remaining below the 15-second serial-read floor. Verified with stock /bin/bash 3.2: all 44 Bearings tests pass; bash syntax and git diff checks pass

* no-mistakes(ci): Fixed legacy summary validation to require exactly one top-level JSON document and added behavioral regression coverage. Stabilized CI by conditionally waiting longer for durable reconcile delivery and synchronously stopping the fm-on worker tree before fixture cleanup. Removed a redundant flaky healthy-path timing assertion; the wedged-reader test still proves concurrent bounded collection. Verified fm-bearings-snapshot, fm-secondmate-reconcile, and fm-on tests, plus project ShellCheck, bash syntax, and git diff checks
* fix(ci): rebalance the portable serial shards on measured durations

The "Behavior portable serial 3" shard ran 17-20 minutes against its
20-minute job cap and intermittently timed out seconds after a passing
test, on branches and on main alike.

Shards are packed longest-processing-time from per-script duration hints,
and those hints were last measured on 2026-08-21 at 116 scripts. The lane
has since grown to 139 scripts and from ~42 to ~63 minutes: 17 scripts had
no hint at all and fell back to the 20 s default, and several existing
hints were low by 2-5x (fm-watch-triage 142 s hinted vs 263 s measured,
fm-public-followup 36 s vs 197 s). The partition therefore looked
perfectly balanced in hint space, 734.6 s per shard, while really running
11.5, 13.6, 18.8 and 16.5 minutes. Script-count balance, which is what the
tests asserted, stayed normal throughout and hid it.

Refresh the hints from the timing artifacts of three green runs, taking
the slowest measurement of each script so the balance holds on a slow
runner, and split the lane across five shards instead of four. Replayed
against those runs' real per-script durations the worst shard is now
12.54 minutes, 63% of the unchanged 20-minute cap, and the serial lane's
wall clock drops from ~20 to ~12.5 minutes.

Bound the drift that caused this rather than relying on the hints being
refreshed by hand: the coverage guard now reports the unmeasured share as
serial_unhinted= and refuses past PORTABLE_SERIAL_MAX_UNHINTED_PERCENT,
which leaves room for newly added tests while making a stale table fail
the guard instead of silently pushing one shard into its cap.

No test changes what it asserts and no test stops running; only the
partition across shards changes.

* no-mistakes(document): Clarify conservative shard timing aggregate
…uid#3491)

* fix(pi): fall back after settled branch errors

* no-mistakes(review): Detect provider errors across prompt compaction

* no-mistakes(review): Preserve in-flight branch state across selection changes
* fix(pi): recover supervision branch after cooldown

* no-mistakes(review): Defer branch recovery until prompt settlement

* no-mistakes(document): Clarify supervision cooldown recovery contract
* refactor: remove legacy remote summary reads

* no-mistakes(document): Document ledger-only snapshot reads

* no-mistakes(ci): Fixed the snapshot test fixture so ledger refreshes use the same fake executable PATH as the snapshot consumer. This preserves observable endpoint freshness after removing legacy summary computation. Verified stock Bash parsing and all 44 Bearings tests pass under /bin/bash; git diff checks pass

* no-mistakes(ci): Fixed the CI-only snapshot fixture failure by ensuring the bounded-ledger refresh uses its fake tmux backend. This removes host tmux availability as a source of nondeterminism. Verified all 44 Bearings tests pass, Bash syntax passes, and git diff checks are clean

* no-mistakes(ci): Fixed CI nondeterminism in the Bearings fixture: all local ledger refreshes now use the fixture’s fake tmux backend when available, instead of depending on host tmux state. Verified stock /bin/bash syntax, git diff checks, and all 44 Bearings tests with a deliberately failing host tmux
…henguid#3498)

* fix(pi): rearm watcher after session replacement

* no-mistakes(review): Queue actionable closes across Pi session replacement

* no-mistakes(review): Stop replacement arm when handoff persistence fails

* no-mistakes(review): Preserve actionable wakes through branch and late child races

* no-mistakes(review): Surface late handoff failures without crashing Pi

* no-mistakes(review): Coordinate replacement delivery settlement and unique handoff tokens

* no-mistakes(review): Retry stale deliveries and release settled claims

* no-mistakes(review): Distinguish branch settlement and retry handoff cleanup

* no-mistakes(review): Deduplicate persistent handoff cleanup alerts

* no-mistakes(review): Acknowledge watcher follow-ups only when consumed

* no-mistakes(review): Persist idle follow-ups until agent consumption

* no-mistakes(review): Preserve pending outcomes when handoff persistence fails

* no-mistakes(review): Arm replacement before awaiting prior delivery settlement

* no-mistakes(review): Adopt pending handoffs after lock reclamation

* no-mistakes(review): Prevent stale generations from adopting replacement handoffs

* no-mistakes(review): Scope replacement handoffs by watcher state

* no-mistakes(document): Clarify replacement handoff documentation

* no-mistakes(ci): Fixed the failing branch-extension tests to model the new settlement-promise contract. Failure cases now assert that delivery ownership returns to the watcher instead of expecting direct extension fallback. Verified the updated branch suite, Pi watcher suite, shell syntax, and diff checks

* no-mistakes(review): Update branch settlement tests and preserve chunked outcomes

* no-mistakes(document): Document watcher-owned replacement handoffs

* no-mistakes(document): Verify replacement handoff documentation

* test(pi): cover watcher-owned branch fallback

* no-mistakes(document): Refresh watcher-owned fallback documentation
…d#3495)

* fix(bin): resurface terminal statuses lost after branch handling

* test(watch): canonicalize process-event fixture homes

* no-mistakes(review): Index branch outcomes by causal status position

* no-mistakes(review): Recover outcome indexes and deduplicate resurfaced statuses

* no-mistakes(review): Handle legacy ambiguity and oversized status diagnostics

* no-mistakes(review): Keep unclassifiable oversized statuses silent

* no-mistakes(document): Document lost-wake outcome backstop

* no-mistakes(document): Update outcome backstop documentation

* no-mistakes(ci): Fixed CI regressions in wake-drain: parseable reserved-key decisions can no longer bypass the durable decision-fold guard, and status output is prepared and receipt-committed before presentation to prevent repeated one-shot outcomes after later failures. Added a behavioral regression for receipt commit failure and retry. Targeted backstop, correlation-token, decision-cursor, open-decision, unread-status, syntax, and diff checks pass locally. Shard-4 failures appeared unrelated/flaky; the network-parallel test passed locally

* no-mistakes(ci): Fixed the Greptile P1 data-loss issue by committing presentation receipts only after prepared output reaches stdout. Added behavioral coverage proving output failure leaves the backstop retryable and receipt failure may duplicate but never lose a presentation. Relevant wake-drain suites and syntax/diff checks pass. The shard-4 Pi extension failure is unrelated to this PR and did not warrant changes

* no-mistakes(ci): Stabilized tests/fm-bootstrap-network-parallel.test.sh by replacing scheduler-sensitive equal-sleep timing with bounded synchronization between mocked fetch and remote probes. This preserves detection of real serialization while avoiding false failures under CI load. Verified with five consecutive test runs, bash syntax validation, ShellCheck, and git diff checks. The separate Pi stock-rendering failure reproduces locally but is unrelated environment/version drift

* no-mistakes(ci): Fixed Behavior portable serial 4 by adding fm-classify-lib.sh and fm-timeout-lib.sh to the broken-root Pi test fixture; fm-branch-outcome.sh now depends on them. Verified the full Pi branch-extension suite with real-Pi checks skipped, the wake-drain outcome-backstop suite, Bash syntax, and git diff checks. Greptile findings are already addressed at HEAD; the no-mistakes attestation failure is external head-SHA state
…id#3503)

* fix(bin): deliver typed terminal results from remote work homes

A public commitment whose work is bound to a REMOTE secondmate home could
never receive its typed terminal result. `fm-public-followup.sh brief`
printed an emit command carrying this home's own absolute path and this
checkout's own script path, neither of which exists on the machine the
worker runs on, so the worker had nothing it could write to that the
owning home would ever read - and `consume` kept finding nothing while
the promise stayed open.

The brief is now route-aware: for a remote work home it prints that
route's own code root and home with `--stage-in`, so the typed event is
staged in the home where the work actually runs, and the closing
paragraph names the owning home as the one on the other machine instead
of pointing at the path above it. The owning home collects those staged
results over the same SSH route it reaches that secondmate on, because
the transport only runs outbound: `consume` pulls them into its own
inbox and reconciles them exactly as it reconciles a local report.
Collection is non-destructive until the result is durably held, so a
dropped connection cannot lose a terminal result, and a route that could
not be reached is named in `consume`'s output with the promise left open
rather than reported as an empty inbox.

A local work home is untouched: the brief still prints `--home` with this
home and this checkout's script, and the event still lands directly in
this home's typed terminal-result inbox.

This is the emit-side counterpart of the retire/clear fix in kunchenguid#3479 and
reuses the remote-route resolution that landed with it. Reconciling a
loop bound to a remote route now reaches that route, so the existing
remote cases drive `consume` through the same faked transport their
other steps already use.

* no-mistakes(review): Fail loudly on unresolved routes and invalid staging homes

* no-mistakes(review): Fail collection when remote outbox is unreadable

* no-mistakes(review): Surface reassigned remote routes during empty collection

* no-mistakes(review): Fail remote collection on invalid registrations

* no-mistakes(review): Reject unsafe registration entries during remote collection

* no-mistakes(review): Restore healthy empty remote collection behavior

* no-mistakes(review): Skip remote collection for delivered registrations

* no-mistakes(review): Skip delivered registrations before route validation

* no-mistakes(document): Document remote follow-up collection semantics
RooseveltAdvisors and others added 25 commits September 12, 2026 11:21
…nnot blind a session start (kunchenguid#4027)

* fix(bin): bound each backlog row read so one wedged backend cannot blind a session start

bin/fm-bootstrap.sh's reconcile and close-replay sweeps read the backlog
backend once per item through fm_backlog_row_show, and that read was
unbounded. A single wedged `tasks-axi show` therefore consumed the whole
FM_SESSION_START_TIMEOUT and truncated the digest before the wake queue,
supervision instructions, fleet state, and context sections ever printed,
leaving the fleet unsupervised with no live watcher. The harm was a blind
startup, not a slow one.

Bound the read with the existing shared timeout primitive
(bin/fm-timeout-lib.sh), so a wedged backend degrades to a loud partial
reconcile: the sweep's existing BACKLOG_RECONCILE diagnostic names the item
it could not read and the loop continues to the next one. The first bound hit
also latches FM_BACKLOG_ROW_SHOW_WEDGED, so a sweep over many items pays one
bound rather than one per item and still names every item it skipped, which is
what keeps the digest whole on a home carrying a large fleet.

The bound holds regardless of any particular tasks-axi install, so it does not
depend on the 0.2.5 `show` hang being resolved separately.

* fix(bin): set the wedged-backend latch where it survives, and prove it

The latch added with the read bound was inert. fm_backlog_row_show runs inside
a command substitution in both of its status-capturing callers, so the subshell
read the inherited value correctly but its write died with the subshell. Every
item still paid a full bound and reported `exceeded`, never `skipped`, which
left the large-fleet case the latch existed to cover completely uncovered.

Move the write to the two callers that capture the read's status and own the
surviving shell, and leave fm_backlog_row_show reading the latch only. Correct
the comments that claimed an ownership the function never had.

The test that was supposed to cover this asserted only that the second read
finished under a generous ceiling, which is true whether or not the latch
works. Assert instead that a latched read is strictly faster than one bound and
that it reports its own item as skipped, so an inert latch fails the test.

* test: cover every item the wedged-backend latch skips

The latch assertion exercised a single skipped item, so "every skipped item is
still named" was inferred rather than tested. Probe three items instead and
assert each skipped one names itself and costs less than a bound.

Verified as a real guard by removing both latch writes: the suite then fails on
the first skipped item instead of passing.

* no-mistakes(review): distinguish backlog read-bound hits from absent rows

* no-mistakes(review): preserve read-bound status through the captain verify gates

* no-mistakes(review): Preserve backlog read-bound hits through resolve_entry and reconcile instead of spending them as absent rows

* no-mistakes(review): Preserve backlog read-bound 124 through migrated-prefix scan and remaining task_show call sites

* no-mistakes(document): Document bounded backlog row reads and FM_BACKLOG_ROW_TIMEOUT_SECS

* no-mistakes(ci): Fixed all four failing CI checks with one root-cause fix plus one test-heredity fix. (1) bin/fm-captain-hold.sh: task_show carries the row in TASK_SHOW_OUTPUT and emits no stdout, but four call sites still used the stale command-substitution convention show=$(task_show ...), leaving show empty: task_show_or_fail (every captain hold failed with 'did not retain its hold-set stamp' - broke fm-captain-hold-lifecycle in parallel 1 and fm-bearings-board in serial 3), resolve_migrated_entry (migrated-prefix resolution could never match), reconcile-requests (existing rows were refused as absent), and command_open --identity (printed a constant '#0' identity, so fm-watch-triage's re-held captain call inherited the previous call's silence in serial 1). This is also the Greptile P1. Fixed by invoking task_show in the current shell and reading show=$TASK_SHOW_OUTPUT, the convention the other eight call sites already use; read-bound hits still stop loudly by name. (2) tests/fm-backlog-read-bound.test.sh (serial 4, unclassified family): the new e2e half implicitly relied on the author's process tree containing a harness process so fm-lock.sh would grant the fleet lock; on CI runners the lock is refused, the reconcile sweep is skipped, and the final BACKLOG_RECONCILE assertion fails. Reproduced by simulating a CI ancestry via a ps shim, fixed by pinning the lock evidence with the established fake-ps harness fixture pattern from tests/fm-session-start.test.sh. Verified: shellcheck clean; parallel-1, serial-3, and serial-4 lanes fully green locally (failed=0); serial-1 lane green except fm-gemini-harness, which fails only under local Node v26 (comm=node-MainThread); CI's default Node 22 reports comm=node, the branch that test passes on, so it is not a CI failure

* no-mistakes(document): Verified bounded backlog read docs accurate across branch
…guid#4285)

* fix(merge): serialize the away-authority check with a synchronous merge

bin/fm-pr-merge.sh read the away-posture record for merge authority (the
per-task merge grant and the yolo/away-grant decision) and handed the merge to
the forge afterwards. An archive at the captain's return or a grant revoked by
a replacement record could land in between, so a merge could proceed on away
authority that no longer held.

The away record now carries a cross-subsystem lock, built on the existing
bounded lock primitive rather than a new lock format: the record-mutating
subcommands hold it across their mutation, and the merge holds it across both
its authority read and the forge command. Because a queued or auto merge
returns before the pull request lands, and would therefore outlive the lock,
an away merge is now refused whenever it could land asynchronously: a
requested --auto, a base branch whose merge-queue state does not prove an
immediate merge, and GitLab's asynchronous flags and configuration. What
remains permitted while away is the synchronous merge that lands inside the
lock.

This closes the common away-record/merge race against a live lock owner. It
does not make the merge atomic in every case, and two narrow races are
accepted and documented at their sites rather than hidden, both
confused-agent-grade in the sense bin/fm-lease-lib.sh already uses:

- A merge-queue rule change or a PR base change in the window between the
  queue-free preflight and the forge call can still enqueue the merge, which
  can then land after its grant lapses.
- Killing the lock-owning shell while its gh or glab child is still running
  lets stale-owner recovery reclaim the lock and the record be archived or
  replaced, after which the orphaned child can complete the merge on lapsed
  authority.

Closing either one needs landing verification or an ownership handoff, which
is deliberately out of scope here.

No existing gate is relaxed. The lock is taken after the live green-at-head
verify and the captain-hold check, the in-lock authority read is unchanged,
and a lock that cannot be taken refuses the merge rather than proceeding
unlocked. The away grant stays a structured field; no prose is parsed.

* no-mistakes(review): Fix GitHub rollup fixture base branch

* no-mistakes(document): Document atomic away-authority merge locking

* no-mistakes(ci): Updated two executable GitHub API fixtures to include the required baseRefName. Both previously failing test suites now pass: fm-captain-hold-lifecycle.test.sh and fm-pr-check-security.test.sh. git diff --check also passes
…unchenguid#4200)

* feat(agy): verify Antigravity CLI as third worker/scout adapter

Detection by anchored ancestry in fm-harness.sh (no marker of its own);
bootstrap harness and effort validation; launch template with model and
effort mapping plus reachable-catalog model validation; rendered-tail
busy fallback in fm-busy-lib.sh with delivery footer in fm-composer-lib.sh;
control mechanics with crewmate/scout-only refusal; tmux liveness naming;
router entry with concise adapter reference; dated verification record;
portable regression plus opt-in live drift guard.

Verified live on agy 1.2.0: supervised spawn, durable steering,
same-copy relaunch, and exit, with Herdr-native busy agreement.

* no-mistakes(review): bound agy model probe, gate trust dialog, narrow busy signature

* no-mistakes(review): pre-register agy workspace trust, make readiness gate strict

* no-mistakes(review): Close Orca terminal on gate failure; isolate live-guard HOME; tighten agy matching

* no-mistakes(document): Document agy adapter in stale harness enumerations

* no-mistakes(review): Clamp non-positive FM_AGY_MODELS_TIMEOUT to the default bound

* no-mistakes(document): Fix stale test-shard snapshots after agy lane additions

* no-mistakes(ci): Fixed ci-3 (tests/fm-agy-harness.test.sh:519). Root cause: the agy spawn fixture's default base PATH (/usr/bin:/bin:/usr/sbin:/sbin) omits node's directory, but the spawn drives the real bin/fm-agy-trust.sh (which hard-requires node to record trust) and the fixture's fake tmux trust lookup (node -e) under that PATH. On the ubuntu-latest CI runner node lives in the toolcache (/usr/local/bin), so trust pre-registration failed on portable serial 2; on typical Arch hosts node is in /usr/bin, masking the defect. Fix (smallest, following the existing tests/fm-kimi-harness.test.sh precedent of carrying the interpreter's resolved directory): resolve node from the invoking environment (failing the test with 'test needs node' if absent, as kimi does for python3) and prepend its directory to the fixture's default base PATH; the FM_TEST_BASE_PATH override contract is untouched. Verified locally: (1) pre-fix reproduction with a CI-shaped base PATH (system bins minus node) produced exactly the reported failure — 'node is required to record workspace trust and was not found on PATH' plus the fake tmux 'node: command not found'; (2) post-fix, all 29 tests in the file pass both with node available only via a leading non-standard dir in the base PATH (CI's shape) and with the default base PATH on this host. bash -n clean; ShellCheck is not installed in this worktree (previously recorded as environmental)

* no-mistakes(test): Give agy typed sends a longer submit-confirm budget

* no-mistakes(document): Document agy send budget, trust gate, and control coverage

* no-mistakes(document): Document agy busy fallback inventory and send-timing evidence
…uid#4337)

* feat(afk): add quiet supervision mode for a present captain

Adds a first-class quiet supervision mode alongside /afk for
kunchenguid#2356: the same away-mode daemon, injection,
busy/composer guards, classification policy, and reliability
properties, but the captain staying present and chatting no longer
exits it - only an explicit /quiet off does.

state/.afk's first line now declares its mode (away, the default, or
quiet); fm_afk_mode() in bin/fm-wake-lib.sh is the single reader,
falling back to away for missing/empty/unreadable/unrecognized
content (including the legacy bare-epoch-timestamp format written
before mode existed) so nothing regresses. fm_afk_flag_write()
preserves the on-disk mode on a bare refresh (no explicit mode given)
rather than defaulting to away, which is what keeps the daemon's own
redundant terminal-side re-write from silently resetting a captain's
quiet mode back to away underneath them.

New .agents/skills/quiet/SKILL.md is a thin wrapper cross-referencing
/afk for every shared mechanism, per the one-owner rule. AGENTS.md
gains the state/.afk table entry and section 8's exit-trigger line.
bin/fm-supervision-instructions.sh, bin/fm-session-start.sh, and
bin/fm-guard.sh's stale-watcher banner all become mode-aware so a
quiet-mode captain is never misdirected to /afk in captain-facing
text.

Closes kunchenguid#2356

* no-mistakes(review): Fix AFK epoch parsing and quiet-mode digest wording for two-line flag

* no-mistakes(document): Fix turnend-guard.md daemon-ownership contract for quiet mode

---------

Co-authored-by: NewAiCoder <claude@theinbtw.com>
Co-authored-by: NewAiCoder <170579485+NewAiCoder@users.noreply.github.com>
…kunchenguid#3578)

* fix(bin): let verified harness ancestry outrank retained markers (#3)

* fix(bin): let a structural harness ancestor outrank a retained marker

bin/fm-harness.sh treated a verified environment marker as unconditionally
authoritative, so a Codex session started from an environment that had retained
CLAUDECODE=1 detected as claude. Session start then emitted Claude's Stop-owned
supervision protocol to a Codex primary, and every turn end was blocked for
missing Claude recovery.

The defect is the precedence boundary, not any one harness. codex, opencode,
kimi, and muse publish no identity marker at all, so with markers winning
outright any retained CLAUDECODE renamed them; the Cursor-before-Claude ordering
was a point patch on the same class of problem, and the launch-time marker
clearing only ever covered sessions fm-spawn started.

Markers and ancestry are now separate evidence layers that detect_own arbitrates:

- no ancestry match, or no marker: the single available layer answers, unchanged;
- same harness family: the marker's finer verdict stands, so a launch-selected
  pi-signed is not flattened to pi by an ancestry walk that can only see the
  shared launcher name;
- different harness with a structural (command-name) ancestor: ancestry wins,
  because only ancestry proves who owns the process tree;
- different harness with only a bare-interpreter script-path match: the marker
  wins, since a harness-shaped path in some node process's arguments is weaker
  evidence than a harness publishing its own identity.

The correction is symmetric: a retained CURSOR_AGENT no longer renames a claude
worker nested under cursor either.

Adds fm-harness.sh ancestry [<pid>], ancestry evidence with no marker layer, so
a real harness process can be asked what the walk makes of it.

tests/fm-harness-precedence.test.sh is the portable regression, built from real
renamed processes with no harness installed. Every case drives the two layers
apart and asserts each alone as well as the combination, so no case can pass
vacuously; it also pins Codex's real two-process install topology, since the fix
depends on the native binary being what a tool subprocess meets first. The
opt-in drift guard gains the matching live half: each installed harness's real
running process must still be identified by the ancestry walk, and it fails
naming the harness and version when a release changes that name.

Documentation follows the corrected contract in the script header, the
harness-adapters detection section, the codex, opencode, kimi, and cursor
references, and a dated verification record.

* fix(tests): drop the unused argument pass-through in the shim-topology helper

bin/fm-lint.sh refused the branch: run_shim declared a `[ancestry]` argument and
forwarded "$@", but every call site that varies the environment or passes the
ancestry subcommand invokes the shim entry point directly, so the helper is only
ever called with no arguments (ShellCheck SC2120/SC2119).

Behavior is unchanged: with no arguments "$@" expanded to nothing.

* fix(bin): examine the top of the process chain instead of assuming init

harness_ancestry stopped as soon as the next pid was 1, on the assumption that
pid 1 is always init and can never be a harness.
Inside a PID namespace that assumption inverts: the harness itself is pid 1, so
the walk never examined the one process that proves who owns the tree, reported
no ancestry at all, and handed the verdict straight back to a retained marker.

A real Codex session under `codex sandbox`, holding CLAUDECODE=1 and
CLAUDE_CODE_ENTRYPOINT=cli, is exactly that shape: it resolved claude and
rendered Claude's Stop-owned supervision protocol even with the marker-vs-ancestry
precedence boundary in place.
The same probe now resolves codex and renders the Codex foreground checkpoint.

A host's real pid 1 (init, systemd, launchd) matches no harness name, so
examining it costs one ps call and can introduce no false positive; the walk
still stops once that top process has been read, and a non-numeric or zero ppid
still ends it.

tests/fm-harness-precedence.test.sh pins the namespace shape with a fake ps that
reports every process as bash with ppid 1 and pid 1 as the harness.
The case asserts the marker still answers alone when pid 1 is host-shaped, so it
cannot pass vacuously, and it fails against the previous stop condition.

* docs(verification): record the real-Codex retained-marker evidence

The existing record proved the precedence boundary with the portable regression
and recorded each installed harness's process name behind the ancestry walk, but
it had no evidence from a real Codex process actually holding a retained Claude
marker, which is the failure the boundary exists for.

Adds the dated before/after result from codex-cli 0.152.0 under `codex sandbox`,
with the exact command and the decisive verdict and rendered protocol on each
side, and records the second boundary that shape exposed: the walk must examine
the top of the process chain, because inside a PID namespace the harness is pid 1.
Refreshes the portable regression's observed output for the case it gained.

* no-mistakes(review): blind ancestry in marker-pinned harness tests

* no-mistakes(review): blind ancestry in the Pi guard-routing test

* no-mistakes(review): classify precedence suite, dedupe ps stub, soften claims

* no-mistakes(review): model the spawn-and-wait Codex shim topology

* no-mistakes(document): correct stale muse marker-clearing detection claims

* no-mistakes: apply CI fixes

* fix(bin): examine the top of the chain in the lock and nudge walks too

The pid-1 defect corrected in bin/fm-harness.sh survived unchanged in the two
other harness-ancestry walks, on the exact topology the branch verified against
a real Codex process.

bin/fm-session-lock-lib.sh's fm_harness_ancestry_pids stopped as soon as the next
pid was 1, so a firstmate whose harness is pid 1 of its own PID namespace could
not find that harness at all and did not recognize its own session lock.
bin/fm-sessionstart-nudge.sh carried the same stop plus a blanket rejection of a
lock pid of 1, so the same session was told to run session start again on every
turn.

Both walks now compare the top process before stopping, matching the shape used
in bin/fm-harness.sh.
For the lock walk this is safe because fm_harness_process_matches rejects a
host's real pid 1.
For the nudge, `kill -0` still gates the lock pid, and on a host an unprivileged
`kill -0 1` fails, so a lock file that wrongly names pid 1 leaves the hook silent
rather than acting on init.

Each walk gains one regression case. The lock case drives a deterministic process
table whose pid 1 is the harness and asserts a host-shaped pid 1 still finds
nothing, so it cannot pass vacuously. The nudge case needs a real PID namespace,
because the builtin `kill -0` gate cannot be reached through a fake ps, and it
first proves the same fixture nudges with no lock present; it skips explicitly
where unprivileged namespaces are unavailable.

* no-mistakes(review): assert comm-strength detection from subprocess vantage in drift guard

* fix(bin): verify the live harness guard at the strength the guarantee needs

The marker-versus-ancestry boundary this branch ships is a strength claim:
detect_own hands an args-strength verdict straight back to a retained foreign
marker, so a harness is only protected where the ancestry walk reaches it at
comm strength.

The installed-harness drift guard probed the pane process alone. Under an
interpreter shim the pane process IS the shim, whose own script path is args
strength, while the native binary that carries comm strength is its child. The
guard therefore observed args for Codex, passed, and would have kept passing if
a release stopped spawning that native child at all, while real sessions
silently regressed to the original bug.

fm-harness.sh gains `ancestry-subtree`, which asks the walk from the pane
process and every descendant of it, the vantage a tool subprocess actually
occupies. The guard now requires comm strength somewhere in that set and
requires every vantage to name the same harness.

This supersedes the preceding commit's in-guard leaf walk, which reached the
same vantage but left the logic inside the test file, where CI could not pin it
and nothing else could reuse it. A harness-dependent check needs both halves:
`tests/fm-harness-precedence.test.sh` now carries a portable case proving the
subtree probe reaches a strength the top-of-session probe cannot, mutation
checked twice, once against the pre-change script and once by disabling
descendant enumeration. The subtree walk also avoids depending on tty and
process-group semantics that differ between Linux and macOS.

Verified live: codex-cli 0.152.0 reports [args codex;comm codex] and Claude Code
2.1.257 reports [comm claude].

* no-mistakes(review): narrow drift guard to the upward vantage path

* no-mistakes(review): judge only comm-strength vantages in drift guard

* no-mistakes(document): drop duplicated rationale in detection precedence evidence

* no-mistakes(review): fix pid-1 nudge case vacuity and descent no-arg expansion

* no-mistakes(document): drop branch-relative phrasing in detection precedence evidence

* no-mistakes(review): guard remaining empty positional expansions in fm-harness

* no-mistakes(document): scope cursor marker-ordering claim to the marker layer

* no-mistakes(review): Prefer comm-strength leaves in equal-depth descent ties

* no-mistakes(document): Document comm-strength descent tie-break

---------

* no-mistakes(review): Blind ancestry in stale gemini/rovo marker-precedence tests

* no-mistakes(document): Add missing equal-depth-tie test line to precedence evidence transcript

* no-mistakes(review): Fix stale/vacuous agy precedence test, add agy to precedence suite and docs

* no-mistakes(document): Fix stale kimi.md marker doc missed by ancestry-precedence fix

---------

Co-authored-by: NewAiCoder <170579485+NewAiCoder@users.noreply.github.com>
…rkers (kunchenguid#3944)

Claude Code's external-imports check (hasClaudeMdExternalIncludesApproved)
reads only the canonical git-root project entry in ~/.claude.json, which its
own worktree-to-primary-checkout canonicalization means is never the task
worktree fm-claude-trust.sh registered. The trust dialog kept working
previously only because its check has an ancestor-walk fallback that happens
to reach the worktree entry; the external-imports check has no such
fallback.

Verified by disassembling the installed claude binary and reproducing in an
isolated three-way tmux launch: identical flags registered only at the
worktree key still showed the external-imports dialog, and registering them
at the primary checkout key suppressed both dialogs.

fm-claude-trust.sh now registers all three flags on both the worktree entry
and the primary-checkout entry in one atomic write, and refuses when the
<project> argument is not itself a primary checkout (its own write target
would then be wrong). Extends the harness-adapters Claude reference and the
trust test suite.

Co-authored-by: NewAiCoder <170579485+NewAiCoder@users.noreply.github.com>
…nguid#4355)

The marker lifecycle (fm-wake-lib.sh _fm_recovery_marker_ack) leaves
state/.watcher-down behind in an acked:* state after a downtime episode
is handled. health_snapshot's presence check reported that as an open
gap on every later return, so a handled episode kept surfacing as a
false GAP forever.
…kunchenguid#4361)

* fix(update): rebind fm-procevent-when watches after a self-update

A self-update fast-forwards bin/ in place, changing an armed watch's
action executable bytes with no tampering involved. The watch's trust
binding was hashed at arm time, so the very next fire was refused as
not matching the registered binding and the watch died silently.

Add fm-procevent-when.sh rebind-all: it re-hashes and republishes the
trust binding for every watch whose action executable lives under
FM_ROOT, using the same spec/trust validation as an ordinary fire, and
leaves any watch whose action lives outside FM_ROOT untouched. Wire it
into fm-update.sh right after a successful fast-forward, for both the
primary home and any local secondmate home that advances.

* no-mistakes(review): Canonicalize FM_ROOT for rebind-all's containment check

* no-mistakes(document): Document fm-update.sh's automatic watch rebind and its verification evidence

* no-mistakes(lint): fix(tests): double-quote printf scripts to satisfy shellcheck SC2016

* no-mistakes(review): Reload trust binding from disk before firing to reach live pollers

* no-mistakes(review): Lock the fire-time trust reload against rebind_one's publish race

* no-mistakes(document): Document rebind-all's self-update guarantee and its two review-round test rows

---------

Co-authored-by: NewAiCoder <170579485+NewAiCoder@users.noreply.github.com>
Preserve curated memory, quality gates, local landing, agy support, and strict
ownership proofs while adopting upstream supervision and liveness fixes.
Apply the approved Pi supervision, run attribution, intent provenance, and
unreadable liveness policies.
…. Targeted behavior regressions, guarded live Herdr test, and full CI lint pass. Evidence: scratchpad-ci-repair/resume/repair-report.md. The required live-coverage section remains in PR #26. Outer executor must commit, publish, and confirm green remote checks
…ent failure by retrying the exact session-lock refusal after both concurrent attempts exit. Reproduced the CI failure locally; the complete test now passes without skips. Targeted lint passes, and the default-session tripwire remained intact. Production safeguards are unchanged. Remote CI confirmation remains with the outer executor
…y from unread interrupt input. Guarded Herdr 0.7.4/Bash 5.2 testing and targeted lint pass. The original CI failure did not reproduce locally; remote confirmation remains required. Evidence: scratchpad-ci-repair/control/repair-report.md
@BohnBawerick

Copy link
Copy Markdown
Owner Author

Closing during the 4 Oct 2026 fork cleanup. main now carries the current fork and upstream through 1f3e769 (#32), and this branch no longer merges cleanly into it. Its tip is kept as tag archive/fm/fm-upstream-reconcile, so nothing is lost.

@BohnBawerick
BohnBawerick deleted the fm/fm-upstream-reconcile branch October 4, 2026 02:01
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.