Skip to content

fix: deliver stranded and stuck-queue omp watcher wakes - #81

Merged
MrGTV-love merged 15 commits into
mainfrom
fm/fm-omp-wake-delivery-gaps
Oct 9, 2026
Merged

MrGTV-love merged 15 commits into
mainfrom
fm/fm-omp-wake-delivery-gaps

Conversation

@MrGTV-love

Copy link
Copy Markdown
Owner

Intent

I would like all bugs to be fixed so tomorrow can be focused entirely to Vernant and not problems preventning Vernant from getting built.

Context: PR #54 (fix: start omp watcher wakes in idle sessions, merged as b1b8856) fixed new idle-session wake stalls in the omp primary watcher extension. Its worker reported three remaining gaps, and the first one stopped Vernant lanes today: (1) wake text typed by older wiring and left unsent in an idle omp composer is never submitted or cleared - on 2026-10-08 five of seven Vernant lane restarts were refused because each lane's composer held such text, and two idle lanes sat with undelivered wakes until Main pressed Enter; (2) the live guard's restored-wake step already fails on main with omp 18.8.1 (the wake is not seen in the follow-up queue display); (3) the restored-wake check still skips when omp has pending messages.

What Changed

  • .omp/extensions/fm-primary-omp-watch.ts now polls the idle omp editor every three seconds for a complete watcher wake that other wiring left unsent. When the same editor text is seen on two consecutive idle polls, it sends that wake alone through the prompt-starting API and removes only that wake and one blank-line separator, so operator drafts, edited or truncated wakes, and wakes the extension still tracks stay untouched. Delivery is bounded to three attempts per distinct wake text.
  • The restored-wake check no longer skips for good when omp reports pending messages. It waits up to fifteen further two-second rounds for the queue to drain, then resubmits an unconsumed wake through the prompt flow (three attempts per wake) and raises one omp warning notification if no turn starts. A check is now also scheduled after a follow-up send, and the agent_end check replaces a pending one so the editor is read two full seconds after the run ended.
  • tests/fm-omp-harness.test.sh adds stranded-wake, pending-queue, ownership, attempt-bound, and queue-episode scenarios. tests/fm-omp-wake-restore-live-e2e.test.sh adds a lab-only probe extension that records omp's queued and idle answers and every editor change, uses it in place of the queue-panel scrape, and adds a stranded-wake step. docs/watcher-continuity.md, docs/verification/runtime-backends.md, and the omp harness reference describe the new behavior and its known limits.

Risk Assessment

⚠️ Medium: The change adds a 3-second composer poll that submits text and rewrites the operator's editor, plus a bounded stuck-queue flush; traced paths (findStrandedWake boundaries and mark handling, the ownership gate, the attempt bounds, episode resets, the timer replacement on agent_end, the follow-up-scheduled check) behave as documented, and the one known stall (an Escape-cancelled stranded wake blocks later stranded wakes) is a recorded accepted residual, so it is safe to merge with that residual tracked.

Testing

I drove the real installed omp 18.8.7 through bin/fm-herdr-lab.sh in a throwaway fm-lab-* Herdr session. The live guard covers all three gaps in the intent. Run 1 used the default Codex model. In that run, the stranded-wake and idle-wake steps passed, which need no tokens. The model steps could not run because the Codex account had no quota left. I re-ran the guard with FM_OMP_WAKE_RESTORE_LIVE_MODEL=opus, and all 7 live steps passed. This includes the restored-wake step, which failed on main before this change and now runs without a skip. The portable omp harness suite passed 33 of 33. The guard removed its lab, and the worktree is clean. This change has no visual UI, so the evidence is CLI transcripts and no screenshots.

  • Live validation: ✅ go - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
Gap 1: an older wiring leaves wake text unsent in an idle omp composer with an operator draft beside it. The watch extension sends that wake as its own turn, clears only the wake, and keeps the draft… ✅ pass live omp-wake-restore-live-run2-opus.log line 2 'live omp stranded wake' (also ok in run 1)
Gap 2: Esc interrupts a running turn, and omp puts the queued wake back in the composer. The extension sends it again, and the lane handles it (this step failed on main with omp 18.8.x). ✅ pass live omp-wake-restore-live-run2-opus.log line 3 'live omp wake restore: re-submitted a wake that Esc restored'
Gap 3: the restored-wake check runs while omp has pending messages and does not skip. The lab probe confirms the wake is queued, and the step gives a real ok. ✅ pass live omp-wake-restore-live-run2-opus.log has no skip lines, and both restored-wake steps are ok
Guard (adversarial): during the wake re-send, the operator's draft stays exactly as typed and is not sent or changed. ✅ pass live omp-wake-restore-live-run2-opus.log line 4 'left the operator's draft exactly as typed'
Regression: a wake that reaches an idle lane behind an advisor note starts its own turn, and the draft is not sent. ✅ pass live omp-wake-restore-live-run2-opus.log line 1 'live omp idle wake'
Regression: the loaded markers name the lock-holding session after a child omp runs, and a busy composer reads empty or pending and takes a doorbell sent mid-turn. ✅ pass live omp-wake-restore-live-run2-opus.log lines 5-7
Evidence: Live guard run 2 (opus): all 7 steps ok

