Skip to content

fix(calm): stop terminal-completed crews and Pi Calm from spamming acknowledgement rows - #1999

Open
AbdullahPesteli wants to merge 4 commits into
kunchenguid:mainfrom
AbdullahPesteli:fm/ship-firstmate-calm-operational-spam
Open

fix(calm): stop terminal-completed crews and Pi Calm from spamming acknowledgement rows#1999
AbdullahPesteli wants to merge 4 commits into
kunchenguid:mainfrom
AbdullahPesteli:fm/ship-firstmate-calm-operational-spam

Conversation

@AbdullahPesteli

Copy link
Copy Markdown

Intent

Fix a Firstmate/Pi supervision-and-presentation defect: a burst of terminal or long-idle task notifications produced dozens of visible exact 'Captain, shipshape.' acknowledgement rows even with config/calm=on. Remove the noise at its two correct owners without adding broad dedupe or disabling monitoring, preserving supervision strength.

Watcher/classifier owner (bin/fm-classify-lib.sh, bin/fm-watch.sh): crew_absorb_class now recognizes a genuinely COMPLETED terminal state (fm-crew-state.sh reports state: done) as its own absorb class, and the stale-pane terminal path absorbs a done crew whose old endpoint has gone idle or agent-free instead of re-surfacing a pane-stale wake on top of the signal and heartbeat paths that already deliver its done: transition. No wedge timer is armed for a completed crew. Terminal truth comes from the reconciler, never the last status event. A follow-up commit scopes the new done token out of pause_state_class so the paused/captain-held non-terminal cadence stays byte-identical to before, keeping the done absorb only in the terminal-stale path. Preserved: failure delivery, actionable blockers, working-task wedge detection, paused external-wait cadence, heartbeat semantics, custom checks, PR merge polls, and durable wake-queue delivery.

