Skip to content

fix(bin): prevent routed secondmate work from stranding - #2848

Merged
kunchenguid merged 24 commits into
mainfrom
fm/fm-handoff-stranding-fix-r1
Aug 23, 2026
Merged

fix(bin): prevent routed secondmate work from stranding#2848
kunchenguid merged 24 commits into
mainfrom
fm/fm-handoff-stranding-fix-r1

Conversation

@kunchenguid

@kunchenguid kunchenguid commented Aug 23, 2026

Copy link
Copy Markdown
Owner

Intent

Fix routed-work STRANDING in firstmate shared supervision and hand-off core, referencing GitHub issue #2791. Address both coupled defects: (1) distinguish agent liveness from wake-loop and queue-consumption liveness for registered local second mates, so when the oldest unacknowledged foreign durable wake-queue row exceeds a bounded age, outer supervision emits exactly one durable, idempotent parent notification identifying the mate and row, remains quiet for empty or healthy queues, and detects age read-only without consuming or rewriting the foreign row; (2) make a routed backlog hand-off reliably wake the receiving home through its supported delivery path, or fail loudly and observably if it cannot, so routed work cannot silently strand until a manual nudge. Preserve the diagnosis boundary around wake-queue consumption and hand-off wake, excluding process-event application or re-arm behavior unless genuinely reproduced. Add behavioral regression tests under tests/ for both reproductions: stalled foreign queue notification exactly once and idempotently with byte-for-byte row preservation plus quiet empty/healthy cases, and routed hand-off waking the receiver or surfacing failure. Exercise executable interfaces and review affected supported harness/runtime surfaces. Deliver one no-mistakes PR, reference #2791, do not merge, and note that homes pick up the fix after merge plus self-update coordinated with the main firstmate.

What Changed

  • Detect aged local-secondmate wake-queue rows read-only and emit one durable, deduplicated parent notification while keeping empty and healthy queues quiet.
  • Durably track and reconcile local and remote backlog-handoff wakes, preserving delivered work and surfacing missing, failed, or unresolved receiver delivery instead of silently succeeding.
  • Add lifecycle-safe wake-state teardown, regression coverage, and documentation for the corrected handoff and supervision behavior in A live second mate can silently stop consuming its durable wake queue #2791.

Risk Assessment

✅ Low: The durable wake, stall supervision, handoff recovery, and teardown staging paths preserve the required invariants with no remaining source-verifiable defect found.

Testing

Targeted local and remote handoff, wake-queue stall supervision, crash recovery, retry, concurrency, teardown, and pending-reply reconciliation checks passed; end-user transcripts demonstrate one durable stall notification with byte-identical foreign rows and a routed item persisted and woken exactly once through the supported tmux endpoint.

Evidence: Stalled foreign queue supervision transcript

Source: Stalled foreign queue supervision transcript

--- parent-visible stall notification ---
check: secondmate wake-loop stalled: mate=mate row=7 age=10s
--- foreign row before/after SHA-256 (must match) ---
4f97967a93682bbc22356d82bd187651c339d978906539c26915490bf64cbb22  /var/folders/0k/bf8mwt2n5qddzk24r20gfk0c0000gn/T//fm-wake-tests.ZN4rFT/secondmate-foreign-stall/foreign-before
4f97967a93682bbc22356d82bd187651c339d978906539c26915490bf64cbb22  /var/folders/0k/bf8mwt2n5qddzk24r20gfk0c0000gn/T//fm-wake-tests.ZN4rFT/secondmate-foreign-stall/foreign-after
--- empty queue checkpoint output (must contain no stall) ---
check: rearm-resurface
--- healthy queue checkpoint output (must contain no stall) ---
check: rearm-resurface
ok - foreign secondmate queue stalls notify once, remain byte-stable, and stay quiet when empty or healthy
Evidence: Routed handoff and receiver wake transcript

Source: Routed handoff and receiver wake transcript

