Skip to content

fix(pi): coalesce ordinary watcher wake rows in the follow-up dock - #3472

Open
npayette84 wants to merge 8 commits into
kunchenguid:mainfrom
npayette84:fm/fm-pi-followup-queue-coalescing
Open

fix(pi): coalesce ordinary watcher wake rows in the follow-up dock#3472
npayette84 wants to merge 8 commits into
kunchenguid:mainfrom
npayette84:fm/fm-pi-followup-queue-coalescing

Conversation

@npayette84

Copy link
Copy Markdown
Contributor

Intent

Implement the captain-approved corrected Pi Follow-up wake presentation coalescing patch identified by a judge report that rejected two prior candidate branches, shipping it as a fresh change on current upstream/main.

Background the reviewer needs: Pi's fixed follow-up dock gained one queued row per ordinary watcher notification, so a burst of actionable watcher closes during one long handling turn could stack more than sixty rows and bury the conversation, even though every event was already durable in the wake queue. Two earlier candidate branches each solved part of this and each failed a required criterion, so neither was promoted; this change is deliberately the union of what each got right, roughly 30 functional extension lines plus regressions, and NOT a port of either candidate wholesale.

Required behavior, all accepted and implemented:

  • At most one ordinary work-waiting Pi Follow-up row is pending while an earlier ordinary wake prompt remains pending.
  • Every durable wake record remains untouched and authoritative; only presentation coalesces.
  • The per-generation ordinary latch is set BEFORE awaiting pi.sendUserMessage and cleared on the consumption edge (agent_start), so an idle-consumed wake cannot strand the latch. This ordering is the decisive correctness point: in real Pi 0.84.4, sendUserMessage on an idle agent runs the whole handling turn inside the call and emits agent_start before the returned promise resolves, so a latch set AFTER the await strands itself true with no pending row and silently suppresses every later ordinary wake. Candidate A had that defect. Do not "simplify" the set-before-await ordering or the try/catch around the send.
  • Immediate separate presentation is preserved for every supervision failure, including continuity-restoration exhaustion and restoration-time lock loss, which travel through the plain actionable-wake delivery branch. Candidate B classified urgency by call site only and therefore suppressed exactly those two typed failures behind a pending ordinary row.
  • Urgency is therefore deliberately belt-and-braces, as the captain required: candidate A's content-aware watcher: FAILED classification IS combined with candidate B's explicit "urgent" call-site marks. The call-site marks are currently redundant with the content classifier for every existing failure text; that redundancy is intentional defence in depth so a future failure path that forgets the marker still surfaces. Do not collapse the WakePresentation parameter into the content check, and do not weaken urgent failure visibility.
  • The ordinary latch rolls back if sendUserMessage rejects before a row is established, then the error propagates so the existing catch reports it as a failure.
  • Generation replacement, ownership, successor restoration, and no-redundant-arm behavior are preserved unchanged.

Known and accepted residual, documented in the extension header, not a defect to fix: Pi exposes no per-row consumption signal, so a captain prompt that starts a run while a row is still docked clears the latch early and a close during that run can dock a second row. That transient is bounded at two rows, both consumed in order, and is the accepted cost of using agent_start as the consumption edge.

Regression evidence required and delivered, all through the real extension boundary (no toy reimplementation): a production-timing fake sendUserMessage where an idle send fires agent_start inside the call and resolves only after the driver-controlled run completes, proving a genuinely later idle wake presents exactly one new row; a busy-agent case where one ordinary row is already docked and a second close hits restoration exhaustion, and a second where it hits restoration-time lock loss, proving each typed failure still presents separately; plus burst, re-arm, later-event, generation replacement, durable-record preservation, and delivery-rejection rollback coverage. Both prior candidates' test suites faked sendUserMessage as instantly resolving, which is exactly what hid their defects, so the production-timing fake is the point of these tests and must not be simplified back to an instant-resolve fake.

Every new test was additionally run against six deliberately broken variants of the extension to prove none is vacuous: unmodified upstream fails only the burst test; latch-after-await fails only the idle-consumed re-arm test; call-site-only urgency fails both restoration-failure tests; no rejection rollback fails only the rejected-delivery test; a latch shared across generations fails only the session-replacement test; and removing urgency entirely fails all four failure-bypass tests.

Scope decisions the reviewer should not second-guess:

  • Delivery target is current upstream/main of kunchenguid/firstmate. The branch was rebuilt on that base on purpose; no unrelated fork commits are bundled.
  • The presentation contract is owned once in the Pi watcher extension header, with docs/watcher-continuity.md carrying only the operator-facing guarantee plus a pointer, matching that file's existing generation-owner convention. This deliberately avoids the one-owner violation of stating the full contract twice.
  • A dated maintainer-verification record was added to docs/verification/supervision.md stating only what was actually observed. It explicitly records that tests/fm-pi-primary-types.test.sh reported "skip: tsc not found", and it corrects a false consumption-edge guarantee that one rejected candidate had claimed. Do not restore or strengthen that claim.
  • The record also reviews applicability across every supported primary harness and runtime backend after inspecting each integration surface: pi and pi-signed load the same tracked extension and are changed; OpenCode has a structurally comparable queued prompt but no accumulating fixed dock and is deliberately unchanged; Claude and Cursor run the watcher only between turns; Codex takes its wake as the return of the one foreground checkpoint it is blocked on; Grok, Kimi, Muse and other persistent-model harnesses surface a wake through a single arm return; and every runtime backend is unaffected because the change touches no spawn, endpoint, or task-metadata path.
  • One line was added to CONTRIBUTING.md recording a verified repo hazard hit while writing these tests: a heredoc body inside a command substitution must avoid apostrophes because bash 3.2, the system bash on macOS, still syntax-scans those lines. This was empirically confirmed, not assumed.
  • No wrapper machinery, no control plane, and no new abstraction was added. AGENTS.md is intentionally untouched.

Verified green before submission: tests/fm-pi-watch-extension.test.sh (45 tests), tests/fm-watch-arm.test.sh (14), tests/fm-watch-recovery-loop.test.sh (2), tests/fm-pi-branch-extension.test.sh (31), tests/fm-calm-pi-extension.test.sh (9), tests/fm-turnend-guard.test.sh (70), CI=true bin/fm-lint.sh under pinned ShellCheck 0.11.0 and actionlint 1.7.12, and bin/fm-doc-audience-check.sh.

The PR must target current upstream main and stop green for captain review; do not merge it.

