Skip to content

fix: recover watcher continuity and reduce decision-fold overhead - #73

Merged
MrGTV-love merged 21 commits into
mainfrom
fm/fm-lifecycle-phase3
Oct 9, 2026
Merged

MrGTV-love merged 21 commits into
mainfrom
fm/fm-lifecycle-phase3

Conversation

@MrGTV-love

@MrGTV-love MrGTV-love commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

Intent

when agents time out we need to understand why they timed out
and fix the root cause
When looking at the timeouts and harness issues, assess individual fixes, but also assess as a system with dependencies. We often fix one thing to break another. I want to fix without breaking. I want to fix without needing to fix again.
2. yes. build the fix plan

Context: data/timeout-rca-20261007/report.md (read it fully; sections 6-9 and 13). This task is its Phase 3, which must land together because all parts share the arm, delivery and relaunch path: (a) incremental decision fold plus closed-decision compaction (drain and home-summary re-parse ~5 MB of status logs from line 1 every time: 131-240 s wall), shipped with a golden equivalence test; (b) F8 omp extension lifecycle log, generation self-heal, single-instance guard (.omp/extensions/fm-primary-omp-watch.ts and the .pi sibling): d1 and d4 lanes show "watcher: not armed - omp session is shutting down" with no successor; (c) F9 event-driven secondmate restart replacing the fixed 900 s wait in bin/fm-secondmate-restart.sh; (d) open backlog fm-restart-autorelaunch-race; (e) firstmate PR 40 (fm-omp-lane-wake-unsubmitted, #40) is in CI now: build on it after it merges, do not duplicate it.

Substance of the referenced report (data/timeout-rca-20261007/report.md, a private record not in this repository):

Dependency map (section 6). The waits this phase touches: E13, the omp primary watcher extension waits forever for a session_start after a session_shutdown, failing as the "session is shutting down" dead end; E14, fm-secondmate-restart waits a fixed 900 s for the mate's persist answer, failing as "did not confirm within 900s"; E11, fm-send waits on worker inbox acknowledgement, and a lane that cannot restart or re-arm goes deaf so its messages escalate and stall (cycle of harm: E13/E14 feed E11); E18, every bounded wait competes for the same host CPU, so work that grows with history becomes a timeout under load.

Blast radius and proof (section 7):

  • F8, omp extension lifecycle log, generation self-heal and single-instance guard. Touches .omp/extensions/fm-primary-omp-watch.ts and its .pi sibling. Could break: duplicate arms if the self-heal races a real successor. Proof: existing extension tests pass; new tests show shutdown without a following start produces exactly one re-arm, shutdown then start produces no double arm, and the watcher singleton lock proves one cycle.
  • F9, event-driven secondmate restart. Touches bin/fm-secondmate-restart.sh, the watcher turn-end handling and the auto-relaunch path. Could break: a restart mid-turn if turn-end detection is wrong, and the auto-relaunch race. Proof: tests show the request is recorded, no restart happens before the turn has ended, the auto-relaunch honors the request lock, and no 900 s path is left.
  • F10, every bounded wait logs waiter, waited-on, bound and actual, one line per expiry (not per wait), with rotated logs.

History (section 8). Earlier fixes in this area were made at the symptom boundary: composer screen recognizers added seven or more times, watcher liveness bounds raised twelve or more times (several explicitly for slowness under load), and the dead-secondmate auto-relaunch and relaunch-on-session-end fixes were followed by a race between relaunch and restart (the open fm-restart-autorelaunch-race). The cause stays, so the next spike fails at the next weakest bound.

Fix plan (section 9, updated by section 16), Phase 3, together: incremental decision fold plus closed-decision compaction; F8; F9; fm-omp-lane-wake-unsubmitted; fm-restart-autorelaunch-race. Must not: raise any timeout (including the 900 s) before the cause behind it is fixed; add another composer recognizer for load-induced partial frames; restart the shared no-mistakes daemon to clear slowness. Throughout, F10 applies.

Timeout 4a (section 4, the F8 item). The omp primary watcher refuses every arm call with "watcher: not armed - omp session is shutting down" once a generation is stopped by session_shutdown, because a new generation is created only on a later session_start, which may never come for this extension instance. Unproven candidate causes: omp emitting session_shutdown for an internal event (such as a compaction pass) without a matching session_start, or a second instance of the extension loaded into the same process (the file header warns naming it both ways loads it twice) making the other instance's generation non-live. Fix: (1) log every session_start, session_shutdown and generation create, activate and stop with timestamps to state/extensions/omp-primary-watch/lifecycle.log; (2) remove the dead end: if a generation stopped without a successor and the process is still alive after a bounded grace, activate a fresh generation (idempotent self-heal) instead of refusing every arm call forever; (3) enforce a single extension instance per process; (4) land together with fm-omp-lane-wake-unsubmitted, same arm and delivery path.

Timeout 4b (section 5, the F9 item). bin/fm-secondmate-restart.sh sends a persist request and waits a fixed 900 s for the mate's own answer, but a mid-turn mate queues the request behind its turn, and lane turns can run for hours, so the fixed timer cannot succeed. Fix: (1) record the restart request durably and perform it at the lane's next turn end, deleting the 900 s limit; (2) the persist step must not need a model turn for state that is already durable (backlog, status, inbox); (3) fold in fm-restart-autorelaunch-race: the same durable request record is the lock the watcher auto-relaunch must honor.

fm-restart-autorelaunch-race: a secondmate restart driven by fm-secondmate-restart collided with the watcher's automatic relaunch of the same secondmate (the d3 restart), because the two relaunch paths did not coordinate.

Timeouts 5 and 6 (section 13, the fold item). The wake drain exceeded 120 s and the home-summary refresh exceeded its 60 s deadline in every run (276 times in one day). The cause: every drain, refresh, spawn, teardown and session start re-folds every append-only status log from line 1 in bash through the status_open_decisions fold; three lane logs alone took 131 s wall (d7 1,330 KB 50.3 s, d5 916 KB 66.2 s, d1 421 KB 15.0 s), the main home holds about 5 MB of status lines that are never compacted, and the cost grows with every status line forever while load multiplies it. Fix: (1) incremental fold: persist the open-decision set with the byte offset folded so far per status file, invalidated by inode change or size shrink, so each call folds only new bytes, removing the growth term; (2) compact closed decisions: a resolved key's lines can move to state/archive/ since the drain never needs them; (3) only then re-check whether the 60 s refresh deadline still fires, and do not raise it first. Blast radius: every reader of open decisions (drain, bearings snapshot, crew state, home summary, lane secondmates). Proof: a golden test comparing incremental and full fold on all current status logs gives identical output, and fold time on the d7 log is under 1 s.

PR 40 (fm-omp-lane-wake-unsubmitted): recovers omp lane wakes whose text was left unsubmitted in the composer. It has since landed on main as #48; this change builds on it and does not duplicate it.

Later decisions:

On the new open decision -- S -- split
(The open decision was whether to keep the event-driven secondmate restart (c) and fm-restart-autorelaunch-race (d) in this change. S = split: (c) and (d) are removed from this change and restored to their origin/main behavior; they ship separately. This change keeps (a) the decision fold, F8 and F10.)

On physical closed-decision compaction of status logs: A - checkpoint-only now; physical compaction comes later as a follow-up with a locked append helper.

What Changed

  • Recover omp watcher generations after shutdown without a successor, preserve pending wake and context handoffs, and guard against duplicate Pi/omp watcher instances without reactivating retired Pi runtimes.
  • Reduce warm open-decision fold overhead through fewer subprocesses and combined file identity/size reads; invalidate checkpoints when parsing locales change while retaining append-only status logs.
  • Add rotated Pi/omp lifecycle logs with expiry records identifying the waiter, dependency, configured bound, and elapsed time.

Risk Assessment

⚠️ Medium: The changes affect asynchronous watcher ownership and recovery plus shared checkpoint readers, but the complete source review found no substantiated introduced defect or conflict with the amended intent.

Testing

Targeted regressions and real drain, snapshot, omp, and Pi scenarios passed after correcting disposable setup/oracle issues and sharding the exhaustive golden check without reducing coverage. Warm production folds on a 1.4 MB synthetic log took 18–34 ms. CLI transcripts, lifecycle records, and screenshots rendered from actual ANSI captures were retained; fixtures were removed and no owned product processes remained. No full repository suite or other pipeline phase was run.

  • Live validation: ✅ go - 13 of 14 scenarios driven live against the product
Scenario Result Live Evidence
Drain a growing status log, retain buried blockers, and remove only resolved keys from the checkpoint ✅ pass live round3/fold-live/drain-*.stdout, lifecycle-read-probe.tsv, and checkpoints/
Warm production fold reads of a d7-sized synthetic history complete below one second ✅ pass live round3/fold-live/benchmark.json
Replace, shrink, corrupt, or finish a partially appended log without retaining stale decisions ✅ pass live round3/fold-live/adversarial-read-probe.tsv and drain-*-rebuild.stdout
Change parsing locale and rebuild the checkpoint using the new whitespace interpretation ✅ pass live round3/fold-cli-locale-observations.json
Read ship, scout, and secondmate decisions with identical seeded and full-fold CLI output ✅ pass live round3/fold-cli-golden-observations.json and fold-cli-*-seeded/unseeded.txt
Generate a fleet snapshot without losing cached secondmate decisions or modifying the live checkpoint ✅ pass live round3/fold-fleet-snapshot.json and fold-fleet-snapshot-observations.json
Load the omp watcher twice and retain one current arm ✅ pass live round3/omp-live-observations.json and omp-live-lifecycle-pre-rotation.log
Recover an omp shutdown without a successor, but avoid double-arming when a successor arrives ✅ pass live round3/omp-live-observations.json and omp-live-lifecycle-pre-rotation.log
Deliver an omp wake after missing-successor recovery behind an advisor note without submitting an operator draft ✅ pass live round3/omp-model-requests.json and omp-recovered-wake.png
Reload Pi repeatedly with duplicate entries, then refuse false active ownership when replacement loading fails ✅ pass live round3/pi-cli-observations.json, pi-cli-lifecycle.log, and pi-cli-terminal.png
Rotate the omp lifecycle log without disrupting the current watcher ✅ pass live round3/omp-rotated-lifecycle.log, omp-rotated-lifecycle.log.1, and omp-live-observations.json
Expire real omp arm, host-readiness, and retirement waits with one attributable record per expiry ✅ pass live round3/omp-default-expiry-lifecycle.log and omp-live-lifecycle.log
Expire real Pi readiness and terminal-shutdown waits with one attributable record per expiry ✅ pass live round3/pi-default-expiry-lifecycle.log and pi-quit-expiry-lifecycle.log
Benchmark the exact private d7 status corpus ⏸️ untested no The supplied RCA directory and private d7 log were absent from the worktree. A larger synthetic history was built and benchmarked live, but it cannot establish an exact private-corpus result. Operator…
Evidence: Complete test-phase execution ledger

Source: Complete test-phase execution ledger

{
  "scope": "Checkpoint-only fold, F8, F10; captain-split restart/race work excluded",
  "runtimeVersions": {
    "omp": "18.8.1",
    "pi": "1.0.2"
  },
  "productEvidence": [
    "Real wake-drain growth/resolution and adversarial checkpoint invalidation",
    "1,406,009-byte synthetic log; fourteen loaded production fold timings: 0.018-0.034 seconds",
    "Real CLI C -> C.UTF-8 -> C with EM SPACE: default -> api -> default; 41 bytes refolded each time",
    "Real seeded/unseeded drain equality for ship, scout and secondmate",
    "Real fleet snapshot --json in private tmux preserves four secondmate decisions and live checkpoint",
    "Real omp: duplicate factories retain one arm, missing successor self-heals, shutdown/start avoids double arm",
    "Real omp advisor tail and retained context: recovery/status wakes start real turns and invoke wake-drain while draft remains unsent",
    "Real omp lifecycle rotation and frozen-child shutdown expiry",
    "Real Pi CLI: duplicate entries, three reloads, failed reload retains handoff marker and logs successor-missing",
    "Real omp arm readiness 12000ms, host readiness 30000ms, unready retirement 1000ms",
    "Real Pi arm readiness 12000ms, unready retirement 1000ms, Ctrl-D shutdown retirement 1000ms"
  ],
  "targetedChecks": "targeted-check-ledger.json; all final selectors and exhaustive golden task-kind shards passed",
  "workarounds": [
    "Vendor TypeBox loading required disposable top-level production extension copy with only lifecycle-event injection seam",
    "Short marked fm-lab homes and private 120x40 tmux sockets",
    "Stock-layout shell gateway removes extension-generated overrides; no gate-refusal bypass",
    "SDK-only Pi attempt did not complete; genuine Pi CLI did",
    "Final CLIs selected isolated local lab/m1 provider instead of ambient provider that returned quota error",
    "Corrected disposable one-shot PID, stopped-child TERM and log-newline-window oracles",
    "Monolithic golden selector exceeded 300s command deadline; identical assertions passed task-kind shards without reducing offsets or changing product bounds",
    "Snapshot home-input correctly excludes secondmates; final projection proof used --json"
  ],
  "visualEvidence": "PNG screenshots of exact captured ANSI grids, with raw ANSI and rendered HTML; not native desktop screenshots or reconstructed mock UI",
  "limitation": "Private d7 corpus/RCA report absent; larger synthetic surrogate driven live",
  "cleanup": "final-cleanup.json verifies no matching owned product processes and all created worktree fixtures/local dependencies/test runners removed; lab homes removed by driver finally blocks",
  "phaseBoundary": "No full repository suite, lint/format/static analysis, pipeline control, push, PR or CI; no retained production/source edits",
  "evidenceWriteNote": "Specialized write returned Internal error twice without creating ledger; reported tool issue and used existing Node evidence-writing route"
}
Evidence: Real drain surfaces buried decisions

Source: Real drain surfaces buried decisions

OPEN DECISIONS (still open, folded from the durable status logs - not just the latest line):
synthetic-secondmate [key=api-shape] needs-decision: choose REST or RPC
synthetic-secondmate [key=anchor] blocked: independent decision remains open
OPEN DECISIONS: close one by answering it: bin/fm-send.sh <task> --resolve-key <key> '<answer>'
Evidence: Warm fold timings and returned blocker

Source: Warm fold timings and returned blocker

{
  "method": "Bash builtin time TIMEFORMAT=%R, functions called in one already-loaded /bin/bash process; no library sourcing or interpreter startup in measured block",
  "loaded_bash_pid": "89128",
  "log_bytes": 1406009,
  "log_sha256": "a2a50bc2e4f1fde6322e9b5515c8d1493a6ef81d68c1cec8263287f7123bf243",
  "samples": [
    {
      "function": "status_open_decisions_incremental",
      "sample": 1,
      "seconds": 0.025,
      "exit_code": 0,
      "output": "anchor\tblocked\tindependent decision remains open",
      "output_file": "~/.no-mistakes/evidence/01M4EYP71G0F1N9FSDAAWHXV2B/round3/fold-live/warm-benchmark/status_open_decisions_incremental-1.output"
    },
    {
      "function": "status_open_decisions_incremental",
      "sample": 2,
      "seconds": 0.021,
      "exit_code": 0,
      "output": "anchor\tblocked\tindependent decision remains open",
      "output_file": "~/.no-mistakes/evidence/01M4EYP71G0F1N9FSDAAWHXV2B/round3/fold-live/warm-benchmark/status_open_decisions_incremental-2.output"
    },
    {
      "function": "status_open_decisions_incremental",
      "sample": 3,
      "seconds": 0.02,
      "exit_code": 0,
      "output": "anchor\tblocked\tindependent decision remains open",
      "output_file": "~/.no-mistakes/evidence/01M4EYP71G0F1N9FSDAAWHXV2B/round3/fold-live/warm-benchmark/status_open_decisions_incremental-3.output"
    },
    {
      "function": "status_open_decisions_incremental",
      "sample": 4,
      "seconds": 0.025,
      "exit_code": 0,
      "output": "anchor\tblocked\tindependent decision remains open",
      "output_file": "~/.no-mistakes/evidence/01M4EYP71G0F1N9FSDAAWHXV2B/round3/fold-live/warm-benchmark/status_open_decisions_incremental-4.output"
    },
    {
      "function": "status_open_decisions_incremental",
      "sample": 5,
      "seconds": 0.029,
      "exit_code": 0,
      "output": "anchor\tblocked\tindependent decision remains open",
      "output_file": "~/.no-mistakes/evidence/01M4EYP71G0F1N9FSDAAWHXV2B/round3/fold-live/warm-benchmark/status_open_decisions_incremental-5.output"
    },
    {
      "function": "status_open_decisions_incremental",
      "sample": 6,
      "seconds": 0.034,
      "exit_code": 0,
      "output": "anchor\tblocked\tindependent decision remains open",
      "output_file": "~/.no-mistakes/evidence/01M4EYP71G0F1N9FSDAAWHXV2B/round3/fold-live/warm-benchmark/status_open_decisions_incremental-6.output"
    },
    {
      "function": "status_open_decisions_incremental",
      "sample": 7,
      "seconds": 0.026,
      "exit_code": 0,
      "output": "anchor\tblocked\tindependent decision remains open",
      "output_file": "~/.no-mistakes/evidence/01M4EYP71G0F1N9FSDAAWHXV2B/round3/fold-live/warm-benchmark/status_open_decisions_incremental-7.output"
    },
    {
      "function": "status_open_decisions",
      "sample": 1,
      "seconds": 0.02,
      "exit_code": 0,
      "output": "anchor\tblocked\tindependent decision remains open",
      "output_file": "~/.no-mistakes/evidence/01M4EYP71G0F1N9FSDAAWHXV2B/round3/fold-live/warm-benchmark/status_open_decisions-1.output"
    },
    {
      "function": "status_open_decisions",
      "sample": 2,
      "seconds": 0.024,
      "exit_code": 0,
      "output": "anchor\tblocked\tindependent decision remains open",
      "output_file": "~/.no-mistakes/evidence/01M4EYP71G0F1N9FSDAAWHXV2B/round3/fold-live/warm-benchmark/status_open_decisions-2.output"
    },
    {
      "function": "status_open_decisions",
      "sample": 3,
      "seconds": 0.021,
      "exit_code": 0,
      "output": "anchor\tblocked\tindependent decision remains open",
      "output_file": "~/.no-mistakes/evidence/01M4EYP71G0F1N9FSDAAWHXV2B/round3/fold-live/warm-benchmark/status_open_decisions-3.output"
    },
    {
      "function": "status_open_decisions",
      "sample": 4,
      "seconds": 0.018,
      "exit_code": 0,
      "output": "anchor\tblocked\tindependent decision remains open",
      "output_file": "~/.no-mistakes/evidence/01M4EYP71G0F1N9FSDAAWHXV2B/round3/fold-live/warm-benchmark/status_open_decisions-4.output"
    },
    {
      "function": "status_open_decisions",
      "sample": 5,
      "seconds": 0.024,
      "exit_code": 0,
      "output": "anchor\tblocked\tindependent decision remains open",
      "output_file": "~/.no-mistakes/evidence/01M4EYP71G0F1N9FSDAAWHXV2B/round3/fold-live/warm-benchmark/status_open_decisions-5.output"
    },
    {
      "function": "status_open_decisions",
      "sample": 6,
      "seconds": 0.027,
      "exit_code": 0,
      "output": "anchor\tblocked\tindependent decision remains open",
      "output_file": "~/.no-mistakes/evidence/01M4EYP71G0F1N9FSDAAWHXV2B/round3/fold-live/warm-benchmark/status_open_decisions-6.output"
    },
    {
      "function": "status_open_decisions",
      "sample": 7,
      "seconds": 0.019,
      "exit_code": 0,
      "output": "anchor\tblocked\tindependent decision remains open",
      "output_file": "~/.no-mistakes/evidence/01M4EYP71G0F1N9FSDAAWHXV2B/round3/fold-live/warm-benchmark/status_open_decisions-7.output"
    }
  ],
  "acceptance_seconds": 1.0
}
Evidence: Real locale-dependent parsing and checkpoint rebuilding

Source: Real locale-dependent parsing and checkpoint rebuilding

[
  {
    "locale": "C",
    "key": "default",
    "logBytes": 41,
    "foldedBytes": 41,
    "cursorSignature": "version=11:secondmate:ctype=C:collate=C",
    "output": "OPEN DECISIONS (still open, folded from the durable status logs - not just the latest line):\ntask needs-decision:  [key=api] choose café\nOPEN DECISIONS: close one by answering it: bin/fm-send.sh <task> --resolve-key <key> '<answer>'\n"
  },
  {
    "locale": "C.UTF-8",
    "key": "api",
    "logBytes": 41,
    "foldedBytes": 41,
    "cursorSignature": "version=11:secondmate:ctype=C.UTF-8:collate=C.UTF-8",
    "output": "OPEN DECISIONS (still open, folded from the durable status logs - not just the latest line):\ntask [key=api] needs-decision: choose café\nOPEN DECISIONS: close one by answering it: bin/fm-send.sh <task> --resolve-key <key> '<answer>'\n"
  },
  {
    "locale": "C",
    "key": "default",
    "logBytes": 41,
    "foldedBytes": 41,
    "cursorSignature": "version=11:secondmate:ctype=C:collate=C",
    "output": "OPEN DECISIONS (still open, folded from the durable status logs - not just the latest line):\ntask needs-decision:  [key=api] choose café\nOPEN DECISIONS: close one by answering it: bin/fm-send.sh <task> --resolve-key <key> '<answer>'\n"
  }
]
Evidence: Real fleet snapshot preserves secondmate decisions

Source: Real fleet snapshot preserves secondmate decisions

{
  "schema": "fm-fleet-snapshot.v1",
  "generated": "2026-10-09T12:53:21Z",
  "fm_home": "~/tmp/fm-lab.BJ1taS",
  "roots": {
    "fm_root": "~/.no-mistakes/worktrees/32d18ed9638d/01M4EYP71G0F1N9FSDAAWHXV2B",
    "state": "~/tmp/fm-lab.BJ1taS/state",
    "data": "~/tmp/fm-lab.BJ1taS/data",
    "config": "~/tmp/fm-lab.BJ1taS/config",
    "projects": "~/tmp/fm-lab.BJ1taS/projects"
  },
  "backlog": {
    "path": "~/tmp/fm-lab.BJ1taS/data/backlog.md",
    "present": false,
    "records": []
  },
  "tasks": [
    {
      "id": "task",
      "kind": "secondmate",
      "harness": "",
      "mode": "",
      "yolo": "",
      "branch": null,
      "project": "",
      "spawn_gen": null,
      "backend": "tmux",
      "remote": null,
      "paths": {
        "meta": {
          "path": "~/tmp/fm-lab.BJ1taS/state/task.meta",
          "present": true
        },
        "status_log": {
          "path": "~/tmp/fm-lab.BJ1taS/state/task.status",
          "present": true,
          "kind": "event_history",
          "last_event": {
            "state": "working",
            "note": "unrelated latest",
            "raw": "working: unrelated latest",
            "age_seconds": null
          }
        },
        "worktree": {
          "path": null,
          "present": false
        },
        "home": {
          "path": null,
          "present": false
        },
        "report": {
          "path": "~/tmp/fm-lab.BJ1taS/data/task/report.md",
          "present": false
        }
      },
      "secondmate_projects": [],
      "current_state": {
        "state": "unknown",
        "source": "none",
        "detail": "worktree gone (torn down?)",
        "raw": "state: unknown · source: none · worktree gone (torn down?)",
        "observed_at": "2026-10-09T12:53:21Z",
        "freshness": "fresh"
      },
      "endpoint": {
        "target": null,
        "exists": null,
        "agent_alive": "not_checked",
        "status": "unknown",
        "observed_at": "2026-10-09T12:53:21Z",
        "freshness": "fresh"
      },
      "pr": {
        "url": null,
        "source": "absent",
        "head": null
      },
      "hints": {
        "pending_decision": true,
        "blocked_event": true,
        "open_decisions": [
          {
            "key": "beta",
            "verb": "blocked",
            "summary": "retained"
          },
          {
            "key": "pending-reply-abcd",
            "verb": "needs-decision",
            "summary": "pending-reply-missed: response absent"
          },
          {
            "key": "gamma",
            "verb": "needs-decision",
            "summary": "after terminal"
          },
          {
            "key": "delta",
            "verb": "needs-decision",
            "summary": "final open"
          }
        ],
        "scout_report_present": false,
        "last_event_text": "working: unrelated latest"
      },
      "actions": {
        "send": "bin/fm-send.sh fm-task '<request>'",
        "watch": "read status/doc return channel; do not routinely fm-peek a secondmate for answers",
        "return_channel_note": "Secondmate answers come back through status/doc paths after a marked fm-send request."
      },
      "backlog": null
    }
  ],
  "main_inventory": {
    "valid": true,
    "reason": null,
    "orphan_in_flight": [],
    "unstructured_current_count": 0
  },
  "contributions": {
    "known": 0,
    "checked": 0,
    "counts": {
      "captain": 0,
      "fleet": 0,
      "maintainer": 0,
      "nobody": 0
    },
    "unmeasured": 0,
    "complete": false,
    "proven_clear": false,
    "stale_verdicts": 0,
    "missing_verdicts": 0,
    "unreadable_records": 1,
    "valid_until": 900,
    "captain": [],
    "captain_omitted": 0
  },
  "scout_reports": [],
  "secondmate_current": {
    "registry": {
      "present": false,
      "available": true,
      "complete": true,
      "reason": null,
      "provenance": "registered-table",
      "path": "~/tmp/fm-lab.BJ1taS/data/secondmates.md",
      "freshness": {
        "status": "fresh",
        "observed_at": "2026-10-09T12:53:21Z"
      },
      "records": [],
      "input_truncated": false,
      "records_truncated": false,
      "reasons": [],
      "lines_in_window": 0,
      "records_in_window": 0
    },
    "records": [
      {
        "id": "task",
        "home": null,
        "host": null,
        "remote": false,
        "registered": false,
        "spawn_gen": null,
        "current": {
          "state": "unknown",
          "reason": "secondmate metadata is not registered"
        },
        "invalidity": null,
        "reconcile_inventory": null,
        "provenance": {
          "selected": "parent-event-fallback",
          "structured_home": null,
          "parent_event_role": "fallback-only-not-current"
        },
        "freshness": {
          "status": "historical-event",
          "observed_at": "2026-10-09T12:53:21Z",
          "age_seconds": 3
        },
        "active_children": [],
        "decisions_open": [],
        "holds": [],
        "queued": [],
        "landed": [],
        "endpoints": [],
        "counts": {
          "active_children": 0,
          "decisions_open": 0,
          "holds": 0,
          "queued": 0,
          "landed": 0,
          "endpoints": 0
        },
        "omitted": [],
        "parent_event": {
          "raw": "working: unrelated latest",
          "note": "unrelated latest",
          "age_seconds": null,
          "open_activities": [
            {
              "key": "default",
              "verb": "working",
              "summary": "unrelated latest"
            }
          ],
          "open_decisions": [
            {
              "key": "beta",
              "verb": "blocked",
              "summary": "retained"
            },
            {
              "key": "pending-reply-abcd",
              "verb": "needs-decision",
              "summary": "pending-reply-missed: response absent"
            },
            {
              "key": "gamma",
              "verb": "needs-decision",
              "summary": "after terminal"
            },
            {
              "key": "delta",
              "verb": "needs-decision",
              "summary": "final open"
            }
          ],
          "activity_scan": {
            "records": [
              {
                "key": "default",
                "verb": "working",
                "summary": "unrelated latest"
              }
            ],
            "available": true,
            "input_truncated": false,
            "retained_truncated": false,
            "reasons": [],
            "lines_in_window": 13,
            "records_in_window": 1
          }
        },
        "terminal_evidence": {
          "provenance": "parent-direct-report-terminal",
          "trust": "untrusted-supplement",
          "captured": false,
          "observed_at": "2026-10-09T12:53:21Z",
          "freshness": "unknown",
          "reason": "no recorded endpoint",
          "lines": 0,
          "bytes": 0,
          "event_note_seen": false,
          "contradiction": false
        },
        "contradiction": false
      }
    ],
    "total_registered": 0,
    "total": 1,
    "shown": 1,
    "truncated": 0
  },
  "secondmate_landed": {
    "records": [],
    "truncated": [],
    "unreadable": [
      "<task: unavailable>"
    ],
    "partial": []
  },
  "secondmate_guidance": {
    "note": "For kind=secondmate, bearings selects validated structured state from that registered home; parent events and bounded terminal evidence are fallback-only supplements and never current-state authority."
  }
}
![Recovered omp wake runs while operator draft remains unsent](https://github.com/user-attachments/assets/0770e2ef-8e1d-4673-87f1-ad9f7045dda3) - Evidence: [Recovered omp wake runs while operator draft remains unsent](https://github.com/MrGTV-love/firstmate/blob/7956c1a3d497e248bd748fbe5bf342126d942ab0/.no-mistakes/evidence/fm/fm-lifecycle-phase3/round3/omp-recovered-wake.png)
Evidence: omp lifecycle transitions before rotation

Source: omp lifecycle transitions before rotation

2026-10-09T12:44:52.695Z pid=47750 instance=1 event=generation-create generation=1 cause=factory-bind
2026-10-09T12:44:52.695Z pid=47750 instance=1 event=generation-activate generation=1 cause=factory-bind
2026-10-09T12:44:52.695Z pid=47750 instance=1 event=factory-bind generation=1
2026-10-09T12:44:52.696Z pid=47750 instance=2 event=generation-create generation=1 cause=factory-bind
2026-10-09T12:44:52.696Z pid=47750 instance=2 event=generation-activate generation=1 cause=factory-bind
2026-10-09T12:44:52.696Z pid=47750 instance=2 event=factory-bind generation=1 superseded=1
2026-10-09T12:44:52.696Z pid=47750 instance=1 event=instance-retired generation=1 by=2
2026-10-09T12:44:52.697Z pid=47750 instance=1 event=generation-stop generation=1 cause=factory-retire
2026-10-09T12:44:53.602Z pid=47750 instance=1 event=session_start-ignored reason=superseded-instance
2026-10-09T12:44:53.602Z pid=47750 instance=2 event=session_start generation=1 stopping=false
2026-10-09T12:44:53.602Z pid=47750 instance=2 event=generation-activate generation=1 cause=session_start
2026-10-09T12:44:59.844Z pid=47750 instance=2 event=session_shutdown generation=1
2026-10-09T12:45:00.854Z pid=47750 instance=2 event=bound-expired waiter=omp-watch-extension waited-on=shutdown-arm-close bound=1000ms actual=1007ms
2026-10-09T12:45:00.854Z pid=47750 instance=2 event=generation-stop generation=1
2026-10-09T12:45:15.855Z pid=47750 instance=2 event=bound-expired waiter=omp-watch-extension waited-on=session_start bound=15000ms actual=15001ms outcome=self-heal
2026-10-09T12:45:15.855Z pid=47750 instance=2 event=generation-create generation=2 cause=self-heal
2026-10-09T12:45:15.855Z pid=47750 instance=2 event=generation-activate generation=2 cause=self-heal
2026-10-09T12:45:15.868Z pid=47750 instance=2 event=self-heal generation=2 ok=true
2026-10-09T12:45:19.558Z pid=47750 instance=2 event=session_shutdown generation=2
2026-10-09T12:45:20.560Z pid=47750 instance=2 event=bound-expired waiter=omp-watch-extension waited-on=shutdown-arm-close bound=1000ms actual=1002ms
2026-10-09T12:45:20.560Z pid=47750 instance=2 event=generation-stop generation=2
2026-10-09T12:45:20.561Z pid=47750 instance=2 event=session_start generation=2 stopping=true
2026-10-09T12:45:20.561Z pid=47750 instance=2 event=generation-create generation=3 cause=session_start
2026-10-09T12:45:20.561Z pid=47750 instance=2 event=generation-activate generation=3 cause=session_start
2026-10-09T12:45:45.560Z pid=47750 instance=2 event=session_shutdown generation=3
2026-10-09T12:45:46.551Z pid=47750 instance=2 event=generation-stop generation=3
2026-10-09T12:46:01.562Z pid=47750 instance=2 event=bound-expired waiter=omp-watch-extension waited-on=session_start bound=15000ms actual=15006ms outcome=self-heal
2026-10-09T12:46:01.564Z pid=47750 instance=2 event=generation-create generation=4 cause=self-heal
2026-10-09T12:46:01.564Z pid=47750 instance=2 event=generation-activate generation=4 cause=self-heal
2026-10-09T12:46:01.591Z pid=47750 instance=2 event=self-heal generation=4 ok=true
![Real Pi failed-reload terminal surface](https://github.com/user-attachments/assets/1aec9886-ecc8-4ef4-8b89-b0ca2a452845) - Evidence: [Real Pi failed-reload terminal surface](https://github.com/MrGTV-love/firstmate/blob/7956c1a3d497e248bd748fbe5bf342126d942ab0/.no-mistakes/evidence/fm/fm-lifecycle-phase3/round3/pi-cli-terminal.png)
Evidence: Pi reload ownership and successor-missing expiry

Source: Pi reload ownership and successor-missing expiry

2026-10-09T12:39:55.587Z pid=76421 instance=1 event=generation-create generation=1 cause=factory-bind
2026-10-09T12:39:55.587Z pid=76421 instance=1 event=generation-activate generation=1 cause=factory-bind
2026-10-09T12:39:55.587Z pid=76421 instance=1 event=factory-bind generation=1
2026-10-09T12:39:55.633Z pid=76421 instance=1 event=duplicate-load-ignored
2026-10-09T12:39:56.394Z pid=76421 instance=1 event=session_start generation=1 stopping=false
2026-10-09T12:39:56.394Z pid=76421 instance=1 event=generation-activate generation=1 cause=session_start
2026-10-09T12:40:03.278Z pid=76421 instance=1 event=session_shutdown generation=1 reason=reload
2026-10-09T12:40:03.280Z pid=76421 instance=1 event=generation-stop generation=1 replacement=true
2026-10-09T12:40:03.395Z pid=76421 instance=2 event=generation-create generation=2 cause=factory-bind
2026-10-09T12:40:03.395Z pid=76421 instance=2 event=generation-activate generation=2 cause=factory-bind
2026-10-09T12:40:03.395Z pid=76421 instance=2 event=factory-bind generation=2 superseded=1
2026-10-09T12:40:03.396Z pid=76421 instance=1 event=instance-retired generation=1 by=2
2026-10-09T12:40:03.397Z pid=76421 instance=1 event=generation-stop generation=1 cause=factory-retire
2026-10-09T12:40:03.407Z pid=76421 instance=2 event=duplicate-load-ignored
2026-10-09T12:40:03.407Z pid=76421 instance=2 event=session_start generation=2 stopping=false
2026-10-09T12:40:03.408Z pid=76421 instance=2 event=generation-activate generation=2 cause=session_start
2026-10-09T12:40:05.796Z pid=76421 instance=2 event=session_shutdown generation=2 reason=reload
2026-10-09T12:40:05.798Z pid=76421 instance=2 event=generation-stop generation=2 replacement=true
2026-10-09T12:40:05.830Z pid=76421 instance=3 event=generation-create generation=3 cause=factory-bind
2026-10-09T12:40:05.830Z pid=76421 instance=3 event=generation-activate generation=3 cause=factory-bind
2026-10-09T12:40:05.830Z pid=76421 instance=3 event=factory-bind generation=3 superseded=2
2026-10-09T12:40:05.830Z pid=76421 instance=2 event=instance-retired generation=2 by=3
2026-10-09T12:40:05.831Z pid=76421 instance=2 event=generation-stop generation=2 cause=factory-retire
2026-10-09T12:40:05.834Z pid=76421 instance=3 event=duplicate-load-ignored
2026-10-09T12:40:05.835Z pid=76421 instance=3 event=session_start generation=3 stopping=false
2026-10-09T12:40:05.835Z pid=76421 instance=3 event=generation-activate generation=3 cause=session_start
2026-10-09T12:40:07.650Z pid=76421 instance=3 event=session_shutdown generation=3 reason=reload
2026-10-09T12:40:07.652Z pid=76421 instance=3 event=generation-stop generation=3 replacement=true
2026-10-09T12:40:07.681Z pid=76421 instance=4 event=generation-create generation=4 cause=factory-bind
2026-10-09T12:40:07.681Z pid=76421 instance=4 event=generation-activate generation=4 cause=factory-bind
2026-10-09T12:40:07.681Z pid=76421 instance=4 event=factory-bind generation=4 superseded=3
2026-10-09T12:40:07.682Z pid=76421 instance=3 event=instance-retired generation=3 by=4
2026-10-09T12:40:07.684Z pid=76421 instance=3 event=generation-stop generation=3 cause=factory-retire
2026-10-09T12:40:07.687Z pid=76421 instance=4 event=duplicate-load-ignored
2026-10-09T12:40:07.688Z pid=76421 instance=4 event=session_start generation=4 stopping=false
2026-10-09T12:40:07.688Z pid=76421 instance=4 event=generation-activate generation=4 cause=session_start
2026-10-09T12:40:09.380Z pid=76421 instance=4 event=session_shutdown generation=4 reason=reload
2026-10-09T12:40:09.381Z pid=76421 instance=4 event=generation-stop generation=4 replacement=true
2026-10-09T12:40:24.382Z pid=76421 instance=4 event=bound-expired waiter=pi-watch-extension waited-on=session_start bound=15000ms actual=15001ms outcome=successor-missing
2026-10-09T12:40:24.382Z pid=76421 instance=4 event=successor-missing generation=4 message=watcher:_not_armed_-_Pi_replacement_runtime_did_not_start;_reload_the_extension_in_a_live_session
Evidence: Default omp readiness and retirement expiry records

Source: Default omp readiness and retirement expiry records

2026-10-09T12:47:45.626Z pid=82577 instance=1 event=generation-create generation=1 cause=factory-bind
2026-10-09T12:47:45.626Z pid=82577 instance=1 event=generation-activate generation=1 cause=factory-bind
2026-10-09T12:47:45.626Z pid=82577 instance=1 event=factory-bind generation=1
2026-10-09T12:47:45.799Z pid=82577 instance=1 event=session_start generation=1 stopping=false
2026-10-09T12:47:45.799Z pid=82577 instance=1 event=generation-activate generation=1 cause=session_start
2026-10-09T12:48:09.995Z pid=82577 instance=1 event=bound-expired waiter=omp-watch-extension waited-on=arm-readiness bound=12000ms actual=12002ms
2026-10-09T12:48:10.996Z pid=82577 instance=1 event=bound-expired waiter=omp-watch-extension waited-on=unready-arm-close bound=1000ms actual=1001ms
2026-10-09T12:48:15.393Z pid=82577 instance=1 event=session_shutdown generation=1
2026-10-09T12:48:16.017Z pid=82577 instance=1 event=generation-stop generation=1
2026-10-09T12:48:16.018Z pid=82577 instance=1 event=session_start generation=1 stopping=true
2026-10-09T12:48:16.018Z pid=82577 instance=1 event=generation-create generation=2 cause=session_start
2026-10-09T12:48:16.018Z pid=82577 instance=1 event=generation-activate generation=2 cause=session_start
2026-10-09T12:48:49.448Z pid=82577 instance=1 event=bound-expired waiter=omp-watch-extension waited-on=supervision-host-readiness bound=30000ms actual=30002ms
2026-10-09T12:48:50.450Z pid=82577 instance=1 event=bound-expired waiter=omp-watch-extension waited-on=unready-arm-close bound=1000ms actual=1002ms
Evidence: Default Pi readiness and retirement expiry records

Source: Default Pi readiness and retirement expiry records

2026-10-09T12:49:11.069Z pid=7957 instance=1 event=generation-create generation=1 cause=factory-bind
2026-10-09T12:49:11.070Z pid=7957 instance=1 event=generation-activate generation=1 cause=factory-bind
2026-10-09T12:49:11.070Z pid=7957 instance=1 event=factory-bind generation=1
2026-10-09T12:49:11.894Z pid=7957 instance=1 event=session_start generation=1 stopping=false
2026-10-09T12:49:11.895Z pid=7957 instance=1 event=generation-activate generation=1 cause=session_start
2026-10-09T12:50:00.678Z pid=7957 instance=1 event=bound-expired waiter=pi-watch-extension waited-on=arm-readiness bound=12000ms actual=12002ms
2026-10-09T12:50:01.681Z pid=7957 instance=1 event=bound-expired waiter=pi-watch-extension waited-on=unready-arm-close bound=1000ms actual=1003ms
Evidence: Pi terminal-quit retirement expiry

Source: Pi terminal-quit retirement expiry

2026-10-09T12:55:55.476Z pid=49702 instance=1 event=generation-create generation=1 cause=factory-bind
2026-10-09T12:55:55.476Z pid=49702 instance=1 event=generation-activate generation=1 cause=factory-bind
2026-10-09T12:55:55.476Z pid=49702 instance=1 event=factory-bind generation=1
2026-10-09T12:55:55.533Z pid=49702 instance=1 event=session_start generation=1 stopping=false
2026-10-09T12:55:55.534Z pid=49702 instance=1 event=generation-activate generation=1 cause=session_start
2026-10-09T12:55:58.025Z pid=49702 instance=1 event=session_shutdown generation=1 reason=quit
2026-10-09T12:55:59.029Z pid=49702 instance=1 event=bound-expired waiter=pi-watch-extension waited-on=shutdown-arm-close bound=1000ms actual=1003ms
2026-10-09T12:55:59.029Z pid=49702 instance=1 event=generation-stop generation=1 replacement=false
Evidence: Disposable fixture and process cleanup

Source: Disposable fixture and process cleanup

{
  "observedAt": "2026-10-09T12:57:36.794Z",
  "ownedProductProcesses": [],
  "removedFixtures": [
    ".phase3-live",
    "tests/.phase3-pi-targeted.sh",
    "tests/.phase3-omp-targeted.sh",
    "tests/.phase3-fold-golden-shard.sh"
  ],
  "allOwnedFixturesAbsent": true,
  "temporaryDependencySymlinkAbsent": true,
  "scope": "Only test-created fixtures, local dependencies and test runners removed; existing worktree files and evidence retained"
}
- Outcome: 🔧 2 issues found → auto-fixed ✅ across 3 runs (2h47m17s)

Pipeline

Updates from git push no-mistakes

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

🔧 **Review** - 2 issues found → auto-fixed (6) ✅

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

🔧 **Test** - 2 issues found → auto-fixed ✅
  • ⚠️ bin/fm-classify-lib.sh:291 - The required under-one-second fold performance was not consistently demonstrated. Against a disposable 1,326,045-byte status log, three warm calls timed inside an already-loaded Bash process took 2.321s, 1.481s, and 0.541s. Functional output and incremental byte-read behavior were correct, but this loaded host still exceeded the target twice. The focused checkpoint suite also exceeded its 1800-second command deadline after completing its locale and golden-equivalence cases. These observations do not establish a new regression versus the base, but they leave the required performance acceptance unresolved. Profile the remaining warm-path cost under representative load and provide the private d7 corpus for the requested benchmark; do not raise product bounds.
  • 🚨 live validation verdict: no-go (10 of 11 scenarios were driven live against the product); failed: Warm folding of a d7-sized synthetic status log remains below one second
  • Live validation: ❌ no-go - 10 of 11 scenarios driven live against the product
Scenario Result Live Evidence
Repeated drains retain open decisions and apply only appended resolutions ✅ pass live fold-product-operations.json and fold-read-probe.tsv: the appended transition read exactly 89 bytes, removed api, and retained deployment.
Replacing or truncating a status log discards stale cached decisions ✅ pass live fold-product-operations.json: real drains displayed only the new rewrite/inode decisions after each boundary change.
Changing parsing locale rebuilds the checkpoint without changing full-fold semantics ✅ pass live fold-locale-boundaries.json: C-to-C.UTF-8 and reverse reads matched checkpoint-free references and published the appropriate locale signature.
Incremental decision state matches full folding across generated lifecycle events ✅ pass live fold-golden-transitions.json: real executable folds agreed at every exercised event prefix, including UTF-8 and unfinished final-line input.
Warm folding of a d7-sized synthetic status log remains below one second ❌ fail live fold-warm-benchmark.txt: function-only warm calls took 2.321s, 1.481s, and 0.541s.
Existing private fleet status logs satisfy golden equivalence and the actual d7 benchmark ⏸️ untested no The referenced report and actual fleet logs are private and absent from the worktree. Reading operator fleet data is prohibited, and synthetic fixtures cannot establish equivalence over those exact by…
omp recovers a missing successor and consumes an idle notification without submitting the operator draft ✅ pass live omp-live-lifecycle-pre-rotation.log and omp-recovered-wake.txt: one self-heal followed an injected actual shutdown handler; production status input reached a real model turn behind an advisor note, wi…
Duplicate omp loading and a timely successor leave exactly one monitoring cycle ✅ pass live omp-singleton-processes.json: superseded instance retirement, no additional timed self-heal after successor activation, and exactly one production watcher descendant.
Pi duplicate entrypoints and successful reload retain one registered tool and one current watcher ✅ pass live pi-live-runtime.json: duplicate loading succeeded, the arm tool was idempotent, reload retired the predecessor watcher and started a live successor.
A failed Pi reload diagnoses the missing successor without claiming an active watcher ✅ pass live pi-live-runtime.json and pi-live-lifecycle.log: invalid entrypoints produced real loader errors, one successor-missing expiry, no registered arm tool, and a persisted phase=handoff marker.
Expired waits report bound and actual duration, and lifecycle history rotates ✅ pass live omp-readiness-expiry.log recorded default readiness and retirement expiries; omp-rotated-lifecycle.log and lifecycle.log.1 preserved rotation; omp-live-terminal.png shows the actual diagnostic.
  • env TMPDIR=&#34;$PWD/.phase3-live&#34; bash tests/fm-wake-drain-open-decisions-cursor.test.sh with 300-second and 1800-second command deadlines; both timed out, with the longer attempt completing locale, terminal/kind-transition, and golden-equivalence cases.
  • bin/fm-wake-drain.sh against disposable data: initial open decision, appended resolution/new decision, unchanged read, truncation, and inode replacement.
  • Executed status_open_decisions_incremental and status_open_decisions under C and C.UTF-8, comparing real incremental/read-only folds against checkpoint-free full folds.
  • Compared real incremental and full folds across generated event prefixes, including keyed updates, resolution, captain-held transfer, UTF-8 whitespace, and an unfinished final line.
  • Used Bash builtin time to measure three warm folds of a 1,326,045-byte synthetic log after library loading.
  • node .phase3-live/pi-runtime.mjs using the installed Pi SDK: duplicate explicit entrypoints, idempotent arm tool, successful reload, and failed reload without a successor.
  • bin/fm-herdr-lab.sh provision/run/teardown on a named non-default lab: real omp 18.8.1, duplicate entrypoints, actual production watcher scripts, a disposable local HTTP model, and injected lifecycle events using the live harness context.
  • Drove recovered omp notification delivery behind an advisor note; observed the model receive the notification while the operator draft remained unsent.
  • Exercised successor-before-grace behavior, inspected exactly one production watcher descendant, and verified lifecycle-log rotation.
  • Injected a one-shot arm-launch delay and observed default 12000ms readiness and 1000ms retirement expiry records and the visible diagnostic.
  • Ran targeted omp/Pi extension suites; encountered real-ancestor detection interference and an expiry-fixture startup race. Replaced the expiry fixture's Node child with a shell child that ignores TERM before publishing readiness, without changing bounds.
  • NODE_NO_WARNINGS=1 node tests/watch-lifecycle-expiry.mjs omp &lt;disposable-fixture&gt; &amp;&amp; NODE_NO_WARNINGS=1 node tests/watch-lifecycle-expiry.mjs pi &lt;disposable-fixture&gt; passed after the test-only fix.
  • Captured the actual Herdr ANSI viewport, rendered it to HTML, captured a Chromium screenshot, closed the browser, verified guarded lab teardown, and removed all disposable worktree scaffolding.

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

  • Live validation: ✅ go - 11 of 12 scenarios driven live against the product
Scenario Result Live Evidence
Repeated wake drains preserve buried decisions and clear resolved keys after appends ✅ pass live round2/fold-product-operations.json and round2/fold-read-probe.tsv
Truncating or replacing a status log invalidates its previous checkpoint ✅ pass live round2/fold-truncate.txt and round2/fold-replace-inode.txt
Completing a partial status line produces identical fresh and checkpoint-seeded drain results ✅ pass live round2/fold-live-golden-cli.json; supplemental targeted golden-equivalence check in round2/fold-golden.log
Changing parsing locale rebuilds the checkpoint instead of reusing another interpretation ✅ pass live round2/fold-live-locale-switch.json
Warm folding of a d7-sized representative log stays below one second ✅ pass live round2/fold-warm-benchmark.txt
An omp shutdown without a successor recovers and consumes an idle wake without submitting the draft ✅ pass live round2/omp-retained-context-recovery.txt, round2/omp-live-lifecycle-pre-rotation.log, and round2/omp-model-requests.json
Duplicate omp loads and a timely successor retain exactly one live watcher ✅ pass live round2/omp-singleton-processes.json and round2/omp-live-lifecycle-pre-rotation.log
Duplicate Pi entrypoints and successful reload preserve one arm tool and retire the predecessor ✅ pass live round2/pi-live-runtime.json and round2/pi-live-lifecycle.log
A failed Pi replacement reports missing successor without falsely publishing active ownership ✅ pass live round2/pi-live-runtime.json and round2/pi-live-lifecycle.log
Expired watcher deadlines record waiter, waited-on, bound, and actual duration ✅ pass live round2/omp-default-bound-expiry.log, round2/omp-host-expiry.log, round2/pi-live-lifecycle.log, and round2/omp-live-terminal.png
Lifecycle logging rotates when the active log crosses its size threshold ✅ pass live round2/omp-rotated-lifecycle.log and round2/omp-rotated-lifecycle.log.1
The exact private d7 status corpus folds in under one second ⏸️ untested no The private report and d7 status corpus are absent from this worktree, and reading the operator's real fleet data is prohibited. A larger disposable synthetic log was driven successfully, but that can…
  • bin/fm-wake-drain.sh against disposable homes: bootstrap, append and resolve, unchanged read, truncation, and inode replacement.
  • bash -c &#39;. &#34;$1&#34;; TIMEFORMAT=&#34;seconds=%R&#34;; for n in 1 2 3 4 5; do time status_open_decisions_incremental &#34;$2&#34;; printf &#34;\n&#34;; done&#39; against a 1,357,598-byte synthetic status log.
  • FM_TEST_ONLY=test_seeded_whole_file_fold_matches_a_fold_from_line_one bash tests/fm-wake-drain-open-decisions-cursor.test.sh.
  • FM_TEST_ONLY=test_utf8_whitespace_uses_full_fold_locale bash tests/fm-wake-drain-open-decisions-cursor.test.sh.
  • FM_TEST_ONLY=test_checkpoint_rejects_a_previous_parsing_locale bash tests/fm-wake-drain-open-decisions-cursor.test.sh.
  • Real bin/fm-wake-drain.sh calls under LC_ALL=C, LC_ALL=C.UTF-8, and LC_ALL=C on identical status bytes, followed by UTF-8 keyed resolution.
  • Real bin/fm-wake-drain.sh seeded-versus-fresh output comparisons for ship, scout, and secondmate histories after completing an unterminated line.
  • FM_PI_PACKAGE_DIR=~/.npm-global/lib/node_modules/@earendil-works/pi-coding-agent node .phase3-retest-Y4uDvp/pi-runtime-driver.mjs: real Pi resource loader, duplicate entrypoints, arm-tool execution, successful reload, and deliberately failed replacement loading.
  • bin/fm-herdr-lab.sh provision/run/teardown fm-lab-phase3-retest-29921-24794: real omp 18.8.1 sessions with a sized foreground viewer, isolated homes, and a disposable local HTTP model provider.
  • Real omp lifecycle handlers invoked through disposable fault-injection commands: shutdown without successor, shutdown followed by session start, retained-context wake delivery behind an advisor note, draft preservation, and singleton process inspection.
  • Real omp expiry paths exercised with launch delays: default 12,000 ms arm readiness, 30,000 ms host readiness, and 1,000 ms retirement bounds; lifecycle rotation exercised across the size threshold.
  • Captured the real Herdr ANSI viewport, rendered it to HTML, and saved a Chromium screenshot.
  • Guarded Herdr teardown passed the fleet-state tripwire; disposable homes, drivers, and worktree fixtures were removed, the local provider stopped, and no owned watcher or supervision-host commands remained.

✅ No issues found.

  • Live validation: ✅ go - 13 of 14 scenarios driven live against the product
Scenario Result Live Evidence
Drain a growing status log, retain buried blockers, and remove only resolved keys from the checkpoint ✅ pass live round3/fold-live/drain-*.stdout, lifecycle-read-probe.tsv, and checkpoints/
Warm production fold reads of a d7-sized synthetic history complete below one second ✅ pass live round3/fold-live/benchmark.json
Replace, shrink, corrupt, or finish a partially appended log without retaining stale decisions ✅ pass live round3/fold-live/adversarial-read-probe.tsv and drain-*-rebuild.stdout
Change parsing locale and rebuild the checkpoint using the new whitespace interpretation ✅ pass live round3/fold-cli-locale-observations.json
Read ship, scout, and secondmate decisions with identical seeded and full-fold CLI output ✅ pass live round3/fold-cli-golden-observations.json and fold-cli-*-seeded/unseeded.txt
Generate a fleet snapshot without losing cached secondmate decisions or modifying the live checkpoint ✅ pass live round3/fold-fleet-snapshot.json and fold-fleet-snapshot-observations.json
Load the omp watcher twice and retain one current arm ✅ pass live round3/omp-live-observations.json and omp-live-lifecycle-pre-rotation.log
Recover an omp shutdown without a successor, but avoid double-arming when a successor arrives ✅ pass live round3/omp-live-observations.json and omp-live-lifecycle-pre-rotation.log
Deliver an omp wake after missing-successor recovery behind an advisor note without submitting an operator draft ✅ pass live round3/omp-model-requests.json and omp-recovered-wake.png
Reload Pi repeatedly with duplicate entries, then refuse false active ownership when replacement loading fails ✅ pass live round3/pi-cli-observations.json, pi-cli-lifecycle.log, and pi-cli-terminal.png
Rotate the omp lifecycle log without disrupting the current watcher ✅ pass live round3/omp-rotated-lifecycle.log, omp-rotated-lifecycle.log.1, and omp-live-observations.json
Expire real omp arm, host-readiness, and retirement waits with one attributable record per expiry ✅ pass live round3/omp-default-expiry-lifecycle.log and omp-live-lifecycle.log
Expire real Pi readiness and terminal-shutdown waits with one attributable record per expiry ✅ pass live round3/pi-default-expiry-lifecycle.log and pi-quit-expiry-lifecycle.log
Benchmark the exact private d7 status corpus ⏸️ untested no The supplied RCA directory and private d7 log were absent from the worktree. A larger synthetic history was built and benchmarked live, but it cannot establish an exact private-corpus result. Operator…
  • python3 .round3-fold-live-driver.py: real wake-drain growth, resolution, checkpoint invalidation, and loaded-Bash performance checks.
  • node .phase3-live/fold-cli-golden.mjs: seeded versus unseeded real drain output for ship, scout, and secondmate.
  • node .phase3-live/fold-cli-locale.mjs: real drain under C → C.UTF-8 → C using EM SPACE and UTF-8 text.
  • node .phase3-live/fleet-snapshot-live.mjs: real bin/fm-fleet-snapshot.sh --json on a private tmux socket.
  • node .phase3-live/omp-live.mjs: real omp duplicate loading, missing-successor recovery, successor handoff, advisor-tail wake delivery, draft preservation, rotation, and shutdown expiry.
  • node .phase3-live/pi-cli.mjs: real Pi duplicate loading, three reload handoffs, and deliberately failed reload.
  • node .phase3-live/omp-expiry-live.mjs: real default arm-readiness, host-readiness, and unready-retirement expiry paths.
  • node .phase3-live/pi-expiry-cli.mjs and node .phase3-live/pi-quit-expiry-cli.mjs: real Pi readiness, unready-retirement, and Ctrl-D shutdown expiry paths.
  • Focused Pi regressions: test_watch_instance_rebind_releases_predecessors, test_pi_missing_successor_reports_without_rearming, test_pi_factory_replacement_retires_and_hands_off, and test_pi_lifecycle_deadline_diagnostics.
  • Focused omp regressions: test_watch_extension_heals_a_generation_stopped_without_a_successor, test_watch_extension_is_single_instance_per_home, and test_watch_lifecycle_deadline_diagnostics.
  • FM_TEST_ONLY=test_checkpoint_rejects_a_previous_parsing_locale and FM_TEST_ONLY=test_utf8_whitespace_uses_full_fold_locale against the existing checkpoint suite.
  • The monolithic test_seeded_whole_file_fold_matches_a_fold_from_line_one exceeded its 300-second command deadline; identical assertions subsequently passed in ship, scout, and secondmate shards, retaining every boundary and split offset.
  • Exploratory SDK/loading attempts and disposable-driver setup corrections are recorded in round3/execution-ledger.json; final proofs used genuine CLIs and isolated local providers.
  • Rendered actual private-tmux ANSI captures, captured reviewer-visible PNGs through Chromium, and removed disposable fixtures, local dependencies, and temporary test runners.
✅ **Document** - passed

✅ No issues found.

✅ No issues found.

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

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

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

✅ No issues found.

- Every whole-file open-decision fold starts read-only from the drain's
  per-task checkpoint, so drain, home summary, crew state, and captain-hold
  reads no longer refold megabyte status logs from line 1 (largest lane log:
  19.8 s to 0.08 s, identical output); the snapshot carries the checkpoint onto
  its point-in-time copies. Closed decisions are compacted out of that
  checkpoint; the status logs themselves are not rewritten, because workers
  append without a lock, captain-hold divergence reads closing lines, and the
  presentation and wake cursors are byte offsets into those logs.
- A declared pause for a still-open decision's own key is that decision's
  current state, so an idle worker waiting on its answer reads paused, not
  parked.
- The omp and Pi primary watcher extensions record every lifecycle
  transition, heal a generation stopped without a successor, and keep one
  live instance per home.
- Second-mate restarts are recorded durably and finished by supervision once
  the mate answers and its turn ends, replacing the fixed 900 s wait; the
  restart and the automatic relaunch share one per-mate lock.

- Wake queue: watcher (`bin/fm-watch.sh`) appends rows; `bin/fm-wake-drain.sh` presents them, folds OPEN DECISIONS through the per-task checkpoint (`status_open_decisions_incremental`, the only checkpoint writer), and runs `fm-captain-hold.sh diverged` (whole-file folds).
- Whole-file fold readers: fleet snapshot / home summary (`fm-fleet-snapshot.sh`, per task, plus `fm-crew-state.sh` -> `status_current_line`), `fm-captain-hold.sh` (complete, verify, diverged), `fm-send.sh --resolve-key`, `fm-pending-reply-lib.sh`, `fm-afk-return.sh`, `status_has_open_needs_decision`. All now start read-only from the checkpoint the drain keeps.
- Arm and delivery: omp and Pi primary watcher extensions own one session generation each; the generation spawns `fm-watch-arm.sh` (or the supervision host) and delivers actionable closes to main; the replacement handoff file carries undelivered closes across session replacement.
- Restart: `fm-secondmate-restart.sh` (persist request through `fm-send.sh` + pending-reply record) -> durable restart request -> service under the per-mate liveness lock -> `fm-control.sh relaunch` / `fm-remote-secondmate-relaunch.sh`.
- Auto-relaunch: watcher `secondmate_liveness_tick` and the session-start sweep (`fm-bootstrap.sh`) probe and relaunch dead mates through `fm-secondmate-liveness-lib.sh` under the same per-mate lock.

- (a) Fold: every open-decision reader. Output is byte-identical to the fold from line 1 by construction (the checkpoint is that fold of the prefix) and by the golden test; any doubt about a checkpoint falls back to line 1. Readers stay pure reads; only the drain writes checkpoints. A fold checkpoint is carried onto snapshot copies only when it describes that copy. The fold version moved to the fold's own library (no semantic change, no version bump).
- (b) omp/Pi extensions: generation lifecycle and the arm tool. Self-heal binds exactly the generation `session_start` would, so a late real successor finds a live generation and arms nothing twice; terminal quit on Pi never heals; a superseded Pi instance keeps the stale refusal; a superseded omp instance forwards its arm call to the live one. Lifecycle logging never throws.
- (c) Restart: the persist gate is unchanged (correlated answer); the clock is replaced by the turn-end event; unanswered requests stay with the pending-reply recovery ladder exactly as before; a turn that never ends wakes once after `FM_BUSY_TURN_MAX_SECS` and stays recorded.
- Same-key pause: `status_current_line` reads a standing `paused` declaration for the same key as a still-open decision as that decision's current state; the decision stays open in the fold (OPEN DECISIONS, fleet snapshot), and an open decision under another key, a newer decision, a captain-held transfer, and an active turn keep their precedence. The watcher's stale path is unchanged: `pause_state_class` still answers `none` for a live ordinary crew by design and bounds it to once per declaration per `FM_PAUSE_RESURFACE_SECS`.
- (d) Race: the restart holds the liveness lock across stop and relaunch, so the liveness probe cannot read the gap as a dead endpoint; a liveness relaunch in progress defers the restart; a successful auto-relaunch retires a waiting restart with an outcome instead of leaving it waiting on a dead agent.

- Deleted: `FM_SECONDMATE_PERSIST_WAIT` (900 s). Its cause - a fixed wait on an event (the mate's turn end) that can be hours away - is removed by the event-driven restart (report sections 5 and 9, F9).
- Added, each logging waiter, waited-on, bound, and actual once per expiry: `FM_OMP_SUCCESSOR_GRACE_MS` and `FM_PI_SUCCESSOR_GRACE_MS` (15 s, lifecycle log `event=bound-expired`), and the existing `FM_BUSY_TURN_MAX_SECS` reused for the long-turn restart notice (watcher triage log `bounded wait expired:`).
- No timeout was raised. No composer recognizer was added.

- New golden fold equivalence: every line boundary of a corpus covering every fold rule, for ship, scout, and secondmate, seeded fold == fold from line 1, and the seeded fold reads only the tail; refusal of foreign-reading checkpoints; snapshot carry; opt-in run over real status logs (`FM_FOLD_GOLDEN_DIRS`).
- New restart: queued instead of timed out, finished by supervision, turn-end gate, shared lock with the auto-relaunch, request reuse; watcher surfacing, auto-relaunch retirement, long-turn notice.
- New extension: omp shutdown without start -> exactly one re-arm; shutdown then start -> no double arm; arm call heals; double load -> one live generation and one arm; Pi replacement heal once, real successor no double arm, quit never heals.
- New same-key pause: fold-level precedence cases plus the real `fm-crew-state.sh` and `crew_absorb_class` (parked/none before, paused/paused after; an active turn still reads working); both fail on the base as a negative control.
- Existing suites rerun: fm-secondmate-restart, fm-omp-harness, fm-pi-watch-extension, fm-pi-primary-types (tsc 5.6.3, Pi 1.0.2), fm-wake-drain-open-decisions(-cursor), fm-wake-queue, fm-watch-triage, fm-watch-recovery-loop, fm-secondmate-liveness, and the others listed in the evidence.
@MrGTV-love
MrGTV-love force-pushed the fm/fm-lifecycle-phase3 branch from 0cec3a3 to a891a0a Compare October 9, 2026 13:31
@MrGTV-love MrGTV-love changed the title fix: recover watcher lifecycle gaps and optimize decision folds fix: recover watcher continuity and reduce decision-fold overhead Oct 9, 2026
…Serial 1, 10, and 4: isolated C-locale byte counting so guarded status appends and annotation rendering preserve the caller’s locale when validating fold checkpoints; extended behavioral coverage for multibyte closes, quiet bookkeeping, later worker wakes, and annotation replay. Serial 3: Pi and omp expiry paths now enforce monotonic deadlines and record measured elapsed time, with deterministic early-callback and backwards-clock coverage. No timeout or bound changes; Pi diagnostic-only recovery remains unchanged. Verification passed: reported triage and multi-key send scenarios, six focused queue/annotation scenarios, eleven Pi lifecycle scenarios, eleven omp lifecycle/wake scenarios, and source-aware ShellCheck. The fold suite passed locale invalidation, UTF-8 byte offsets, golden equivalence, partial-line refusal, I/O recovery, and checkpoint carry before its local deadline. Complete local suites were not green: the broad run encountered unrelated lock-timing failures and timed out. Disposable verification files were removed; relevant docs updated
@MrGTV-love
MrGTV-love merged commit 6652c1e into main Oct 9, 2026
22 checks passed
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