Skip to content

fix: report semantic busy and idle state for OMP workers - #302

Merged
dnth merged 3 commits into
mainfrom
fm/fm-omp-busy-adapter
Nov 13, 2026
Merged

dnth merged 3 commits into
mainfrom
fm/fm-omp-busy-adapter

Conversation

@dnth

@dnth dnth commented Nov 12, 2026 •

Copy link
Copy Markdown
Owner

Summary

bin/fm-crew-state.sh had no semantic source for OMP, so every healthy OMP worker read unknown. The generated OMP worker extension now reports the agent-run lifecycle, and the classifier trusts that source only for harness=omp:

 state/<id>.omp-ext.ts   (generated by bin/fm-spawn.sh)
   session_start   -> doorbell ready marker
+  agent_start     -> fm-busy-event.sh apply busy  --source omp-ext
+  agent_end       -> fm-busy-event.sh apply idle  --source omp-ext (skipped when willContinue)
   turn_start      -> doorbell turn open + started marker
   turn_end        -> doorbell turn close + turn-end wake marker
+
+  only the process's first extension instance writes busy/idle;
+  native child sessions rebind the extension and stay inert

 bin/fm-busy-lib.sh   trust table
+  omp) adapter=omp-ext

turn_start/turn_end stay wake notifications: one OMP agent run contains several inner turn pairs, so a turn boundary is never a work boundary. That is why idle is gated on agent_end plus !willContinue.

Files changed:

bin/fm-spawn.sh                          # generator, busy wiring, arm
bin/fm-busy-lib.sh                       # omp-ext trust
bin/fm-test-run.sh                       # live guard family
.agents/skills/harness-adapters/SKILL.md # OMP busy source row
docs/architecture.md                     # adapter source pointer
docs/verification/runtime-backends.md    # dated live result
tests/fm-busy-adapter-wiring.test.sh     # portable regression
tests/fm-omp-busy-live-e2e.test.sh      # opt-in live guard