--- handoff CLI response ---
handed off 1 item(s) to design: wake-item
  into /private/var/folders/0k/bf8mwt2n5qddzk24r20gfk0c0000gn/T/fm-backlog-handoff.rhlKJ7/live-wake-sub/data/backlog.md
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
●  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 a missing or failed watcher cycle with the Pi tool fm_watch_arm_pi, or restart Pi with -e /Users/kunchen/.no-mistakes/worktrees/016d88035d58/01M0Q2GKJAPCVZS6Q7RQADKM5M/.pi/extensions/fm-primary-turnend-guard.ts -e /Users/kunchen/.no-mistakes/worktrees/016d88035d58/01M0Q2GKJAPCVZS6Q7RQADKM5M/.pi/extensions/fm-primary-pi-watch.ts if the extensions are not loaded.
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
--- receiving home durable backlog ---
## Queued
- [ ] wake-item - routed to a live receiver (repo: alpha)

## Done
## In flight
--- supported tmux endpoint received wake ---
send-keys -t firstmate:fm-design -l [fm-from-firstmate]⁣corr=6efd57a321fb63f4 New routed work is in your backlog. Run bin/fm-session-start.sh now, then act on the routed task.
wake submissions after idempotent rerun: 1
ok - a routed handoff wakes once and a successful rerun stays idempotent

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 1 issue found → auto-fixed ✅
  • ⚠️ bin/fm-teardown.sh:2653 - The home is irreversibly removed before wake retirement. If handoff_wake_retire then fails, teardown preserves the registry route and metadata while its home no longer exists; the advertised retry fails earlier at home validation and can never finish cleanup. Make home removal and wake retirement recoverable as one teardown transition, such as staging wake state for rollback before removing the home.

🔧 Fix: Make local wake retirement recoverable
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • Inspected the target diff against base commit 801c0838fdb19823b6d215f6ad15522ee7c3c007.
  • bash tests/fm-wake-queue.test.sh
  • bash tests/fm-backlog-handoff.test.sh
  • bash tests/fm-remote-backlog-handoff.test.sh
  • bash tests/fm-pending-reply.test.sh
  • Executed evidence-only focused harnesses exercising fm-watch-checkpoint.sh, fm-wake-drain.sh, and fm-backlog-handoff.sh through their executable interfaces.
  • git status --short confirmed testing left the worktree clean.
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@greptile-apps

greptile-apps Bot commented Aug 23, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

The PR appears safe to merge because the previously reported handoff and teardown failures no longer remain at the current head.

No blocking failure remains.

Reviews (7): Last reviewed commit: "no-mistakes(document): Clarify handoff r..." | Re-trigger Greptile