Pi Calm presentation owner (.pi/extensions/lib/*): the assistant-layout render override collapses ONLY the exact no-action acknowledgement AGENTS.md section 9 defines ('Captain, shipshape.'), bound to a typed operational-input provenance AND the exact text together, never string-only. A captain reply, a real outcome such as a PR URL, a decision, a failure, and a superficially similar sentence all stay visible; the acknowledgement renders in full whenever Calm is off and in stock /export and /share; session data is unchanged.

Kept both fixes at their existing owners; did NOT expand AGENTS.md. Updated docs/calm.md, docs/calm-mode-feasibility.md, docs/architecture.md, docs/configuration.md. Regression tests assert behavior via public interfaces, never source bytes. The shell watcher tests use live tmux subprocesses so full files exceed two minutes; run the change's own functions selectively and do not build extra evidence artifacts. Pushing to a fork and opening a PR against kunchenguid/firstmate:main. Scoped OUT as a follow-up: a Pi MCP-approval-modal false-working detection.

What Changed

  • Added a done absorb class to crew_absorb_class (bin/fm-classify-lib.sh) plus a crew_is_terminal_done helper, so the stale-pane terminal path in bin/fm-watch.sh now reconciler-checks each new-hash wake and absorbs a genuinely completed crew (state: done) whose endpoint has gone idle/agent-free instead of re-surfacing a pane-stale wedge on top of the signal/heartbeat paths that already deliver its done: transition; failed/parked/blocked/unknown verdicts still surface.
  • Scoped the new done token out of pause_state_class (bin/fm-watch.sh) so the paused/captain-held non-terminal reclassification cadence for a dead agent stays byte-identical, keeping the done absorb confined to the terminal-stale path.
  • Made the Pi Calm assistant-layout render override (.pi/extensions/lib/) collapse only the exact no-action acknowledgement bound to a typed operational-input provenance AND its exact text together — never string-only — so captain replies, real outcomes, decisions, failures, and lookalike sentences stay visible, and the row renders in full when Calm is off and in stock /export and /share.
  • Updated docs (architecture, calm, calm-mode-feasibility, configuration) for the new done absorb class and added regression tests asserting behavior through public interfaces (live tmux subprocesses for the watcher; the real render path for the Pi extension).

Risk Assessment

✅ Low: The change is tightly scoped to two named owners, precisely matches the authoritative intent, preserves the paused/captain-held cadence byte-identically (verified across every branch), binds the Calm collapse to provenance-plus-exact-text, and ships comprehensive public-interface regression tests; the only finding is a harmless docstring inaccuracy.

Testing

Exercised both fix owners selectively (never the full >2-min live-tmux suites, per intent) and all targeted tests passed. For the Pi Calm presentation owner I ran the render/lifecycle test against the actually-installed Pi 0.84.1 and, via an instrumented copy of the same fixture, captured a rendered TUI transcript proving the Captain, shipshape. ack collapses only under typed operational provenance + exact text, while a genuine captain reply, a real PR-URL outcome, a near-miss (Captain, shipshape!), and stock /export all render in full. For the watcher/classifier owner I ran the new done-absorb-class functions and drove a live fm-watch.sh subprocess, capturing two artifacts: a completed crew's stale pane is absorbed silently (empty wake-queue, no wedge timer armed, status not marked surfaced, triage log line absorbed stale (terminal completed, endpoint idle/agent-free)), whereas an unknown/torn-down crew on the same terminal status still surfaces a wake — demonstrating the spam is removed without weakening failure delivery or monitoring. Transient test-driver files were removed; the worktree is clean.

Evidence: Pi Calm rendered TUI transcript — ack collapses only for typed operational provenance + exact text; genuine reply, PR URL, near-miss, and /export stay visible

Calm: ON FIRSTMATE_OPERATIONAL_ACK = "Captain, shipshape." Scenario 1: routine ack answering a typed operational (watcher) input -> (row collapsed / not shown) Scenario 2: same text after a GENUINE captain message -> "Captain, shipshape." (visible) Scenario 3: real operational OUTCOME -> "Captain, PR https://example.test/pr/9 checks green." (visible) Scenario 4: near-miss "Captain, shipshape!" -> visible Scenario 5: stock /export - /share of the collapsed ack -> "Captain, shipshape." (shown in full)

Firstmate Pi Calm - assistant acknowledgement render transcript
Pi package: /Users/pestly/.npm-global/lib/node_modules/@earendil-works/pi-coding-agent
Calm: ON   FIRSTMATE_OPERATIONAL_ACK = "Captain, shipshape."

=== Scenario 1: routine ack answering a typed operational (watcher) input ===
captain sees:
(row collapsed / not shown)

=== Scenario 2: same 'Captain, shipshape.' text after a GENUINE captain message ===
captain sees:

 Captain, shipshape.

=== Scenario 3: real operational OUTCOME (PR URL) after operational input ===
captain sees:

 Captain, PR https://example.test/pr/9 checks green.

=== Scenario 4: near-miss 'Captain, shipshape!' (exclamation) after operational input ===
captain sees:

 Captain, shipshape!

=== Scenario 5: stock /export - /share rendering of the collapsed ack (Scenario 1) ===
export shows:

 Captain, shipshape.
Evidence: Watcher triage — completed crew (state: done) stale pane is ABSORBED (no spam)

reconciler verdict: state: done · source: run-step · checks green wake reason to captain (stdout): (none - absorbing) durable .wake-queue: (empty - no acknowledgement/wake enqueued) triage log: absorbed stale (terminal completed, endpoint idle/agent-free): test:fm-shipped wedge timer: (not armed) status marked surfaced: (not marked)

Firstmate watcher triage - completed-crew stale pane (reconciler state: done)
reconciler verdict: state: done · source: run-step · checks green: PR ready for review
captain-relevant status log line: done: PR https://example.test/pr/7 checks green

--- wake reason printed to captain (stdout): ---
(none - watcher still absorbing, no wake surfaced)

--- durable wake-queue (.wake-queue): ---
(empty - no acknowledgement/wake enqueued)

--- triage log (.watch-triage.log): ---
[2026-08-09T12:07:47+0300] absorbed stale (terminal completed, endpoint idle/agent-free): test:fm-shipped

--- wedge timer (.stale-since-test_fm-shipped): ---
(not armed - a completed crew cannot wedge)
--- status marked surfaced (.hb-surfaced-shipped): ---
(not marked - stale-pane path does not own delivery)
Evidence: Watcher triage CONTRAST — unknown/torn-down crew still SURFACES (supervision preserved)

reconciler verdict: state: unknown · source: none · worktree gone wake reason to captain (stdout): stale: test:fm-torndown durable .wake-queue: 1786266474\t1\tstale\ttest:fm-torndown\tstale: test:fm-torndown

Firstmate watcher triage - CONTRAST: reconciler unknown/torn-down, stale pane
reconciler verdict: state: unknown · source: none · worktree gone

--- wake reason printed to captain (stdout): ---
stale: test:fm-torndown

--- durable wake-queue (.wake-queue): ---
1786266474	1	stale	test:fm-torndown	stale: test:fm-torndown

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ℹ️ bin/fm-classify-lib.sh:648 - crew_is_terminal_done's docstring states "The stale-pane terminal path uses this," but the production terminal-stale path in bin/fm-watch.sh:1002 inlines crew_absorb_class directly in a case statement and never calls crew_is_terminal_done. The helper is referenced only by tests (mirroring the test-only crew_is_paused). The comment overstates a caller relationship that does not exist, which could mislead a future maintainer in this heavily comment-driven codebase. Purely a documentation-accuracy nit; behavior is correct.
✅ **Test** - passed

✅ No issues found.

  • Selective driver over tests/fm-watch-triage.test.sh: test_crew_absorb_class_classifier, test_terminal_stale_surfaced, test_terminal_completed_stale_absorbed, test_stale_terminal_status_overridden_by_active_run, test_exited_declared_pause_is_bounded_but_live_gate_surfaces, test_nonterminal_stale_pause_transitions_reclassify_unchanged_hash — all ok
  • Selective driver over tests/fm-calm-pi-extension.test.sh: test_rendering_and_session_lifecycle (contains the new operational-ack collapse assertions) against installed Pi 0.84.1 — ok, EXIT=0
  • Evidence capture via instrumented copy of the calm node fixture driving the real AssistantMessageComponent.render for 5 provenance/text scenarios + stock export toggle
  • Evidence capture via a real bin/fm-watch.sh subprocess for the completed-crew (state: done) stale-pane absorb path and the unknown/torn-down surface contrast
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Abdullah Peşteli added 4 commits August 9, 2026 11:55
…knowledgement spam

A burst of terminal or long-idle task notifications produced dozens of visible
exact "Captain, shipshape." acknowledgement rows even with config/calm=on. The
defect had two correct owners, fixed at each without broad dedupe or weakened
monitoring.

Watcher/classifier: crew_absorb_class now recognizes a genuinely completed
terminal state (state: done) as its own absorb class via the authoritative
reconciler, and the stale-pane terminal path absorbs a done crew whose endpoint
has gone idle or agent-free instead of re-surfacing a pane-stale wake on top of
the signal and heartbeat paths that already deliver its done: transition. No
wedge timer is armed for a completed crew, and terminal truth comes from the
reconciler, never the last status event. Failure delivery, actionable gates,
working-task wedge detection, paused cadence, heartbeat semantics, checks, PR
merge polls, and durable wake-queue delivery are unchanged.

Pi Calm presentation: the assistant-layout adapter's render override collapses
the single no-action acknowledgement AGENTS.md section 9 defines, bound to a
typed operational-input provenance and the exact text together, never the string
alone. The operational-user adapter records that provenance on every user
message. A captain-authored reply, a real outcome such as a PR URL, a decision,
a failure, and a superficially similar sentence all stay visible, and the
acknowledgement renders in full whenever Calm is off and in stock export/share
rendering. Session data is unchanged.
The review-fix commit compared $class against an unquoted done, which
ShellCheck 0.11.0 flags as SC1010 (done is a shell keyword). The pipeline's
lint step never ran because the test step failed first, so the warning was
left in the tree. Quote it to match the same convention used in
fm-classify-lib.sh; behavior is unchanged.
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