Source: Live guard run 2 (opus): all 7 steps ok

ok - live omp idle wake: started its own turn for a wake that reached an idle lane behind an advisor note and left the operator draft unsent ok - live omp stranded wake: delivered wake text an older wiring left unsent in an idle composer as its own turn, cleared only that wake, and left the operator draft as typed ok - live omp wake restore: re-submitted a wake that Esc restored to the composer, and the lane handled it ok - live omp wake restore: left the operator's draft exactly as typed while it re-submitted the wake ok - live omp markers: kept both loaded markers on the session pid ok - live omp busy composer: reads empty and pending while a turn runs ok - live omp busy composer: took an injected doorbell mid-turn and left the composer empty exit=0

ok - live omp idle wake: omp (omp/18.8.7) on herdr 0.9.1 started its own turn for a wake that reached an idle lane behind an advisor note and left the operator draft unsent
ok - live omp stranded wake: omp (omp/18.8.7) on herdr 0.9.1 delivered wake text an older wiring left unsent in an idle composer as its own turn, cleared only that wake, and left the operator draft as typed
ok - live omp wake restore: omp (omp/18.8.7) on herdr 0.9.1 re-submitted a wake that Esc restored to the composer, and the lane handled it
ok - live omp wake restore: omp (omp/18.8.7) on herdr 0.9.1 left the operator's draft exactly as typed while it re-submitted the wake
ok - live omp markers: omp (omp/18.8.7) on herdr 0.9.1 kept both loaded markers on the session pid 14110 before parent repair and after the parent resumed
ok - live omp busy composer: omp (omp/18.8.7) on herdr 0.9.1 reads empty and pending while a turn runs
ok - live omp busy composer: omp (omp/18.8.7) on herdr 0.9.1 took an injected doorbell mid-turn and left the composer empty
exit=0
Evidence: Live guard run 1 (default Codex model): steps 4-5 ok, then Codex usage limit

Source: Live guard run 1 (default Codex model): steps 4-5 ok, then Codex usage limit

ok - live omp idle wake: omp (omp/18.8.7) on herdr 0.9.1 started its own turn for a wake that reached an idle lane behind an advisor note and left the operator draft unsent
ok - live omp stranded wake: omp (omp/18.8.7) on herdr 0.9.1 delivered wake text an older wiring left unsent in an idle composer as its own turn, cleared only that wake, and left the operator draft as typed

                                                                                                                                                ████████████
                                                                                                                                                   ██  ██       ▄▀▀▄ █▀▄▀▄ █▀▀▄
                                                                                                                                                   ██  ██       ▀▄▄▀ █ █ █ █▄▄▀
                                                                                                                                                   ▒▒  ██                  █
                                                                                                                                                       ██       v18.8.7

                                                                                                                            Tip: Lint/type errors piling up? `omp cleanse` (or /cleanse right here)
                                                                                                                              hunts project diagnostics and fixes them with parallel subagents — ⎋
                                                                                                                                                            cancels


 FIRSTMATE_OP: v1 launch-brief: Live wake recovery lab: arm watcher when asked, perform only requested checks, and otherwise stay idle.


 Error: Codex error event: The usage limit has been reached (code=usage_limit_reached)

 Error: Retry failed after 1 attempts: Provider requested 365560302ms wait, exceeds retry.maxDelayMs (300000ms). Original error: Codex error event: The usage limit has been reached (code=usage_limit_reached)


 Call the fm_watch_arm_omp tool exactly once now, then reply with only the word ARMED.


 Error: Codex error event: The usage limit has been reached (code=usage_limit_reached)

 Error: Retry failed after 1 attempts: Provider requested 365557677ms wait, exceeds retry.maxDelayMs (300000ms). Original error: Codex error event: The usage limit has been reached (code=usage_limit_reached)

╭── π > ◔ GPT-6-Astra 👁 > 🗑 fm-omp-wake-restore.VrPE35/project > ⑂ fm/fm-omp-wake-delivery-gaps *1 ?1 > (sub) ▶────────────────9%─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╎───────────────────┃───────────────────────────272K─◀ 🆔 01a122aa ──╮
╰─                                                                                                                                                                                                                                                                                                                            ─╯
not ok - omp (omp/18.8.7) on herdr 0.9.1 never armed the watcher for wake-ext
exit=1
Evidence: Portable omp harness suite (33 ok)