.omp/extensions/ and extension-template files changed: none. The behaviour is ported from upstream commits 2e65d2e2 and 4735ddc6 (kunchenguid#3867) onto this repo's generator shape.

Evidence

  • Before: fm_busy_sources_for_harness omp printed nothing, so bin/fm-crew-state.sh emitted unknown · pane · harness state unavailable (unknown missing) for a live OMP turn.
    After: the same classifier emits busy omp-ext during a turn and idle omp-ext after it. The portable regression (bash tests/fm-busy-adapter-wiring.test.sh) prints ok - omp extension: agent_start busy, willContinue stays busy, plain agent_end idle, turn boundaries stay notifications, and the live guard on OMP 18.18.6 prints ok - omp/18.18.6: real omp reports 'busy omp-ext' during a turn and 'idle omp-ext' after it (never unknown).
  • Native child sessions and inner turn boundaries cannot move the parent's record; both are pinned by the portable regression, and the live guard self-skips with skip: omp not found on PATH; the live OMP busy-state guard checked nothing when OMP is absent.
  • OMP workers launched before this change keep classifying unknown until relaunched. There is no migration; docs/verification/runtime-backends.md#omp-semantic-busy-state records that requirement.

macOS runner substitute for the cancelled check

GitHub cancelled Stock macOS Bash snapshot compatibility twice while it sat queued (job 131315249121, then rerun job 131330483668); zero steps ran in either attempt and no log exists. The job's own command block from .github/workflows/ci.yml was run instead under a locally built GNU bash 3.2.57(2), substituting only the interpreter path:

GNU bash, version 3.2.57(2)-release (x86_64-unknown-linux-gnu)
inventory_files=444
parse_sweep=ok
macos_stock_bash_block=ok snapshot=17 bearings=47
  • bash -n parse sweep over every bin/fm-lint.sh --list-files root: 444 files, zero failures.
  • tests/fm-fleet-snapshot-view.test.sh: 17 ok - lines (the job requires 17).
  • tests/fm-bearings-snapshot.test.sh: 47 ok - lines (the job requires 47).
  • Block exit 0, BASH_VERSION gate included.

The changed shell lines carry no bash 4+ or GNU-only surface: bin/fm-busy-lib.sh:184 (omp) adapter=omp-ext ;;), bin/fm-spawn.sh:4750 (case alternation), bin/fm-test-run.sh:197 (case alternation), and the two test scripts use only case/if/for/[ ]/${x#p}/${x%p}/${x:-}/heredocs/printf/touch/wait/trap/kill. Group kill was checked separately under bash 3.2.57 (set -m, kill -KILL -- -$pid: pgid matched the job pid and the process was reaped). An added-line scan found no grep -P, sed -i, readlink -f, date -d, stat -c, mktemp --, find -printf, xargs -r/-d, arrays, [[ ]], &>, or local -a. Remaining gap: the substitute ran on GNU coreutils rather than BSD userland, and the changed lines do not exercise userland flags.

Merge Danger

Door: two-way. Reverting restores the previous unknown classification; no data is rewritten or migrated.

Blast Radius: OMP workers only. New OMP tasks gain state/<id>.busy-gen and state/<id>.busy-state, which the existing teardown busy retirement removes. Other harnesses keep their own sources because omp-ext is trusted only for harness=omp.

Testing

OMP 18.18.6 passed isolated CLI and SDK lifecycle scenarios against synthetic localhost providers. Focused adapter and state regressions passed; missing-tool skipping and timeout cleanup were verified. Setup probes were corrected, evidence retained, and all started processes stopped.

  • Live validation: ✅ go - 4 of 4 scenarios driven live against the product
Scenario Result Live Evidence
Real OMP 18.18.6 executes a localhost-supplied shell-tool turn and reports busy then idle ✅ pass live live-guard.log; omp-turn.log
Real OMP SDK child sessions cannot overwrite their parent's busy or idle record ✅ pass live sdk-guards.log
Real OMP SDK continuation remains busy until terminal completion ✅ pass live sdk-guards.log
A stalled localhost provider triggers the live guard's deadline and terminates owned OMP processes ✅ pass live stalled-guard.log; stalled-result.log
Evidence: Real OMP guard and persisted state transitions
v1 gen=g1794497147.2765971.1363 seq=1 state=busy source=fm-spawn event=launch-brief ts=1794497147
v1 gen=g1794497147.2765971.1363 seq=2 state=busy source=omp-ext event=agent-start ts=1794497149
v1 gen=g1794497147.2765971.1363 seq=3 state=idle source=omp-ext event=agent-end ts=1794497157
v1 gen=g1794497147.2765971.1363 seq=4 state=busy source=omp-ext event=agent-start ts=1794497158
v1 gen=g1794497147.2765971.1363 seq=5 state=busy source=omp-ext event=agent-start ts=1794497159
v1 gen=g1794497147.2765971.1363 seq=6 state=idle source=omp-ext event=agent-end ts=1794497159
warning: {run-worktree}/.local-validation/tmp/fm-omp-busy-live.PgWhAk/home/data/omp-busy-live/brief.md predates acceptance-criterion receipts; launching is allowed, but implementation completion remains evidence-gated until Firstmate installs concrete criteria
warning: {run-worktree}/.local-validation/tmp/fm-omp-busy-live.PgWhAk/home/data/omp-busy-live/brief.md records no delivery contract line (scaffolded before ship briefs recorded one); launching on the explicit --mode no-mistakes - confirm its definition of done matches
spawned omp-busy-live harness=omp kind=ship mode=no-mistakes yolo=off window=firstmate:fm-omp-busy-live worktree={run-worktree}/.local-validation/tmp/fm-omp-busy-live.PgWhAk/wt
ok - omp/18.18.6: real omp reports 'busy omp-ext' during a turn and 'idle omp-ext' after it (never unknown)

Local provider requests=2; guard exit=0
Evidence: Real SDK ownership and continuation events
parent agent_start willContinue=undefined: busy omp-ext
parent turn_start willContinue=undefined: busy omp-ext
child agent_start willContinue=undefined: busy omp-ext
child turn_start willContinue=undefined: busy omp-ext
child turn_end willContinue=undefined: busy omp-ext
child agent_end willContinue=undefined: busy omp-ext
child completed during parent stream: parent record unchanged
parent turn_end willContinue=undefined: busy omp-ext
parent agent_end willContinue=true: busy omp-ext
parent agent_start willContinue=undefined: busy omp-ext
parent turn_start willContinue=undefined: busy omp-ext
parent turn_end willContinue=undefined: busy omp-ext
parent agent_end willContinue=undefined: idle omp-ext
child agent_start willContinue=undefined: idle omp-ext
child turn_start willContinue=undefined: idle omp-ext
child turn_end willContinue=undefined: idle omp-ext
child agent_end willContinue=undefined: idle omp-ext
real SDK continuation requests=2; final idle omp-ext
ok - real parent/child sessions and session_stop continuation preserve semantic ownership and settle idle
Evidence: Timeout and process cleanup verification
PASS: stalled local provider reached busy omp-ext; guard exited 1 at its 120-second deadline; wrapper PID 2829991 and real OMP PID 2830001 are absent. Initial immediate kill(0) check raced asynchronous process termination; follow-up confirmed both exited.
Evidence: Stalled-provider guard transcript
v1 gen=g1794497182.2828641.27407 seq=1 state=busy source=fm-spawn event=launch-brief ts=1794497182
v1 gen=g1794497182.2828641.27407 seq=2 state=busy source=omp-ext event=agent-start ts=1794497183
warning: {run-worktree}/.local-validation/tmp/fm-omp-busy-live.iD6kFM/home/data/omp-busy-live/brief.md predates acceptance-criterion receipts; launching is allowed, but implementation completion remains evidence-gated until Firstmate installs concrete criteria
warning: {run-worktree}/.local-validation/tmp/fm-omp-busy-live.iD6kFM/home/data/omp-busy-live/brief.md records no delivery contract line (scaffolded before ship briefs recorded one); launching on the explicit --mode no-mistakes - confirm its definition of done matches
spawned omp-busy-live harness=omp kind=ship mode=no-mistakes yolo=off window=firstmate:fm-omp-busy-live worktree={run-worktree}/.local-validation/tmp/fm-omp-busy-live.iD6kFM/wt
tests/fm-omp-busy-live-e2e.test.sh: line 185: 2829991 Killed                  ( cd "$WT" && exec omp --no-session -p --auto-approve -e "$EXT" "Run the shell command 'sleep 8; echo hi' and then reply with exactly DONE" > "$OMP_LOG" 2>&1 )
not ok - omp/18.18.6: live omp turn timed out after 120 seconds

Local provider requests=1; guard exit=1
Evidence: Validation scope and setup notes
Target 279a5890db18b32176bc6613b987ba1ea8f7eb61; base bfa8629b73642eac3132bda490129c7138206086.
Runtime: real installed OMP 18.18.6. HOME, TMPDIR, and PI_CODING_AGENT_DIR isolated beneath .local-validation/ in the gate worktree. No operator credentials/configuration used. Local OpenAI-compatible HTTP endpoint supplies synthetic responses; real OMP executes the requested bash tool.
Live guard uses existing tmux/treehouse test doubles only for extension generation and launch acknowledgement; the subsequent OMP CLI invocation and SDK parent/child sessions are real. This does not certify a real tmux launch, a production provider, or a complete fm-crew-state fleet session.
Portable adapter and busy-state contract regressions passed, supplementing live evidence. They are not claimed as live harness sessions.
SDK setup correction: waitForIdle can resolve before asynchronous agent_end hooks settle, so the driver waits for observable idle. Queued followup consumes responses within a single run; session_stop's documented continuation request exercises an actual willContinue=true boundary instead.
Source inspection confirmed no changes under .omp/extensions/ and a dated OMP verification record. PR-body requirements belong to the outer delivery phase and were not executed here.
Timeout cleanup verification: an immediate PID existence check raced group SIGKILL processing. Follow-up verified both owned processes absent; no cleanup intervention was needed.

Pipeline

Updates from git push no-mistakes

✅ **Intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 2 issues found → auto-fixed ✅
  • 🚨 bin/fm-spawn.sh:4929 - Native OMP subagents bind this extension again in the parent's process, as documented by .omp/extensions/lib/fm-task-inbox-doorbell.ts:96 and tests/fm-omp-task-inbox-doorbell.test.sh:596. Sequence: parent starts, launches a child, then the child emits its final agent_end while the parent continues working. The child writes idle to the parent's record using the same ID and generation, falsely classifying the worker as idle. Both bin/fm-spawn.sh:4928 (busy) and :4929–4931 (idle), through :4906–4911, need parent-session ownership gating in the generated extension. Extend the portable regression at tests/fm-busy-adapter-wiring.test.sh:561 to exercise parent and child instances.
  • ⚠️ tests/fm-omp-busy-live-e2e.test.sh:161 - After the first busy observation, polling stops and this wait has no deadline. A stalled provider stream therefore hangs the live guard indefinitely despite its bounded polling loops. Also, the failure at tests/fm-omp-busy-live-e2e.test.sh:152 exits without terminating the running OMP process; the inherited cleanup only removes fixtures. Bound the actual OMP invocation at :142 and terminate/reap its owned processes on failure or interruption.

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

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 4 of 4 scenarios driven live against the product
Scenario Result Live Evidence
Real OMP 18.18.6 executes a localhost-supplied shell-tool turn and reports busy then idle ✅ pass live live-guard.log; omp-turn.log
Real OMP SDK child sessions cannot overwrite their parent's busy or idle record ✅ pass live sdk-guards.log
Real OMP SDK continuation remains busy until terminal completion ✅ pass live sdk-guards.log
A stalled localhost provider triggers the live guard's deadline and terminates owned OMP processes ✅ pass live stalled-guard.log; stalled-result.log
  • bash tests/fm-busy-adapter-wiring.test.sh with isolated HOME and TMPDIR
  • bash tests/fm-busy-state.test.sh with isolated HOME and TMPDIR
  • FM_OMP_BUSY_LIVE_E2E=1 bash tests/fm-omp-busy-live-e2e.test.sh through a disposable localhost-provider driver
  • Real OMP SDK parent/child sessions and session_stop continuation using the generated extension
  • Real OMP live guard against a deliberately stalled localhost provider; verified deadline and process termination
  • Live guard with OMP excluded from PATH; confirmed explicit missing-OMP skip
  • Verified temporary workspace removal and clean git status
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

dnth added 3 commits November 12, 2026 22:49
The omp adapter had no busy-state source, so fm-crew-state.sh reported
unknown for healthy omp workers. The generated per-task extension now writes
busy on agent_start and idle on agent_end when willContinue is not set, with
source omp-ext; fm-spawn arms the contract for omp and fm-busy-lib.sh trusts
that source only for the omp harness.

turn_start/turn_end stay wake notifications: one omp agent run contains
several inner turn pairs (verified on 18.18.6), so a turn boundary is not a
work boundary.

tests/fm-busy-adapter-wiring.test.sh drives the real generated extension in a
plain Node host. tests/fm-omp-busy-live-e2e.test.sh is the opt-in live guard
that ran against omp 18.18.6 and observed busy then idle across a real turn.
@dnth
dnth merged commit de84b17 into main Nov 13, 2026
39 of 41 checks passed
@dnth
dnth deleted the fm/fm-omp-busy-adapter branch November 13, 2026 00:48
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