What Changed

  • .pi/extensions/fm-primary-pi-watch.ts now tracks a per-generation pendingOrdinaryWakeRow latch: an ordinary watcher wake docks a follow-up row only when no earlier ordinary row is pending. The latch is set before calling pi.sendUserMessage (which is void on Pi 0.84.4's ExtensionAPI, so nothing can be awaited or observed) and cleared on both agent_start and agent_settled, the two run boundaries that show a docked row was consumed or discarded. A replacement session activates a fresh generation with a clear latch. Delivery is untouched — every event is still enqueued durably by the watcher; only presentation coalesces.
  • Wake urgency is now decided two ways: sendWake takes an explicit WakePresentation mark from the call sites that own failure paths, and any message carrying the watcher: FAILED marker is treated as urgent regardless of the branch that delivered it. Typed continuity-restoration failures ride the ordinary delivery branch, so the content check keeps them from being coalesced behind a pending row.
  • tests/fm-pi-watch-extension.test.sh gains nine regressions driving the real extension through a follow-up fake that models Pi's actual void send surface and its run transitions — burst coalescing with durable-queue preservation, re-arm after idle-consumed, busy-path inline-drained, and discarded rows, four failure-bypass cases, and generation replacement. docs/watcher-continuity.md states the operator-facing guarantee and points at the extension header for the full contract; docs/verification/supervision.md records the dated verification run, the runtime surfaces it was read from, the broken-variant counterfactuals, the skip: tsc not found result, and the applicability review across primaries and runtime backends. CONTRIBUTING.md notes the bash 3.2 heredoc-in-command-substitution apostrophe hazard hit while writing these tests.

Risk Assessment

✅ Low: The functional change is ~30 lines confined to one Pi extension, its central invariant (both latch-clearing edges, set-before-send ordering, urgency backstop) checks out against the installed Pi 0.84.4 runtime, the nine new regressions are non-vacuous under hand-traced counterfactuals, and the only open items are a test tidiness nit and one intent divergence the captain already adjudicated with the runtime evidence in hand.

Testing

I ran the extension suite (tests/fm-pi-watch-extension.test.sh, 46 ok, exit 0) plus the three other suites that load the same tracked extension (primary-types, which skips with no tsc present, branch-extension 31, calm-extension 9, all exit 0), then went past pass/fail for product-level proof. Two demo drivers exercise the real extension through the real ExtensionAPI boundary with a void-returning sendUserMessage fake matching Pi 0.84.4, and their transcripts show the actual dock the captain would see: 20 rows on upstream 355f46f versus 1 row on this change for the same 20-close burst, with all 20 durable wake-queue records intact in both, and a second transcript where restoration exhaustion still surfaces its own watcher: FAILED row while the rejected call-site-only-urgency shape swallows it. Because tests/lib.sh exits on first failure, I built a per-test runner and ran each of the 9 new coalescing tests separately against 7 deliberately broken extension variants; every variant fails exactly the set the change's verification record claims, so none of the new tests is vacuous. I also confirmed the CONTRIBUTING hazard empirically on this machine's bash 3.2 and read the installed Pi 0.84.4 runtime to check the void-send claim the design rests on. No screenshots apply: the affected surface is a text follow-up dock inside a Pi session, so the captured transcripts are the rendered end-user output.

Evidence: Pi follow-up dock before/after: 20-close burst during one captain turn

Source: Pi follow-up dock before/after: 20-close burst during one captain turn

--- BEFORE: upstream main (355f46f) --- actionable watcher closes during the captain's turn : 20 durable wake-queue records (bin/fm-wake-drain.sh) : 20 Pi follow-up dock rows presented to the captain : 20 --- AFTER: this change (5a4babd) --- actionable watcher closes during the captain's turn : 20 durable wake-queue records (bin/fm-wake-drain.sh) : 20 Pi follow-up dock rows presented to the captain : 1 Pi follow-up dock as the captain would see it: [row 1] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate alpha finished, needs review Full text of dock row 1 (what the model actually receives): | FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate alpha finished, needs review | | Run bin/fm-wake-drain.sh first and handle the queued wake. Watcher continuity is extension-owned.

Pi follow-up dock under a burst of ordinary watcher closes during one long captain turn
Driven through the real tracked extension and the real ExtensionAPI boundary:
20 actionable secondmate closes arrive while the captain is mid-turn.

--- BEFORE: upstream main (355f46f) ---
actionable watcher closes during the captain's turn : 20
durable wake-queue records (bin/fm-wake-drain.sh)   : 20
Pi follow-up dock rows presented to the captain     : 20

Pi follow-up dock as the captain would see it:
  [row  1] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate alpha finished, needs review
  [row  2] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate 2 finished, needs review
  [row  3] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate 3 finished, needs review
  [row  4] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate 4 finished, needs review
  [row  5] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate 5 finished, needs review
  [row  6] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate 6 finished, needs review
  [row  7] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate 7 finished, needs review
  [row  8] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate 8 finished, needs review
  [row  9] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate 9 finished, needs review
  [row 10] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate 10 finished, needs review
  [row 11] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate 11 finished, needs review
  [row 12] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate 12 finished, needs review
  [row 13] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate 13 finished, needs review
  [row 14] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate 14 finished, needs review
  [row 15] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate 15 finished, needs review
  [row 16] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate 16 finished, needs review
  [row 17] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate 17 finished, needs review
  [row 18] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate 18 finished, needs review
  [row 19] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate 19 finished, needs review
  [row 20] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate 20 finished, needs review

Full text of dock row 1 (what the model actually receives):
  | FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate alpha finished, needs review
  | 
  | Run bin/fm-wake-drain.sh first and handle the queued wake. Watcher continuity is extension-owned.

Durable queue (untouched by presentation coalescing):
  1  1  signal  close-1  signal: secondmate alpha finished, needs review
  1  2  signal  close-2  signal: secondmate 2 finished, needs review
  1  3  signal  close-3  signal: secondmate 3 finished, needs review
  1  4  signal  close-4  signal: secondmate 4 finished, needs review
  1  5  signal  close-5  signal: secondmate 5 finished, needs review
  1  6  signal  close-6  signal: secondmate 6 finished, needs review
  1  7  signal  close-7  signal: secondmate 7 finished, needs review
  1  8  signal  close-8  signal: secondmate 8 finished, needs review
  1  9  signal  close-9  signal: secondmate 9 finished, needs review
  1  10  signal  close-10  signal: secondmate 10 finished, needs review
  1  11  signal  close-11  signal: secondmate 11 finished, needs review
  1  12  signal  close-12  signal: secondmate 12 finished, needs review
  1  13  signal  close-13  signal: secondmate 13 finished, needs review
  1  14  signal  close-14  signal: secondmate 14 finished, needs review
  1  15  signal  close-15  signal: secondmate 15 finished, needs review
  1  16  signal  close-16  signal: secondmate 16 finished, needs review
  1  17  signal  close-17  signal: secondmate 17 finished, needs review
  1  18  signal  close-18  signal: secondmate 18 finished, needs review
  1  19  signal  close-19  signal: secondmate 19 finished, needs review
  1  20  signal  close-20  signal: secondmate 20 finished, needs review

--- AFTER: this change (5a4babd) ---
actionable watcher closes during the captain's turn : 20
durable wake-queue records (bin/fm-wake-drain.sh)   : 20
Pi follow-up dock rows presented to the captain     : 1

Pi follow-up dock as the captain would see it:
  [row  1] FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate alpha finished, needs review

Full text of dock row 1 (what the model actually receives):
  | FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate alpha finished, needs review
  | 
  | Run bin/fm-wake-drain.sh first and handle the queued wake. Watcher continuity is extension-owned.

Durable queue (untouched by presentation coalescing):
  1  1  signal  close-1  signal: secondmate alpha finished, needs review
  1  2  signal  close-2  signal: secondmate 2 finished, needs review
  1  3  signal  close-3  signal: secondmate 3 finished, needs review
  1  4  signal  close-4  signal: secondmate 4 finished, needs review
  1  5  signal  close-5  signal: secondmate 5 finished, needs review
  1  6  signal  close-6  signal: secondmate 6 finished, needs review
  1  7  signal  close-7  signal: secondmate 7 finished, needs review
  1  8  signal  close-8  signal: secondmate 8 finished, needs review
  1  9  signal  close-9  signal: secondmate 9 finished, needs review
  1  10  signal  close-10  signal: secondmate 10 finished, needs review
  1  11  signal  close-11  signal: secondmate 11 finished, needs review
  1  12  signal  close-12  signal: secondmate 12 finished, needs review
  1  13  signal  close-13  signal: secondmate 13 finished, needs review
  1  14  signal  close-14  signal: secondmate 14 finished, needs review
  1  15  signal  close-15  signal: secondmate 15 finished, needs review
  1  16  signal  close-16  signal: secondmate 16 finished, needs review
  1  17  signal  close-17  signal: secondmate 17 finished, needs review
  1  18  signal  close-18  signal: secondmate 18 finished, needs review
  1  19  signal  close-19  signal: secondmate 19 finished, needs review
  1  20  signal  close-20  signal: secondmate 20 finished, needs review
Evidence: Supervision failure still presents its own row past a pending ordinary row

Source: Supervision failure still presents its own row past a pending ordinary row

--- AFTER: this change (5a4babd) --- Pi follow-up dock rows presented to the captain: 2 [row 1] | FIRSTMATE WATCHER WAKE: signal: secondmate alpha finished, needs review [row 2] | FIRSTMATE WATCHER WAKE: signal: secondmate bravo finished, needs review | watcher: FAILED - Pi extension could not verify a ready successor watcher | watcher: FAILED - Pi extension could not restore watcher continuity after 1 retries --- COUNTEREXAMPLE: call-site-only urgency (rejected candidate B shape) --- Pi follow-up dock rows presented to the captain: 1 *** no supervision-failure row reached the captain: the failure was coalesced away ***

Supervision-failure visibility while an ordinary work-waiting row is already docked

--- AFTER: this change (5a4babd) ---
scenario: one ordinary work-waiting row already docked, then a second close
          whose continuity restoration exhausts its retries
Pi follow-up dock rows presented to the captain: 2

  [row 1]
    | FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate alpha finished, needs review
    | 
    | Run bin/fm-wake-drain.sh first and handle the queued wake. Watcher continuity is extension-owned.
  [row 2]
    | FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate bravo finished, needs review
    | 
    | watcher: FAILED - Pi extension could not verify a ready successor watcher
    | watcher: FAILED - Pi extension could not restore watcher continuity after 1 retries
    | 
    | Run bin/fm-wake-drain.sh first and handle the queued wake. Watcher continuity is extension-owned.

--- COUNTEREXAMPLE: call-site-only urgency (rejected candidate B shape) ---
scenario: one ordinary work-waiting row already docked, then a second close
          whose continuity restoration exhausts its retries
Pi follow-up dock rows presented to the captain: 1

  [row 1]
    | FIRSTMATE_OP: v1 watcher: FIRSTMATE WATCHER WAKE: signal: secondmate alpha finished, needs review
    | 
    | Run bin/fm-wake-drain.sh first and handle the queued wake. Watcher continuity is extension-owned.

  *** no supervision-failure row reached the captain: the failure was coalesced away ***
Evidence: Non-vacuity matrix: 9 coalescing tests against 7 broken extension variants

Source: Non-vacuity matrix: 9 coalescing tests against 7 broken extension variants

v1-upstream -> FAIL burst only v2-latch-after-await -> FAIL idle-consumed re-arm only v3-callsite-only-urgency -> FAIL restoration exhaustion + restoration-time lock loss v4-shared-latch -> FAIL session replacement only v5-no-urgency -> FAIL all four failure-bypass tests v6-no-agent-start-clear -> FAIL idle-consumed re-arm only v7-no-agent-settled-clear -> FAIL busy-path re-arm + discarded-row re-arm

### variant: v1-upstream
FAIL test_pi_ordinary_burst_coalesces_to_one_dock_row
PASS test_pi_consumed_row_rearms_for_genuinely_later_wake
PASS test_pi_busy_consumed_row_rearms_for_genuinely_later_wake
PASS test_pi_cleared_dock_rearms_for_genuinely_later_wake
PASS test_pi_restoration_exhaustion_bypasses_ordinary_latch
PASS test_pi_restoration_lock_loss_bypasses_ordinary_latch
PASS test_pi_retry_lock_loss_bypasses_ordinary_latch
PASS test_pi_refused_handshake_bypasses_ordinary_latch
PASS test_pi_session_replacement_discards_ordinary_latch

### variant: v2-latch-after-await
PASS test_pi_ordinary_burst_coalesces_to_one_dock_row
FAIL test_pi_consumed_row_rearms_for_genuinely_later_wake
PASS test_pi_busy_consumed_row_rearms_for_genuinely_later_wake
PASS test_pi_cleared_dock_rearms_for_genuinely_later_wake
PASS test_pi_restoration_exhaustion_bypasses_ordinary_latch
PASS test_pi_restoration_lock_loss_bypasses_ordinary_latch
PASS test_pi_retry_lock_loss_bypasses_ordinary_latch
PASS test_pi_refused_handshake_bypasses_ordinary_latch
PASS test_pi_session_replacement_discards_ordinary_latch

### variant: v3-callsite-only-urgency
PASS test_pi_ordinary_burst_coalesces_to_one_dock_row
PASS test_pi_consumed_row_rearms_for_genuinely_later_wake
PASS test_pi_busy_consumed_row_rearms_for_genuinely_later_wake
PASS test_pi_cleared_dock_rearms_for_genuinely_later_wake
FAIL test_pi_restoration_exhaustion_bypasses_ordinary_latch
FAIL test_pi_restoration_lock_loss_bypasses_ordinary_latch
PASS test_pi_retry_lock_loss_bypasses_ordinary_latch
PASS test_pi_refused_handshake_bypasses_ordinary_latch
PASS test_pi_session_replacement_discards_ordinary_latch

### variant: v4-shared-latch
PASS test_pi_ordinary_burst_coalesces_to_one_dock_row
PASS test_pi_consumed_row_rearms_for_genuinely_later_wake
PASS test_pi_busy_consumed_row_rearms_for_genuinely_later_wake
PASS test_pi_cleared_dock_rearms_for_genuinely_later_wake
PASS test_pi_restoration_exhaustion_bypasses_ordinary_latch
PASS test_pi_restoration_lock_loss_bypasses_ordinary_latch
PASS test_pi_retry_lock_loss_bypasses_ordinary_latch
PASS test_pi_refused_handshake_bypasses_ordinary_latch
FAIL test_pi_session_replacement_discards_ordinary_latch

### variant: v5-no-urgency
PASS test_pi_ordinary_burst_coalesces_to_one_dock_row
PASS test_pi_consumed_row_rearms_for_genuinely_later_wake
PASS test_pi_busy_consumed_row_rearms_for_genuinely_later_wake
PASS test_pi_cleared_dock_rearms_for_genuinely_later_wake
FAIL test_pi_restoration_exhaustion_bypasses_ordinary_latch
FAIL test_pi_restoration_lock_loss_bypasses_ordinary_latch
FAIL test_pi_retry_lock_loss_bypasses_ordinary_latch
FAIL test_pi_refused_handshake_bypasses_ordinary_latch
PASS test_pi_session_replacement_discards_ordinary_latch

### variant: v6-no-agent-start-clear
PASS test_pi_ordinary_burst_coalesces_to_one_dock_row
FAIL test_pi_consumed_row_rearms_for_genuinely_later_wake
PASS test_pi_busy_consumed_row_rearms_for_genuinely_later_wake
PASS test_pi_cleared_dock_rearms_for_genuinely_later_wake
PASS test_pi_restoration_exhaustion_bypasses_ordinary_latch
PASS test_pi_restoration_lock_loss_bypasses_ordinary_latch
PASS test_pi_retry_lock_loss_bypasses_ordinary_latch
PASS test_pi_refused_handshake_bypasses_ordinary_latch
PASS test_pi_session_replacement_discards_ordinary_latch

### variant: v7-no-agent-settled-clear
PASS test_pi_ordinary_burst_coalesces_to_one_dock_row
PASS test_pi_consumed_row_rearms_for_genuinely_later_wake
FAIL test_pi_busy_consumed_row_rearms_for_genuinely_later_wake
FAIL test_pi_cleared_dock_rearms_for_genuinely_later_wake
PASS test_pi_restoration_exhaustion_bypasses_ordinary_latch
PASS test_pi_restoration_lock_loss_bypasses_ordinary_latch
PASS test_pi_retry_lock_loss_bypasses_ordinary_latch
PASS test_pi_refused_handshake_bypasses_ordinary_latch
PASS test_pi_session_replacement_discards_ordinary_latch
Evidence: Variant definitions and what each proves

Source: Variant definitions and what each proves

# The seven broken extension variants

Each is a single edit to `.pi/extensions/fm-primary-pi-watch.ts`.

| Variant | Edit | Coalescing tests that fail |
| --- | --- | --- |
| `v1-upstream` | the extension at base commit `355f46f`, no coalescing at all | burst only |
| `v2-latch-after-await` | `pendingOrdinaryWakeRow = true` moved after `await pi.sendUserMessage(...)` | idle-consumed re-arm only |
| `v3-callsite-only-urgency` | `const urgent = presentation === "urgent"` (drops the `watcher: FAILED` content check) | restoration exhaustion + restoration-time lock loss |
| `v4-shared-latch` | one latch shared across generations instead of a per-generation field | session replacement only |
| `v5-no-urgency` | `const urgent = false` | all four failure-bypass tests |
| `v6-no-agent-start-clear` | the `agent_start` handler removed, clearing only on `agent_settled` | idle-consumed re-arm only |
| `v7-no-agent-settled-clear` | the `agent_settled` handler removed, clearing only on `agent_start` | busy-path re-arm + discarded-row re-arm |

The intent's original variant list included a "no rejection rollback" variant.
That variant no longer exists: the rollback and its test were removed during
review once the installed Pi 0.84.4 runtime showed `ExtensionAPI.sendUserMessage`
returns `void` and every rejection is routed to `runner.emitError`, so the
extension can never observe one. `v6` and `v7` stand in its place and show both
latch-clearing edges are load-bearing against different tests.

Verbatim results: `pi-coalescing-mutation-matrix.txt`.
Evidence: tests/fm-pi-watch-extension.test.sh full run (46 ok, exit 0)

Source: tests/fm-pi-watch-extension.test.sh full run (46 ok, exit 0)

$ bash tests/fm-pi-watch-extension.test.sh
ok - Pi extension reports external healthy watcher output
ok - Pi custom tool exposes repair-only metadata and returns automatic-continuation guidance
ok - Pi redundant tool call returns ownership guidance and spawns no second child
ok - Pi scheduled retry remains extension-owned after another tool call
ok - Pi actionable close starts one successor before wake delivery settles
ok - Pi dispatcher branch offer owns accepted wakes and falls back to main
ok - Pi dispatcher flags a fleet-wide heartbeat offer as branch-eligible
ok - a co-present check row neither vetoes nor rides a heartbeat into main
ok - every main-only check class still reaches main, never the supervision branch
ok - heartbeat restoration failure stays on main
ok - watcher-failure repair stays with main even with a live, accepting branch listener
ok - Pi refused handling handshake is classified and not swallowed
ok - Pi hung successor falls back to one typed actionable wake
ok - Pi unretired successor falls back without an overlapping retry
ok - Pi late unretired closes resume classified supervision
ok - Pi ordinary burst coalesces to one dock row and preserves every durable event
ok - Pi idle-consumed row re-arms presentation so a genuinely later wake presents one new row
ok - Pi busy-path consumed row re-arms presentation so a genuinely later wake presents one new row
ok - Pi discarded dock row re-arms presentation so a genuinely later wake still presents
ok - Pi restoration exhaustion surfaces separately from a pending ordinary row
ok - Pi restoration-time lock loss surfaces separately from a pending ordinary row
ok - Pi retry-path lock loss surfaces separately from a pending ordinary row
ok - Pi refused handshake surfaces separately from a pending ordinary row
ok - Pi session replacement discards the ordinary latch so a new session presents fresh
ok - Pi clean empty close triggers a bounded continuity retry
ok - Pi established clean closes stop at the configured retry limit
ok - Pi close handler verifies session-lock ownership before successor launch
ok - Pi watcher arm distinguishes all session lock ownership states
ok - Pi session transitions use a generation owner across /new /resume /fork, stale callbacks, and quit
ok - Pi process-exit cleanup listener remains singular across session replacement
ok - Pi process-exit cleanup stops the attached arm child
ok - OpenCode plugins have an explicit ESM boundary even under a typeless parent package
ok - OpenCode watcher plugin uses the effective FM_HOME state
ok - OpenCode watcher plugin sources the effective config
ok - OpenCode watcher plugin requires session lock ownership
ok - OpenCode watcher coordinator respects primary scope
ok - OpenCode watcher plugin starts one successor before wake prompt delivery settles
ok - OpenCode pre-ready actionable close preserves its successor
ok - OpenCode hung successor falls back to one typed actionable wake
ok - OpenCode unretired successor falls back without an overlapping retry
ok - OpenCode late unretired closes resume classified supervision
ok - OpenCode clean empty close triggers a bounded continuity retry
ok - OpenCode established clean closes stop at the configured retry limit
ok - OpenCode close handler verifies session-lock ownership before successor launch
ok - OpenCode watcher plugin coordinates with the turn-end guard
ok - OpenCode healthy arm output does not suppress the turn-end guard
exit=0 (46 ok, 0 not ok)
Evidence: Other suites loading the same extension (types/branch/calm)

Source: Other suites loading the same extension (types/branch/calm)

### fm-pi-primary-types
skip: tsc not found for Pi extension typecheck
exit=0
### fm-pi-branch-extension
ok - fm_branch_outcomes hides through ToolExecutionComponent while Calm-off and HTML export stay stock
ok - the installed Pi still bounds the picker's list and ranks its search
ok - branch owns accepted wakes with a stable prefix contract and verdict-driven merge delivery
ok - a captain outcome reaches main's model as typed, self-describing input while routine notes stay plain
ok - requested and unsolicited healthy outcomes keep distinct delivery and event ownership
ok - a broken operational encoder still delivers one invisible instructed captain outcome as a follow-up
ok - scopeForUnreadWake excludes every main-only class without vetoing eligible task-local rows, and writes the eligible snapshot
ok - branch prompt_cache_key is stable per home across sessions and distinct between homes
ok - branch default-on eligibility (task-scoped, heartbeat, afk) binds and a broken branch falls back to main
ok - a heartbeat review survives a check row arriving before its drain
ok - pre-drain eligibility re-check excludes a newly main-owned row without deferring eligible work
ok - a settled branch turn releases an unacknowledged grant for main replay
ok - a stale main claim cannot silently suppress later wake delivery
ok - pre-drain eligibility re-check no-ops an already-drained wake
ok - dialog mirror filters tool and operational traffic, lands before wakes, and keeps a durable cursor
ok - branch session persists across process restarts through the recorded pointer
ok - the current pin state binds every branch create and reopen, and clearing it returns the branch to main's model
ok - unpinned branches follow main model changes live while pinned branches stay fixed
ok - supervision-model command persists the captain's pick and rebinds the live branch
ok - supervision-model opens a bounded searchable list, follow main first, and pins the branch alone
ok - branch model picker keeps follow main first and filters the eligible catalog
ok - the effort pin binds every branch create and reopen, and clearing it returns the branch to main's effort
ok - unpinned branches follow main effort changes live while pinned branches stay fixed
ok - supervision-model runs an effort picker after the model picker and persists both independently
ok - an unusable model pin falls back to main and an unparseable one is treated as no pin
ok - replacement activation cleans old branch leases and retries failed cleanup
ok - branch activates on a cold start once the lock is acquired, never before
ok - queued wakes and mirrors stop mutating branch state after lock ownership is lost
ok - stale reports, shells, mirrors, cursors, leases, and prompts perform no side effects
ok - a Pi session that does not own the lock accepts nothing and mutates no branch state
ok - an extension rebind re-mirrors undelivered dialog instead of dropping it
exit=0
### fm-calm-pi-extension
ok - Pi calm resolves its persistent home independently of Pi's launch directory
ok - Pi calm compatibility evidence never rejects a Pi version for being newer than 0.82.0, and still fails closed on a missing or malformed version
ok - a missing collapsed-thinking presentation API degrades only that Calm adapter with a clear skip reason, while the rest of Calm still registers
ok - missing Pi presentation class exports reach the independent adapter degradation path
ok - Calm registers none of its 7 built-in tool wrappers at load while config/calm is off, and all 7 synchronously at load while config/calm is on
ok - Calm's first same-session /calm activation claims every uncontested built-in, leaves a foreign bash tool fully intact and callable, warns prominently and logs the contested name, and only rows constructed before that activation - the documented bound - fail to retroactively collapse
ok - Pi calm centralizes transcript visibility, preserves execution/export data, keeps Pi's stock working row visible while no run is active, and persists its choice across session starts
ok - Pi calm on collapses mid-turn assistant working notes to zero height while Calm off keeps them, leaves streaming, truncated-final, and genuine final replies untouched, never mutates the messages, ignores every /calm argument, and restores a legacy persisted max as ordinary Calm on
skip: pi or tmux not found for Pi operational follow-up E2E
skip: pi or tmux not found for Pi Calm hidden-block geometry E2E
ok - Pi Calm working ship moves on a slow independent cadence over faster fixed-cell blue water, paints the complete boat standard yellow with balanced resets, keeps ANSI-stripped width exact, flips the directional sail on the exact bounce at both edges and every width, clamps visible and hidden resizes, falls back deterministically when narrow, freezes and resumes column/direction across settle/start without hidden-time jumps or duplicate timers, resets only on a fresh session, and installs and removes one scheduler-owning widget across starts, settle, abort, failure, shutdown, reload, replacement, and Calm toggles while leaving Calm-off visibility untouched
skip: pi or tmux not found for Pi calm interactive E2E
exit=0
Evidence: Evidence README with reproduction steps

Source: Evidence README with reproduction steps

# Pi Follow-up wake presentation coalescing: test evidence

All artifacts here were produced by driving the tracked extension
`.pi/extensions/fm-primary-pi-watch.ts` through the real Pi `ExtensionAPI`
boundary (the same fixture install `tests/fm-pi-watch-extension.test.sh` uses),
with a `sendUserMessage` fake that matches the real surface: Pi 0.84.4 declares
`ExtensionAPI.sendUserMessage(...): void` and its loader discards the runtime
promise, so the fake queues a dock row and returns, and runtime effects
(`agent_start`, inline drain, `agent_settled`) happen afterwards off that call
stack. That surface was read from the installed runtime, not assumed.

| Artifact | What it shows |
| --- | --- |
| `pi-dock-burst-transcript.txt` | The end-user symptom and the fix: 20 actionable watcher closes during one long captain turn produce 20 dock rows on upstream `355f46f` and 1 dock row on this change, with all 20 durable wake-queue records intact in both runs. |
| `pi-dock-failure-visibility-transcript.txt` | With an ordinary row already docked, continuity-restoration exhaustion still presents its own `watcher: FAILED` row. The counterexample run shows the call-site-only urgency shape swallowing that failure. |
| `pi-coalescing-mutation-matrix.txt` | Non-vacuity: each of the 9 new coalescing tests run individually against 7 deliberately broken extension variants. |
| `pi-watch-extension-suite.log` | `bash tests/fm-pi-watch-extension.test.sh`: 46 ok, exit 0. |
| `pi-related-suites.log` | The other suites that load this extension. primary-types skips (no `tsc` on this machine); branch-extension (31) and calm-extension (9) both exit 0. |
| `pi-dock-burst-demo.sh`, `pi-dock-failure-visibility-demo.sh` | The demo drivers that produced the two transcripts. |

## Reproducing

Both demos and the per-test variant runner need a `defs.sh`: the test file with
its runner list stripped, so its fixture helpers can be sourced and the
extension under test can be swapped with `EXT_OVERRIDE`.

`` `sh
sed -n '1,3827p' tests/fm-pi-watch-extension.test.sh \
  | sed "6s|.*|. \"$PWD/tests/lib.sh\"|" \
  | sed '9s|.*|EXT="${EXT_OVERRIDE:-$ROOT/.pi/extensions/fm-primary-pi-watch.ts}"|' \
  > /tmp/defs.sh

DEFS=/tmp/defs.sh bash pi-dock-burst-demo.sh "AFTER" 20
DEFS=/tmp/defs.sh bash pi-dock-failure-visibility-demo.sh "AFTER"
`` `

`tests/lib.sh` `fail()` exits on the first failure, so the mutation matrix was
produced by appending a runner that calls each of the 9 coalescing tests in its
own subshell and records a per-test verdict, then running that file once per
variant with `EXT_OVERRIDE` pointed at the broken copy.
Evidence: Demo drivers that produced the two transcripts

Source: Demo drivers that produced the two transcripts

#!/usr/bin/env bash
# Reviewer-visible demo of the Pi follow-up dock under a burst of ordinary
# watcher closes, run through the real tracked extension and the real
# ExtensionAPI boundary (same fixture install the suite uses).
# Usage: DEFS=<defs.sh> EXT_OVERRIDE=<extension.ts> pi-dock-burst-demo.sh <label> <N>
set -u
LABEL=$1
N=$2
# shellcheck source=/dev/null
. "$DEFS"
repo="$TMP_ROOT/demo-root"
home="$TMP_ROOT/demo-home"
mkdir -p "$repo/bin" "$home/state" "$home/config"
install_pi_watch_extension_fixture "$repo"
cat > "$repo/bin/fm-watch-arm.sh" <<'SH'
#!/usr/bin/env bash
if [ "${1:-}" = --handling-delivered ]; then
  printf 'confirmed\n' >> "${FM_CONFIRM_LOG:?}"
  exit 0
fi
printf 'arm=%s\n' "$$" >> "${FM_ARM_LOG:?}"
count=$(grep -c '^arm=' "$FM_ARM_LOG")
if [ "$count" -eq 1 ]; then
  printf '1\t1\tsignal\tclose-1\tsignal: secondmate alpha finished, needs review\n' >> "${FM_WAKE_QUEUE:?}"
  printf 'watcher: started pid=%s (beacon fresh)\n' "$$"
  printf 'signal: secondmate alpha finished, needs review\n'
  exit 0
fi
printf 'watcher: started pid=%s (beacon fresh) recovery-generation=gen-%s\n' "$$" "$count"
while [ ! -e "${FM_BURST_GO:?}/$count" ] && [ ! -e "${FM_STOP_FILE:?}" ]; do sleep 0.02; done
[ -e "${FM_STOP_FILE:?}" ] && exit 0
printf '1\t%s\tsignal\tclose-%s\tsignal: secondmate %s finished, needs review\n' "$count" "$count" "$count" >> "${FM_WAKE_QUEUE:?}"
printf 'signal: secondmate %s finished, needs review\n' "$count"
SH
chmod +x "$repo/bin/fm-watch-arm.sh"
PLUGIN="$repo/.pi/extensions/fm-primary-pi-watch.ts" \
FM_HOME="$home" FM_ROOT_OVERRIDE="$repo" \
FM_ARM_LOG="$TMP_ROOT/arm.log" FM_CONFIRM_LOG="$TMP_ROOT/confirm.log" \
FM_WAKE_QUEUE="$home/state/.wake-queue" FM_BURST_GO="$TMP_ROOT/go" \
FM_STOP_FILE="$TMP_ROOT/stop" DEMO_LABEL="$LABEL" DEMO_N="$N" \
node --input-type=module <<'EOF'
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { pathToFileURL } from "node:url";

const N = Number(process.env.DEMO_N);
let tool = null;
const handlers = new Map();
const dock = [];
let running = false;
const pi = {
  on(event, handler) { handlers.set(event, handler); },
  registerCommand() {},
  registerTool(candidate) { if (candidate.name === "fm_watch_arm_pi") tool = candidate; },
  // Pi 0.84.4 ExtensionAPI.sendUserMessage: queues a follow-up row, returns void.
  sendUserMessage: (message) => { dock.push(String(message)); return undefined; },
};
writeFileSync(`${process.env.FM_HOME}/state/.lock`, `${process.pid}\n`);
const mod = await import(pathToFileURL(process.env.PLUGIN).href);
mod.default(pi);
// The captain is mid-turn for the whole burst: a long handling turn is exactly
// when the dock accumulates.
running = true;
await handlers.get("agent_start")?.({ type: "agent_start" }, {});
await tool.execute("demo-arm", {}, undefined, undefined, {});
mkdirSync(process.env.FM_BURST_GO, { recursive: true });
const lines = (f) => (existsSync(f) ? readFileSync(f, "utf8").trim().split("\n").filter(Boolean) : []);
const confirms = () => lines(process.env.FM_CONFIRM_LOG).length;
async function waitFor(p, label) {
  for (let i = 0; i < 1000; i += 1) {
    if (p()) return;
    await new Promise((r) => setTimeout(r, 10));
  }
  throw new Error(`timeout waiting for ${label}`);
}
await waitFor(() => confirms() >= 1, "close 1");
for (let c = 2; c <= N; c += 1) {
  writeFileSync(`${process.env.FM_BURST_GO}/${c}`, "go\n");
  await waitFor(() => confirms() >= c, `close ${c}`);
}
await waitFor(() => lines(process.env.FM_ARM_LOG).length >= N + 1, "final successor");
await new Promise((r) => setTimeout(r, 150));
writeFileSync(process.env.FM_STOP_FILE, "stop\n");

const queue = lines(process.env.FM_WAKE_QUEUE);
console.log(`--- ${process.env.DEMO_LABEL} ---`);
console.log(`actionable watcher closes during the captain's turn : ${confirms()}`);
console.log(`durable wake-queue records (bin/fm-wake-drain.sh)   : ${queue.length}`);
console.log(`Pi follow-up dock rows presented to the captain     : ${dock.length}`);
console.log("");
console.log("Pi follow-up dock as the captain would see it:");
for (const [i, row] of dock.entries()) {
  const first = row.split("\n").find((l) => l.includes("FIRSTMATE WATCHER WAKE")) || row.split("\n")[0];
  console.log(`  [row ${String(i + 1).padStart(2, " ")}] ${first.replace(/^⁣/, "")}`);
}
console.log("");
console.log("Full text of dock row 1 (what the model actually receives):");
for (const l of dock[0].replace(/^⁣/, "").split("\n")) console.log(`  | ${l}`);
console.log("");
console.log("Durable queue (untouched by presentation coalescing):");
for (const l of queue) console.log(`  ${l.replace(/\t/g, "  ")}`);
console.log("");
process.exit(0);
EOF
- Outcome: ⚠️ 1 warning across 1 run (10m3s)

Pipeline

Updates from git push no-mistakes

... (4 earlier update rounds omitted to keep the PR body within GitHub's 65536-char limit; full history is in the run log.)

⚠️ **Review** - 2 infos

🔧 Fix: drop unreachable send rollback, align fakes to void API
2 issues (1 warning, 1 info) still open:

  • ⚠️ .pi/extensions/fm-primary-pi-watch.ts:634 - The inline comment above the agent_start handler still carries the superseded ordering claim that the fix rounds were required to remove everywhere. It reads "The idle-path consumption edge: an idle send starts its run inside sendUserMessage, so the row is consumed at run start", which directly contradicts the corrected header 21 lines above: "the await resumes on the very next microtask while the run it triggers starts from the runtime's own asynchronous chain and emits agent_start later" (lines 21-26), and the verification record's "every runtime effect happens afterwards, off that call stack" (docs/verification/supervision.md:477). The installed Pi 0.84.4 confirms the header, not the comment: ExtensionAPI.sendUserMessage is void (dist/core/extensions/types.d.ts:980, discarded at loader.js:300), and even AgentSession.prompt awaits the extension input emit, the auth check, and _checkCompaction (agent-session.js:842-812) before reaching _runAgentPrompt, so no run starts on the extension's call stack. Commit e67eed9 rewrote the header but left this comment, which commit 684d80f had introduced. Behavior is unaffected - agent_start remains the correct idle-path edge - but the file is the designated single owner of this contract and now states both orderings, and the captain's round-1 instruction 3 required correcting every place stating the old ordering. Fix: restate it as "an idle send makes the runtime start a run that takes the queued row and emits agent_start, re-arming presentation at run start rather than at run end".
  • ℹ️ tests/fm-pi-watch-extension.test.sh:1598 - The busy-path and cleared-dock drivers are near-identical copies and each carries the other's helper unused: clearQueuedRows is defined at line 1598 but never called in test_pi_busy_consumed_row_rearms_for_genuinely_later_wake (which only calls drainQueuedInline at 1647), and drainQueuedInline is defined at line 1729 but never called in test_pi_cleared_dock_rearms_for_genuinely_later_wake (which only calls clearQueuedRows at 1784). Nothing lints inside these node heredocs, so the dead halves will not be caught later. Dropping the unused helper from each driver removes the misleading suggestion that both transitions are exercised in both tests. The tests themselves are sound and non-vacuous: I traced that each genuinely fails under an agent_start-only clear (row docks, inline drain or discard emits no agent_start, settle is the only remaining edge), which is exactly the defect they guard.

🔧 Fix: correct stale ordering comment, drop dead test helpers
2 issues (1 warning, 1 info) still open:

  • ⚠️ docs/watcher-continuity.md:38 - The operator-facing guarantee states an absolute that the extension header contradicts in the same change. Line 38 reads "Coalescing never costs a wake: the run that consumes or discards the pending row re-arms presentation, so a genuinely later wake still presents one new row." But .pi/extensions/fm-primary-pi-watch.ts:39-41 — the file this doc names as the contract owner — states "a wake arriving after an inline drain but before settle is suppressed for the remainder of that one run." That window is reachable and I traced it: a captain run is in flight, an ordinary close docks a row and sets the latch, Pi's runLoop drains that row inline (pi-agent-core/dist/agent-loop.js:161-165, pendingMessages = followUpMessages; continue;) with no second agent_start, and a genuinely later actionable close during the remainder of that long run hits the early return at .pi/extensions/fm-primary-pi-watch.ts:311 and presents no row of its own. Only at agent_settled does presentation re-arm. So coalescing does cost that wake its own row; the durable queue still holds the event and the bound is one run, which is exactly what the header says and what the doc should say. The neighbouring sentence at line 41, "nothing is acknowledged, truncated, or delayed by the latch", reads as an absolute too, while presentation delay is the deliberate mechanism (durable records genuinely are untouched — I confirmed fm_recovery_marker_begin_handling only moves downtime->handling and leaves the pending/announced prefix, so no recovery announcement is consumed by a coalesced wake). Fix: keep the operator guarantee but scope it to the owned contract, e.g. that the run consuming or discarding the row re-arms presentation and a wake arriving after that still presents, with a wake arriving inside the drain-to-settle window of one run held only until that run settles.
  • ℹ️ .pi/extensions/fm-primary-pi-watch.ts:317 - Recorded for the final acceptance decision only, not a defect and no action recommended. The frozen --intent text marks as required: "The ordinary latch rolls back if sendUserMessage rejects before a row is established, then the error propagates so the existing catch reports it as a failure", plus "Do not 'simplify' the set-before-await ordering or the try/catch around the send", a "production-timing fake sendUserMessage where an idle send fires agent_start inside the call and resolves only after the driver-controlled run completes", and "delivery-rejection rollback coverage". None of those are present now: sendWake calls await pi.sendUserMessage(...) bare with no try/catch, test_pi_rejected_delivery_rolls_back_ordinary_latch is gone, and every fake models a void fire-and-forget send. That divergence is the captain's own round-2 instruction, and the evidence behind it checks out against the installed Pi 0.84.4: ExtensionAPI.sendUserMessage is declared void (dist/core/extensions/types.d.ts:980), dist/core/extensions/loader.js:299-302 discards the runtime promise, and dist/core/agent-session.js:2013 routes every rejection to runner.emitError, so the rollback branch and its test could never run against real Pi. The set-before-send ordering the intent calls decisive is preserved; only its stated justification changed. The residual is documented at lines 51-57 and the record's superseded-claims note at docs/verification/supervision.md:480 is accurate. I am not re-raising this as a blocker since the user already decided it with the evidence in hand; it is listed so the intent-versus-shipped divergence is visible rather than silent.

🔧 Fix: qualify coalescing guarantee to the bounded within-run window
2 infos still open:

  • ℹ️ tests/fm-pi-watch-extension.test.sh:1326 - Residual dead halves of the shared fake, the same tidiness class round 3 cleaned from the busy-path and cleared-dock drivers. In the burst driver, running is set true by startCaptainRun() before any send and is only cleared by the final settleRun() immediately before process.exit(0), so the if (!running &amp;&amp; !runScheduled) branch in sendUserMessage never fires: startRun is unreachable and consumed is written by nothing and read by nothing. The assertion agentStarts !== 1 in that driver is therefore guaranteed by the fake's own bookkeeping rather than by extension behavior. Separately, the idle re-arm driver at line 1452 imports existsSync and readFileSync and uses neither. Nothing lints inside these node heredocs, so these will not be caught later. Dropping the unused import pair and either removing consumed from the burst driver or leaving startRun with a comment that it models the idle branch this driver never enters would keep each driver honest about what it exercises. No guarantee depends on any of this, and every coalescing assertion I traced remains non-vacuous.
  • ℹ️ .pi/extensions/fm-primary-pi-watch.ts:317 - Recorded for the acceptance decision only; already adjudicated in round 4 and no action recommended. The frozen --intent marks as required "The ordinary latch rolls back if sendUserMessage rejects before a row is established, then the error propagates so the existing catch reports it as a failure", plus "Do not 'simplify' the set-before-await ordering or the try/catch around the send", a fake where "an idle send fires agent_start inside the call and resolves only after the driver-controlled run completes", and "delivery-rejection rollback coverage". None are present: line 317 is a bare await pi.sendUserMessage(...) with no try/catch, test_pi_rejected_delivery_rolls_back_ordinary_latch is gone, and every fake models a void fire-and-forget send. That divergence is the captain's own round-2 instruction and the evidence holds against the installed Pi 0.84.4 (ExtensionAPI.sendUserMessage declared void at dist/core/extensions/types.d.ts:980, discarded at dist/core/extensions/loader.js:300, rejections routed to runner.emitError at dist/core/agent-session.js:2013), so the rollback branch and its test could never execute against real Pi. The set-before-send ordering the intent calls decisive is preserved; only its stated justification changed, and the residual is documented at the extension header and docs/verification/supervision.md:480. Listed so the intent-versus-shipped divergence stays visible rather than silent.
⚠️ **Test** - 1 warning
  • ⚠️ .pi/extensions/fm-primary-pi-watch.ts:317 - The intent lists as required that "the ordinary latch rolls back if sendUserMessage rejects before a row is established, then the error propagates", with a rejected-delivery regression as required evidence. Neither exists in the shipped change: review commit e67eed9 removed the rollback and its test, and no rejected-delivery test is in the suite. I verified independently why, in the installed Pi 0.84.4 runtime: ExtensionAPI.sendUserMessage is declared void at dist/core/extensions/types.d.ts:980, dist/core/extensions/loader.js:302 calls the runtime method without returning its promise, and dist/core/agent-session.js:2013 catches every rejection into runner.emitError. The extension can never observe a failed send, so the required behavior is not implementable on this API surface and no test can demonstrate it. The extension header and docs/verification/supervision.md both record this as a residual. The code looks correct to me and the intent text looks stale on this one point, but confirming that supersession is the captain's call, not mine.
  • bash tests/fm-pi-watch-extension.test.sh (46 ok, exit 0, includes the 9 new coalescing regressions)
  • bash tests/fm-pi-branch-extension.test.sh (31 ok, exit 0)
  • bash tests/fm-calm-pi-extension.test.sh (9 ok, exit 0)
  • bash tests/fm-pi-primary-types.test.sh (exit 0, reports skip: tsc not found for Pi extension typecheck, matching the verification record)
  • Manual end-user demo: 20-close burst through the real extension and ExtensionAPI boundary, upstream 355f46f vs HEAD, capturing the rendered follow-up dock and the durable wake queue (pi-dock-burst-demo.sh)
  • Manual end-user demo: pending ordinary row plus continuity-restoration exhaustion, HEAD vs call-site-only-urgency variant (pi-dock-failure-visibility-demo.sh)
  • Non-vacuity matrix: each of the 9 coalescing tests run individually (subshell-isolated runner, EXT_OVERRIDE) against 7 broken variants (upstream, latch-after-await, call-site-only urgency, shared latch, no urgency, no agent_start clear, no agent_settled clear)
  • /bin/bash -n reproduction of the bash 3.2 heredoc-apostrophe hazard recorded in CONTRIBUTING.md, plus /bin/bash -n tests/fm-pi-watch-extension.test.sh
  • Read the installed @earendil-works/pi-coding-agent 0.84.4 dist/core/extensions/types.d.ts, extensions/loader.js, and agent-session.js to confirm ExtensionAPI.sendUserMessage returns void and rejections go to runner.emitError
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-02T15:56:49.035619Z f88f9d1 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@greptile-apps

greptile-apps Bot commented Sep 1, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Reviews (3): Last reviewed commit: "fix(pi): rebase wake coalescing onto the..." | Re-trigger Greptile

@npayette84
npayette84 force-pushed the fm/fm-pi-followup-queue-coalescing branch from 4a6d1b0 to 1596472 Compare September 2, 2026 07:36

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 15964722f3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const urgent = presentation === "urgent" || isUrgentSupervisionFailure(message);
// The already-pending row directs the model to drain the durable queue, so
// this event is presented by that row; its durable record is untouched.
if (!urgent && owner.pendingOrdinaryWakeRow) return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve wakes suppressed after an inline drain

Captain, when an ordinary event arrives after a busy run has consumed the docked row and drained the durable queue, but before agent_settled, this early return suppresses the event's only notification. The settle handler only clears the latch, so if no subsequent event or captain turn occurs, the idle session never drains that newly queued wake; retain that a wake was suppressed and enqueue a replacement row at settle, or clear the latch on the matching inline-consumption edge. The new busy-path test triggers its later event only after settling, so it misses this timing.

AGENTS.md reference: AGENTS.md:L401-L404

Useful? React with 👍 / 👎.

npayette84 and others added 8 commits September 2, 2026 11:05
…ures

Pi's fixed follow-up dock gained one queued row per ordinary watcher
notification, so a burst of actionable closes during one long handling turn
buried the conversation under rows even though every event was already durable
in the wake queue.

Presentation now separates from delivery: while one ordinary work-waiting row
is pending, later ordinary wakes add no row. The per-generation latch is set
before the delivery await, because on an idle agent Pi runs the whole handling
turn inside sendUserMessage and emits agent_start before that call resolves; a
latch set after the await would strand itself and suppress every later ordinary
wake. It clears on agent_start, the edge where the pending row is consumed, and
rolls back when delivery rejects before a row exists.

Urgent supervision failures bypass the latch from both directions: call sites
mark the paths they own, and any message carrying the `watcher: FAILED` marker
is urgent whichever branch delivered it, so continuity-restoration exhaustion
and restoration-time lock loss, which ride the ordinary delivery branch, still
present their own row while a row is pending.

The durable wake queue is untouched, and ownership, successor restoration, and
no-redundant-arm behavior are unchanged.

Regressions drive the real extension through a production-timing follow-up fake
built from Pi 0.84.4's runtime ordering, and each was run against deliberately
broken variants of the extension to prove it is not vacuous.
…ent contract

Rebasing onto upstream main brought in the tokenized wake acknowledgement
added by kunchenguid#3498: an actionable wake settles only when Pi reports the docked
row back through before_agent_start, and delivery of the next pending close
waits on that. The coalescing fakes now announce consumption the same way,
so a coalesced ordinary wake reports as presented by the row already docked
instead of stalling the pending-delivery loop.

The discarded-dock regression is dropped on this base: a row that is never
consumed leaves the pending-delivery loop waiting on its acknowledgement, so
no later wake reaches presentation at all and the latch is no longer what the
scenario observes. The agent_settled clearing edge stays covered by the
busy-path re-arm test.

docs/verification/supervision.md records the re-run counterfactual matrix on
this base, including that the latch-after-send variant no longer fails any
test there.
…contract

Upstream main now settles a main delivery once Pi accepts the follow-up and
observes consumption at before_agent_start for an idle main and at the user
message_start for a streaming one (kunchenguid#3513), replacing the acknowledgement
contract this change was built against.

Presentation coalescing moves on top of that contract unchanged in behavior:
sendWake keeps its WakePresentation argument next to the base's pending-record
argument, sets the ordinary latch before the delivery await, rolls it back in
the catch that already drops the unconsumed-wake record, and a coalesced wake
never enters that map, so its pending record is finished rather than left
waiting for a consumption that will never come.

The base's streaming-time delivery regression drove two actionable closes
through one streaming run and expected a docked row for each. Coalescing
presents the second through the row already docked, so it now proves the
successor chain advances without a second row and takes its unconsumed handoff
wake from a later close raised after that row is consumed at message_start and
its run settles.

The verification record names the new base, and the counterfactual matrix was
re-run there: unmodified upstream fails only the burst test, each single
clearing edge fails only its own re-arm test, call-site-only urgency fails both
restoration-failure tests, removing urgency fails all four failure-bypass
tests, and a latch shared across generations fails only the session-replacement
test.
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