Source: Portable omp harness suite (33 ok)

ok - omp shutdown, arm and host readiness, and unready retirement each log one expiry
ok - fm-harness: omp detects by its anchored name; the marker is a precedence override that needs real omp ancestry
ok - session lock and tmux liveness: omp is anchored, decoys stay out
ok - fm-spawn: the omp launch line clears markers, pins posture, and wires the state-resident extension
ok - omp ship and scout launches select replace edit mode only in the worker process
ok - real omp spawns authorize firstmate only and carry project scope through the generated extension
ok - fm-spawn: omp model validation is scoped to providers the listing can prove
ok - fm-spawn: an omp launch with no model refuses a missing or unlisted default role and names the remedy
ok - fm-spawn: global role inspection is read-only, unlayered, and honors PI_CODING_AGENT_DIR
ok - fm-spawn: raw omp launches validate the default only without an effective model override
ok - fm-spawn: raw role and catalog inspection honor the actual launch directory and uncertain evidence passes through
ok - fm-spawn: omp profile flags, assignments, and invoking environment pass through without default-role catalog probes
ok - fm-spawn: any expanded raw token passes through unchanged without evaluating it or probing the default-role catalog
ok - fm-spawn: unquoted, single-quoted, and double-quoted literal evidence still refuses invalid defaults
ok - fm-spawn: a real omp secondmate launch preserves primary posture and supervision
ok - fm-spawn: the config/secondmate-harness model pin is validated against the omp catalog before launch
ok - omp extension: agent_start busy, willContinue stays busy, plain agent_end idle, turn_end a notification, jev-guard tool hooks installed
ok - control, composer, and supervision-model tables carry omp's verified values
ok - fm-wake-lib: the omp ownership proof is keyed on its own extensions and gates the hand-off tolerance
ok - .omp turn-end guard: digest delivery, seatbelt block, one compelled continuation, flagged stop stands down
ok - .omp watch extension: fm_watch_arm_omp arms once, repeats as a no-op, and delivers an actionable close as one follow-up
ok - .omp watch extension: an opted-in home runs the supervision host and relays every host line (away record)
ok - .omp watch extension: an opted-in home runs the supervision host and relays every host line (quiet record)
ok - .omp watch extension: a home without config/supervision-host or with an off file keeps the plain arm
ok - .omp watch extension: a host-only boundary rides the replacement handoff and replays in the successor session
ok - .omp watch extension: a host close split across stream chunks reaches main as one whole follow-up
ok - .omp watch extension: an idle lane's wake starts its own turn behind an advisor tail; a wake omp restored to the composer is submitted again alone, bounded, and never over a draft or a running turn
ok - .omp watch extension: isolated replacement, queue drain, running observations, accepted messages, and turn completion each restart the fifteen-check wait and once-only warning
ok - .omp extensions: a descendant omp session neither records itself as the loaded session nor arms a watcher
ok - .omp turn-end guard: the loaded marker follows the lock owner at turn boundaries instead of only at load
ok - .omp watch extension: a generation stopped without a successor heals once; a real successor or arm call never double-arms
ok - .omp watch extension: a double load keeps one live generation, one arm, and forwards the superseded tool
ok - .omp watch extension: publication failure retains the wake and permits tool, command, and factory repair
exit=0

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - medium risk

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
Gap 1: an older wiring leaves wake text unsent in an idle omp composer with an operator draft beside it. The watch extension sends that wake as its own turn, clears only the wake, and keeps the draft… ✅ pass live omp-wake-restore-live-run2-opus.log line 2 'live omp stranded wake' (also ok in run 1)
Gap 2: Esc interrupts a running turn, and omp puts the queued wake back in the composer. The extension sends it again, and the lane handles it (this step failed on main with omp 18.8.x). ✅ pass live omp-wake-restore-live-run2-opus.log line 3 'live omp wake restore: re-submitted a wake that Esc restored'
Gap 3: the restored-wake check runs while omp has pending messages and does not skip. The lab probe confirms the wake is queued, and the step gives a real ok. ✅ pass live omp-wake-restore-live-run2-opus.log has no skip lines, and both restored-wake steps are ok
Guard (adversarial): during the wake re-send, the operator's draft stays exactly as typed and is not sent or changed. ✅ pass live omp-wake-restore-live-run2-opus.log line 4 'left the operator's draft exactly as typed'
Regression: a wake that reaches an idle lane behind an advisor note starts its own turn, and the draft is not sent. ✅ pass live omp-wake-restore-live-run2-opus.log line 1 'live omp idle wake'
Regression: the loaded markers name the lock-holding session after a child omp runs, and a busy composer reads empty or pending and takes a doorbell sent mid-turn. ✅ pass live omp-wake-restore-live-run2-opus.log lines 5-7
  • FM_OMP_WAKE_RESTORE_LIVE=1 bash tests/fm-omp-wake-restore-live-e2e.test.sh (run 1, default model openai-codex/gpt-6-astra: steps 4 and 5 passed, then steps 1-3 stopped because Codex reported usage_limit_reached)
  • omp -p 'reply with the word hi' --no-session --thinking low --model opus (checked that a different logged-in model answers)
  • FM_OMP_WAKE_RESTORE_LIVE=1 FM_OMP_WAKE_RESTORE_LIVE_MODEL=opus bash tests/fm-omp-wake-restore-live-e2e.test.sh (run 2: all 7 live steps ok, exit 0)
  • bash tests/fm-omp-harness.test.sh (portable extension suite: 33 ok, 0 not ok, exit 0)
  • Cleanup check: git status --short was clean, the run's lab temp dirs were removed, and no lab processes were left
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

An idle omp raises no event when text lands in its composer, so Firstmate
wake text left unsent there was never delivered, and a lane restart that
needs an empty composer was refused. The restored-wake check also skipped
for good whenever omp still reported queued messages, and the live guard's
restored-wake step failed on omp 18.8.1.

- omp watch extension: poll the editor at a low rate while the session owns
  the watch, and deliver a complete, unchanged watcher wake found there
  through the prompt-starting API, removing only that wake. Operator text,
  edited or partial wakes, and wakes the extension still tracks stay as
  found; delivery is bounded per wake text.
- Restored-wake check: wait a bounded time for queued messages to drain,
  then recover a wake from the composer, or resubmit an unconsumed wake to
  flush a queue omp will not run, and report the wait once through omp's
  notification when resubmission starts no turn. A wake omp accepts as a
  follow-up now schedules the check, and a run end re-arms its two-second
  settle instead of keeping an earlier timer.
- Portable regression scenarios for each gap, failing on the previous
  extension: stranded text with and without the transport mark, beside a
  draft, several wakes, non-wake text, a running turn, text still changing,
  and the attempt bound; queued messages that drain, never drain, hold the
  wake itself, or ignore resubmission; and the run-end settle time.
- Live guard: omp 18.8.1 never redraws its follow-up queue panel for a
  follow-up an extension queues, so the guard reads omp's own queued state
  and every editor change through a lab-only probe and keeps the pane
  readers as a second signal; a new stranded-wake step; the draft step waits
  for the draft alone before reading it.
- Docs: watcher-continuity contract and dated verification evidence.
An idle omp raises no event when text lands in its composer, so Firstmate
wake text left unsent there was never delivered, and a lane restart that
needs an empty composer was refused. The restored-wake check also skipped
for good whenever omp still reported queued messages, and the live guard's
restored-wake step failed on omp 18.8.1.

- omp watch extension: poll the editor at a low rate while the session owns
  the watch, and deliver a complete, unchanged watcher wake found there
  through the prompt-starting API, removing only that wake. Operator text,
  edited or partial wakes, and wakes the extension still tracks stay as
  found; delivery is bounded per wake text.
- Restored-wake check: wait a bounded time for queued messages to drain,
  then recover a wake from the composer, or resubmit an unconsumed wake to
  flush a queue omp will not run, and report the wait once through omp's
  notification when resubmission starts no turn. A wake omp accepts as a
  follow-up now schedules the check, and a run end re-arms its two-second
  settle instead of keeping an earlier timer.
- Portable regression scenarios for each gap, failing on the previous
  extension: stranded text with and without the transport mark, beside a
  draft, several wakes, non-wake text, a running turn, text still changing,
  and the attempt bound; queued messages that drain, never drain, hold the
  wake itself, or ignore resubmission; and the run-end settle time.
- Live guard: omp 18.8.1 never redraws its follow-up queue panel for a
  follow-up an extension queues, so the guard reads omp's own queued state
  and every editor change through a lab-only probe and keeps the pane
  readers as a second signal; a new stranded-wake step; the draft step waits
  for the draft alone before reading it.
- Docs: watcher-continuity contract and dated verification evidence.
# Conflicts:
#	.omp/extensions/fm-primary-omp-watch.ts
#	docs/verification/runtime-backends.md
#	docs/watcher-continuity.md
#	tests/fm-omp-harness.test.sh
#	tests/fm-omp-wake-restore-live-e2e.test.sh
@MrGTV-love
MrGTV-love merged commit 1a66f62 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