pi.md

September 3, 2026 · View on GitHub

Mode: Pi extension background wake.

When this session owns supervision and away mode is not active:

  1. Drain first with bin/fm-wake-drain.sh. After handling all emitted wakes and reconciling open decisions and unread status lines, run the exact --ack-through command printed as WAKE_ACK_REQUIRED; until then the work remains durable for idempotent re-handling after interruption.
  2. Confirm the Pi primary auto-loaded both project extensions (plain pi or pi-signed, after approving project trust once per clone); if not, restart the selected executable with -e __FM_PI_TURNEND_EXT__ -e __FM_PI_EXT__ as a trust-free fallback.
  3. Initial process cycle only: make the one required fm_watch_arm_pi call; if startup already owned the fleet lock, this is an ownership-based no-op. Use /fm-watch-arm-pi only as a human-entered fallback. Never run bin/fm-watch-arm.sh through Pi's bash tool because that foreground arm can wedge the agent and bypasses extension-owned cleanup.
  4. If the extension says no live session holds the lock, run bin/fm-session-start.sh to reclaim the session lock, then call fm_watch_arm_pi again.
  5. The extension starts bin/fm-watch-arm.sh --restart, keeps the child attached to the live Pi process, and owns every later successor launch.
  6. Ordinary same-process session replacement (/new, /resume, /fork, reload) retires only the prior generation; when the replacement owns the fleet lock, its session_start arms the new generation without a model turn or another fm_watch_arm_pi call. The generation-owner contract and in-flight actionable-close handoff live in .pi/extensions/fm-primary-pi-watch.ts.
  7. After an actionable child close, the extension rechecks session-lock ownership and verifies one successor before it delivers the follow-up wake; its bounded fallback is defined in docs/watcher-continuity.md.
  8. Ordinary work, turn completion, and ordinary signal, stale, check, heartbeat, or other wake handling: do not call fm_watch_arm_pi again because continuity is extension-owned rather than model-memory-owned.
  9. An unexpected child close enters bounded exponential retry, and an exhausted retry or lost session lock is surfaced as a watcher failure instead of disappearing.
  10. Missing, failed, or unhealthy cycle only: if a later notification explicitly reports one of those repair conditions, drain queued wakes, inspect the failure text, call fm_watch_arm_pi, and restart the selected Pi-family executable with both extensions loaded if needed. A redundant call while the extension owns an arm child or scheduled retry is an ownership-based watcher: unchanged no-op, not an independent health claim.
  11. Never use shell & for watcher supervision. The arm mechanism above is extension-owned, not a model tool call, but a manual recovery probe that backgrounds, pipes, or bundles the arm is denied automatically by the PreToolUse seatbelt (bin/fm-arm-pretool-check.sh, wired into the turn-end guard extension at __FM_PI_TURNEND_EXT__).

The supervision branch is default-on (docs/pi-supervision-branch.md): whenever this session owns the fleet lock and away mode is not active, the watcher extension hands eligible task-local rows from ordinary actionable wakes, plus selected fleet-wide heartbeat reviews, to the in-process supervision branch while main-only rows remain queued for this conversation. A no-change heartbeat outcome explicitly reported with task=fleet and silent=true is delivered silently with no rendered note, while every other routine outcome returns as an appended, rendered note that leads with ⛵ then the dim outcome text. A captain-facing outcome instead appears as one exact, sequence-keyed visible transcript entry, and then arrives in this conversation as one hidden supervision processing request listing each [seq N] task: summary it covers. That request is the one turn in which MAIN processes the outcome: give the captain a visible response where one is due, answer or escalate a decision, act on a blocker or failure, or record that no further action is needed, then call the fm_branch_processed tool with the highest sequence the request listed, exactly once. Only that call closes the outcome; an unrelated, empty, or paraphrased answer leaves it open, and the current unprocessed sequence set is presented again at the next run boundary and at session start until it is acknowledged. The persisted entry is already the captain-visible record, so MAIN must not re-emit it verbatim merely because it appeared. Before MAIN steers, controls lifecycle, or cleans up a task, claim its lease with bin/fm-lease.sh claim <task> and release it afterwards; a refused claim means the branch is acting on that task right now. This conversation still receives every other fleet-wide or unresolvable wake, the branch's wakes when it is unavailable or away mode is active, and every watcher-failure alarm regardless, so the arm and repair contract above is unchanged. Treat the merged fleet event as already handled for fleet operations: MAIN must not re-drain, re-run, or acknowledge it. Separately, MAIN applies judgment about whether and how to surface, summarize, reference, or incorporate a merged sailboat outcome in the captain conversation; event ownership does not decide the conversational treatment. Read the durable outcome store with the fm_branch_outcomes tool when the captain asks what happened.

The turn-end guard extension lives at __FM_PI_TURNEND_EXT__. The watcher extension lives at __FM_PI_EXT__. Both are tracked, project-local .pi/extensions/*.ts files that Pi auto-discovers once the project is trusted; bin/fm-session-start.sh reports when the running Pi session has not loaded both required extensions.