Comment thread bin/fm-backlog-handoff.sh Outdated
Comment thread bin/fm-backlog-handoff.sh
@kunchenguid kunchenguid changed the title fix(bin): prevent routed secondmate work from stranding fix: prevent routed work from stranding Aug 23, 2026
Comment thread bin/fm-teardown.sh Outdated
@kunchenguid
kunchenguid force-pushed the fm/fm-handoff-stranding-fix-r1 branch from ec352be to 8727e97 Compare August 23, 2026 10:38
@kunchenguid kunchenguid changed the title fix: prevent routed work from stranding fix(bin): prevent routed secondmate work from stranding Aug 23, 2026
@kunchenguid
kunchenguid merged commit 52f62ab into main Aug 23, 2026
15 checks passed
@kunchenguid
kunchenguid deleted the fm/fm-handoff-stranding-fix-r1 branch August 23, 2026 18:54
Vinosaamaa added a commit to Vinosaamaa/firstmate that referenced this pull request Aug 24, 2026
* fix(composer): stop a blocked pi pane from proving an empty composer (kunchenguid#2811)

A pi worker parked on an interactive prompt - a permission dialog, a
question menu, a trust dialog - reports agent_status=blocked, because it
is waiting on a human keystroke. Pi draws that menu above its separator
pair, so the composer region between the rules is blank and structure
alone looks like a free composer. _fm_composer_pi_verdict admitted
blocked alongside idle and done, so the shared classifier reported an
affirmatively empty composer for exactly the pane where typing is unsafe.

Every "is it safe to type here?" consumer reads that verdict and proceeds
only on an affirmative empty, so both are told yes on a parked prompt:
the away-mode injection guard in bin/fm-supervise-daemon.sh, and fm-send's
pre-type refusal. The keys then answer the menu instead of composing a
message - the highlighted default is selected, the text is discarded, and
the record attributes a decision to a human who never made it.

blocked now defers to unknown, which every consumer already treats as
fail-closed. idle and done still prove an empty composer, so ordinary
steering is unchanged, and Cursor is unaffected because its always-blocked
panes never reach this pi-only branch.

Regression coverage lands first at both levels: the verdict owner
(a blocked pi defers) and the herdr adapter (a parked pi prompt is not an
empty composer).

* fix(bin): require project clone roots during fleet sync (kunchenguid#2849)

* fix(bin): require a clone root before fleet-sync touches a project

Git repository discovery walks upward, so `git -C projects/<dir>` on a plain
directory nested under projects/ resolves to the enclosing repository - in a
firstmate home, the firstmate checkout itself. fm-fleet-sync.sh guarded its
candidates with `rev-parse --is-inside-work-tree`, which such a directory
passes, so every later git call read, pruned and fast-forwarded firstmate's own
default branch and reported it under the project directory's label. A running
session's AGENTS.md changed underneath it, and the report named a project that
had nothing to do with the change.

Require each candidate to be the root of its own work tree before any other git
command: compare `rev-parse --show-toplevel` against the directory's own
physical path. Both sides are physical, so a symlinked clone still compares
equal. Anything else is skipped by name, naming the repository that would have
been touched, and bootstrap relays that as a FLEET_SYNC line.

Regression coverage reproduces the wrong-repo fast-forward against a home nested
inside another repository, in both the whole-fleet and single-project forms, and
pins that a symlinked clone dir still syncs.

* no-mistakes(review): Keep enclosing fixture clean during clone-root regression

* fix(bin): retry transient Lavish poll interruptions (kunchenguid#2846)

* fix(procevent): retry a transient Lavish poll interruption quietly

A live Lavish listener can be cut short by the server with exactly

    error: Lavish Editor poll response was interrupted
    code: SERVER_ERROR

while the session's marks remain available. Firstmate registered raw
`lavish-axi poll` output, so the generic process-event runner captured
that transient response as a result and woke the whole fleet over what is
really an internal retry.

The Lavish adapter now registers its own listener command, which reruns
the published blocking poll up to 12 times at 5 second intervals for that
one exact two-line response. The match is deliberately narrow: real
feedback, ended and missing sessions, any other SERVER_ERROR, and the same
interruption still standing once the bound is spent all pass straight
through and are captured and announced as before. The retry is a Lavish
fact, so the generic runner stays adapter-agnostic.

`FM_LAVISH_POLL_RETRY_DELAY` is a bounded 0 to 60 second override for the
interval only, refused rather than rounded when malformed, so a test can
exercise the real bound without waiting it out.

* no-mistakes(review): Harden Lavish retry matching, validation, and cleanup

* no-mistakes(review): Bound Lavish retry staging and stabilize regression

* no-mistakes(document): docs: explain Lavish retry adoption

* no-mistakes(lint): Restore Lavish trap ShellCheck suppression

* fix(brief): stop the documented {TASK} fill from corrupting the Herdr gate (kunchenguid#2838)

The unguarded Herdr declaration quoted `{TASK}` in its own prose while the
scaffold instructs firstmate to replace every `{TASK}` placeholder. The
documented global replace therefore spliced the whole task body into the
middle of the safety gate's sentence, silently destroying the one contract
that exists precisely because the scaffold cannot inspect the task text.

Reword the gate to refer to the task text filled in above, leaving the
placeholder only at its genuine fill site. Rewording rather than renaming the
token keeps the unfilled-charter guards in fm-home-seed.sh and
fm-remote-home-seed.sh working unchanged.

Add a regression test that performs the documented global fill on ship and
scout scaffolds and asserts the body lands once and the gate survives.

* fix(bin): resolve the busy-state lock mtime with the platform's own stat form (kunchenguid#2837)

The writer lock's stale-lock branch read the lock's mtime with
`stat -f %m ... || stat -c %Y ...`. On GNU coreutils `-f` is filesystem
stat, so it consumed the format string as a path, complained on stderr,
printed a partial filesystem dump ("  File: ...") on stdout, and still
exited 0. The GNU form in the fallback therefore never ran, and the
following arithmetic evaluated the word `File`, aborting the writer under
`set -u` with "File: unbound variable".

fm-teardown.sh died there after returning the worktree, leaving
state/<id>.meta, .status, .busy-gen, .busy-state, .busy-state.lock/ and
.turn-ended behind. The surviving metadata kept the watcher monitoring an
endpoint whose agent was gone, so a finished task produced stale wakes
forever, and every re-run died identically because the abandoned lock was
never broken.

Detect the platform once and pick the right stat form, the pattern
bin/fm-watch.sh already documents, and treat any non-numeric result as
"just created" so a future portability surprise degrades to a lock-timeout
refusal rather than killing teardown mid-way.

* fix(stow): add opt-in pass horizon for memory decay (kunchenguid#2850)

* fix(stow): give memory decay a per-pass horizon so the clock fires

The tiered decay clocks were wall-clock only, while admission is per-pass:
each /stow admits the findings that pass produced. In a home that stows
daily those two rates diverge by the stow cadence, an entry the fleet keeps
exercising never reaches 30 days unreinforced, and memory only grows while
the pass reports decay evaluated.

Give each dated marker an optional unreinforced-pass counter and make both
tiers stale at whichever horizon comes first: 10 passes or 30 days for
aging, 3 passes or 7 days for perishable. Reinforcement clears the counter
and nothing else does, so the existing evidence-based restamp rule stays
the only way an entry renews its lease. An absent /N means zero, so entries
that stay exercised carry no extra marker bytes, and a rarely stowed home
keeps its current behaviour through the unchanged date horizon.

* no-mistakes(document): Align stow workflow with dual decay clocks

* fix(stow): make the per-pass decay horizon opt-in

The unreinforced-pass horizon shipped as a new default archival cadence,
which is a product default rather than a restoration of the existing
wall-clock contract. Keep the 30-day and 7-day horizons as the only
default clock, and put the 10-pass and 3-pass horizons behind an explicit
opt-in: config/stow-pass-horizon for the firstmate home, and the file's
own header pointer for the public skill.

With the opt-in absent no counter is written and no counter is read, so a
home that does not ask for it decays exactly as it does today.

* no-mistakes(review): Preserve frozen counters and correct archive provenance

* test(watcher): stop fixture confirmation budgets racing real child startup (kunchenguid#2876)

tests/fm-watcher-lock.test.sh passed in isolation but failed intermittently
under full-suite and ambient concurrent load. bin/fm-watch-arm.sh computes its
confirmation deadline immediately after forking the real child watcher, so the
child's entire fork, exec, lock acquisition and beacon publication has to land
inside that wall clock. Two cases shrank that budget to one second, leaving a
two-second window for work measured at 3.1-4.9s under CPU oversubscription, so
the arm honestly reported "FAILED - no live watcher with a fresh beacon" and
their premises collapsed. A third case ran on the production budget, but its
child must also execute a registered check before exiting: measured at 1.9-2.3s
idle and 9.1-13.1s under load, against an 11s budget.

The two cases that must confirm a real child now hold the arm to production's
own budget instead of a shrunken fixture one, the immediate-wake case gets an
explicit budget with headroom over its measured loaded cost, and the two waits
for the arm's typed failure are sized off the largest production default rather
than a fixed eight seconds.

No bin/ change and no default behavior change: the lock's fail-closed semantics,
SIGSTOP handling, stale-heartbeat detection and the arm's typed failures are
untouched. Verified 4/4 green at 3x CPU oversubscription (loadavg 75-80) after
3/3 red before the change, and CONTRIBUTING.md records the convention.

* fix(bin): deterministically order remote tool paths (kunchenguid#2870)

* fix(bin): order discovered tool installs by the shell's own expansion

fm_remote_job_compose_operator_path built the asdf and mise install
directories with `compgen -G`, which does not sort. Bash sorts glob
matches in pathexp.c, on the shell's own pathname-expansion path only;
`compgen -G` reaches the same glob_filename through pcomplete.c, which
sorts nothing. On bash 3.2 (macOS /bin/bash) and every bash before 5.3
that handed the composition raw readdir order, so which install of a
multi-version tool a remote job resolved was decided by directory order
on disk rather than by this composition.

Expand the globs at the call sites and let the function take the matches,
so the composition and the documented portable-PATH contract are the same
operation. Quoting the account home at the call site also stops a home
whose name contains glob metacharacters from being reinterpreted.

The colocated regression pins both the order and the mechanism: bash 5.3
moved sorting into the glob library, so an order-only assertion cannot
see the defect there.

* no-mistakes(review): Remove source-reading PATH regression guard

* fix(bin): prevent routed secondmate work from stranding (kunchenguid#2848)

* fix: surface stalled secondmate queues and wake handoffs

* no-mistakes(review): Make handoff wakes retryable and stall alerts crash-safe

* no-mistakes(review): Prevent duplicate handoff wakes and cover remote delivery

* no-mistakes(review): Serialize local handoffs and preserve pre-move wake intent

* no-mistakes(review): Serialize teardown with handoffs and retain remote wake confirmation

* no-mistakes(review): Reconcile correlated handoff wake delivery after crashes

* no-mistakes(review): Keep failed wakes retryable and isolate stall receipts

* no-mistakes(review): Reset known-undelivered wake attempts for durable retries

* no-mistakes(review): Refuse duplicate sends for unresolved delivery attempts

* no-mistakes(review): Atomically restore retryability after reconciled send failures

* no-mistakes(review): Serialize delivery confirmation with reconciliation

* no-mistakes(document): Document routed wake and stall supervision

* no-mistakes(lint): Fix ShellCheck expansion and subshell warnings

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes(review): Retire stale wake state and defer pre-move wakes

* no-mistakes(review): Secure markers, bind batches, and preserve teardown routes

* no-mistakes(review): Preserve unresolved prepared wakes across unrelated handoffs

* no-mistakes(review): Preserve prepared wakes before unrelated moving handoffs

* no-mistakes(document): Document prepared wake batch ownership

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes(review): Make local wake retirement recoverable

* no-mistakes(document): Clarify handoff recovery and teardown documentation

* fix: make macOS inbox test path portable (kunchenguid#2857)

* feat(bin): deliver local steers through durable task inboxes (kunchenguid#2856)

* feat(bin): steer local tasks by durable inbox record plus constant doorbell

Stage 1 (local steers) of the captain-adopted reframe in
data/fm-send-reliability-reframe-s1/report.md: an ordinary fm-send text
steer to a task recorded in this home is appended as a sequenced durable
record under state/<id>.inbox/ and the terminal receives only one constant
self-describing doorbell line, best-effort. The worker acknowledges by
moving the record into handled/; the watcher re-rings an unacknowledged
message on an idle pane and escalates once as an ordinary stale wake.
--resolve-key closes decisions at enqueue time, because the durable
enqueue IS delivery to the task's record. bin/fm-task-inbox-lib.sh owns
the record format, doorbell line, and re-ring ladder.

The typed plane remains for what must reach the terminal itself:
lifecycle keys, harness-native slash and codex $-skill invocations,
explicit backend targets, and the remote secondmate leg (unchanged until
the remote inbox leg ships separately). The composer classifier is
demoted from delivery proof to an advisory ring guard that skips only on
a proven pending verdict.

Verified live against claude, codex, opencode, pi, grok, and muse: each
real worker read its record, acted, and acked with the mv
(docs/verification/runtime-backends.md "Steering-inbox doorbell").

* docs(verification): flag the grok 1.0.5 composer-matrix staleness observed by the doorbell run

* test(captain-hold): read the chat-channel answer from the durable inbox record

* test: migrate fm-control's marker contrast to the inbox record and fix macOS wc padding in the tool-update suite

* no-mistakes(review): Harden inbox locking, teardown races, and acknowledgements

* no-mistakes(review): Serialize watcher actions with inbox acknowledgements

* no-mistakes(review): Bound metadata locking and tighten acknowledgement rechecks

* no-mistakes(review): Preserve exact inbox bytes and harden delivery recovery

* no-mistakes(review): Harden watcher bookkeeping against concurrent inbox teardown

* no-mistakes(document): Update inbox and typed-plane documentation

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* revert(pipeline): keep parser-native secondmate marking and the both-failed exit out of stage 1

The CI monitor's fix changed the secondmate marking contract for
parser-native invocations (appending the marker after the text) and
softened the both-commit-and-marker-failed branch to exit 0. The merge
authority ruled the marking question out of scope for this stage-1
transport PR (follow-up: fm-send-secondmate-harness-invocation-r1) and
ruled the both-failed case a loud nonzero local failure. Restore both,
keeping the monitor's legitimate migrations and hardening.

* no-mistakes(document): Document inbox and typed-plane boundaries

* no-mistakes(document): Scope backend transport docs to typed plane

* no-mistakes(document): Clarify inbox attempt-budget documentation

* no-mistakes: apply CI fixes

* fix(send): the durable record alone governs the inbox exit status

Captain-refined ruling on the F2/Greptile finding: the durable inbox
record is what delivers the steer, so pending-reply bookkeeping trouble
after a successful enqueue never exits nonzero - a resend-inviting status
would make automated callers enqueue the delivered instruction again
under a new sequence. With the recovery marker stored the watcher
reconciles silently; with the commit and marker both lost the send
surfaces a distinct reply-tracking-degraded do-not-resend warning and
still exits 0. Nonzero remains only where nothing was delivered (or a
decision close needs its manual command). Regression: record durable +
both bookkeeping writes lost -> exit 0, one record, no duplicate.

* no-mistakes(review): Preserve inbox ordering with drain-all doorbells

* no-mistakes(review): Surface unwritable inbox ladder bookkeeping

* no-mistakes(review): Silence ladder failures after inbox acknowledgement

* no-mistakes(document): Update steering inbox documentation

* no-mistakes: apply CI fixes

* feat(bin): add fast local lint mode (kunchenguid#2891)

* feat: add fast local lint mode

* fix: preserve complete fm-lint help

* fix: isolate fast lint mode

* no-mistakes(document): Clarify lint mode documentation ownership

* no-mistakes: apply CI fixes

* feat(bin): deliver remote steers through durable inboxes (kunchenguid#2901)

* feat(bin): deliver remote secondmate steers through durable task inboxes

Stage 2 of the inbox+doorbell steer channel (stage 1: kunchenguid#2856). A remote
secondmate steer now crosses fm-on.sh as a durable record written
idempotently into the remote home's steering inbox plus a best-effort
remote doorbell, and the last typed-payload steer transport is deleted:

- fm-remote-secondmate-control.sh cmd_send writes the record via the new
  fm_task_inbox_write_idempotent and rings the doorbell; it no longer
  types the payload through an inner fm-send at an explicit pane target.
- fm-send.sh routes every remote text steer (harness-native included,
  which marking already reduced to chat) onto the remote inbox leg,
  retries the identical leg once on ssh 255, closes --resolve-key
  decisions at enqueue for remote too, and preserves a marked request's
  reply expectation when completion stays unknown. The exit-3-as-
  delivered remap, the 255 do-not-resend trap, and the remote typed
  submit block are removed.
- fm-task-inbox-lib.sh owns the idempotent enqueue: an exact-body re-run
  lands on the existing record, handled or not, so an ambiguous
  transport can always be safely re-run.
- Tests pin the new contract end to end (record + doorbell + no typed
  payload across ssh, one-record idempotence under an ambiguous
  transport, enqueue-time decision close, loud real failures, and the
  deleted typed-payload behaviors gone), and AGENTS.md plus
  docs/remote-secondmates.md describe the remote leg's new semantics.

* no-mistakes(review): Harden remote inbox delivery against lifecycle races

* no-mistakes(review): Enable correlation-preserving remote steer resends

* no-mistakes(review): Fail closed on stale correlation resends

* no-mistakes(review): Include home context in remote resend commands

* no-mistakes(review): Lock and revalidate remote parent routes

* no-mistakes(document): Clarify remote steer retry documentation

* no-mistakes: apply CI fixes

* feat: add persistent Pi supervision branch (kunchenguid#2858)

* wip: forked supervision on Pi (checkpoint before docs)

* fix(pi-branch): harden mirror delivery, fallback encoding, and session replacement

Peek-then-shift mirror flush so a failed append retries instead of dropping;
durable mirror cursor commits only after delivery into the branch;
the main fallback wake is operational-encoded like every watcher injection;
session_shutdown quiesces the generation and session_start re-arms, so /new
and /resume no longer kill the branch permanently. Registers the extension in
the strict typecheck, adds the dispatch handshake test, the branch extension
suite, the bash-level regression suite, the session-start replay test, and
the opt-in real-SDK live guard.

* test(fixtures): carry the branch-dispatch lib and lease lib into isolated fixtures

The watcher extension now imports lib/fm-branch-dispatch.ts and fm-teardown
sources fm-lease-lib.sh, so every fixture that copies or symlinks those
files in isolation gains the new sibling.

* no-mistakes(review): Prevent shutdown wake loss and serialize lease claims

* no-mistakes(review): Durably hand off wakes and retain portable leases

* no-mistakes(review): Require durable reports and clear disposed branch leases

* no-mistakes(review): Enforce per-wake outcomes and quiescent lease cleanup

* no-mistakes(review): Require wake acknowledgements and tighten branch lifecycle boundaries

* no-mistakes(review): Require complete acknowledgements and replay cleanup failures

* no-mistakes(review): Bind supervision to lock ownership and durable delivery

* no-mistakes(review): Activate branch lazily after session lock acquisition

* no-mistakes(review): Preserve undelivered mirror context across extension rebinds

* no-mistakes(review): Acknowledge startup replay only after main delivery

* no-mistakes(review): Isolate replay metadata from untrusted digest content

* no-mistakes(review): Reject duplicate reports for active wake sequences

* no-mistakes(review): Retain failed fallbacks and deduplicate outcome replay

* no-mistakes(review): Deduplicate durable outcomes and cache delivery receipts

* no-mistakes(review): Anchor wake sequence matching to outcome fields

* no-mistakes(document): Clarify Pi supervision durability contracts

* no-mistakes(lint): Fix ShellCheck issues in branch supervision scripts

* 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

* refactor(pi-branch): collapse to confused-agent-grade guards per captain decision

Captain decision A: the lease/actor guards target the CONFUSED-AGENT threat
model bin/fm-gate-refuse-lib.sh already documents; adversarial-grade
separation is impossible in the shared-process design and is filed as
separate follow-up work. Rip out the machinery that chased it: the
generation fence and shell-provenance markers, the wrapper-tagged ancestry
walks, guard auto-claim with per-script release traps, the pending-wake
files and ack-receipt correlation (the durable wake queue already
re-presents anything unacknowledged), the delivery-receipt store with
contiguous cursor advancement, the session-start replay-metadata channel,
and the branch tool quiescence counters.

Keep the behaviors the board requires, each on its simplest implementation:
lazy per-action session-lock ownership (cold start activates after the lock
lands; a secondary session stays inert), mirror durability across extension
rebinds via the durable cursor, replay-exactly-once from the one read
cursor, the awaited operational-encoded fallback, per-generation stray-lease
cleanup, session-lock-bound lease liveness (a recycled pid or a non-Pi home
never honors a leftover lease), the loud accidental-override guards
(readonly actor prelude, cross-actor claim refusal), and the role-partition
refinements (no forced teardown, no direct relaunch for the branch).
Default-on-for-Pi is unchanged.

* no-mistakes(review): Enforce lock ownership and serialize lease mutations

* no-mistakes(review): Synchronize guard cleanup and bind leases to lock owner

* no-mistakes(review): Report outcomes before acknowledging durable wakes

* no-mistakes(review): Restrict leases to Pi and instruct main claims

* no-mistakes(review): Reject malformed lease locks and torn outcome tails

* no-mistakes(review): Validate complete outcome tails before appending

* no-mistakes(review): Guard branch side effects across session replacements

* no-mistakes(document): Update Pi supervision durability and lease documentation

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

* no-mistakes: apply CI fixes

* fix(pi-branch): authorize lease releases by caller

* fix(lint): break redundant source-analysis path in fm-lease-lib.sh

fm-lease-lib.sh's lazy fallback source of fm-wake-lib.sh gave ShellCheck's
--external-sources traversal a second path into an already 1540-line file
that fm-send.sh and fm-teardown.sh also source directly, blowing up the
recursive analysis past CI's lint timeout. Mark it a source=/dev/null
analysis boundary, matching the existing fm-task-inbox-lib.sh convention.

Also restores bin/fm-lint.sh and tests/fm-lint.test.sh to the shared
serial-lint definition (dropping an unrelated parallel-sharding change
that was itself hanging and masked this root cause).

* no-mistakes(document): Correct lease caller-authorization documentation

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* fix(bin): parallelize startup network sweeps (kunchenguid#2927)

* feat(bin): parallelize session-start remote secondmate network sweeps

Run per-secondmate liveness and convergence probes concurrently and overlap clone refresh, while replaying each mate's fail-closed diagnostic in original order. Ignore scratchpad* so untracked scratch no longer blocks remote sync.

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

* no-mistakes(document): Document parallel startup network sweeps

* no-mistakes(lint): Fix empty environment assignment lint warning

* no-mistakes: apply CI fixes

---------

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

* test: handle absent watcher wake queues (kunchenguid#2845)

* fix(tests): count declared-pause wakes without crashing on an absent queue

The exited-declared-pause case counts queued stale wakes by handing
state/.wake-queue straight to awk. A watcher that queues nothing never
creates that file, and awk aborts on a missing path before its END rule
runs, so the count collapses to the empty string. The next comparison
then fails as an integer-expression error and surfaces as a wake flood
with no number, hiding the real contract breach the following grep names.

Read the queue the way the drain-count assertion at the end of this file
already does: silence awk's open error and default an absent queue to
zero. Applied to all four counts in this case, including the live
external-decision gate pair whose queue an acknowledged drain can also
leave behind. An absent queue now reports "did not use the bounded
paused recheck", while a genuine flood still fails with its real count.

Fixes kunchenguid#2628

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

---------

Co-authored-by: Christopher McKay <101884182+karotkriss@users.noreply.github.com>
Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: wenkxu <v-wenkxu@expediagroup.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant