Skip to content

fix(bin): let routine-wake triage close pause rechecks of merge-word waits after the agent exits - #46

Closed
talktejas wants to merge 2 commits into
mainfrom
fm/fm-jev-wake-triage-no-checks
Closed

talktejas wants to merge 2 commits into
mainfrom
fm/fm-jev-wake-triage-no-checks

Conversation

@talktejas

Copy link
Copy Markdown
Owner

Intent

The first Jev item, merged as pull request 38, was accepted from a proposal table as: "Routine monitoring checks - Jev decides: does this check need firstmate, or is it routine? - Stops the stream of 'nothing new' turns; routine checks are closed without a message." All Jev items were then ordered merged: "merge all jev items".

It is live but is not closing the checks it was built for. Since it went live the supervisor has still been woken, one at a time, for every "stale: (paused Ns, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)" recheck of the ten JewelTrek pull requests that are open and waiting for a merge word (jeweltrek2627-slim pull requests 222 to 231; examples at 18:49Z and 18:51Z on 2026-10-05 for tasks jt-diamond-cert-mismatch-flag and jt-diamond-consignor-statement, whose metadata carry pr= and whose pull requests are open). state/.watch-triage.log in the firstmate home shows exactly one "jev triage" line all evening (a different task, delivered at confidence 0.61) and none for these.

What Changed

  • jev_triage_task_evidence in bin/fm-watch.sh now offers a paused task whose agent state is dead as well as alive, instead of refusing everything but alive. The evidence gains a worker_agent field (running or exited), and any other agent state (missing, ambiguous, unreadable, unverified) is still delivered without asking.
  • The triage question and criteria now treat an open pull request that only awaits its merge word, whoever gives it, as routine whether the agent is running or has exited. Every other wait of an exited agent, and a wait on a person for anything other than that merge, is needs_firstmate.
  • Tests cover the exited-agent recheck, the worker_agent evidence value, and further pull-request reads: no-checks output is still offered, while a pending required check, a draft, and a conflict are delivered. The pull-request gate itself is unchanged; only its comment now says a repository with no checks is not a blocker. docs/configuration.md and docs/verification/jev-wake-triage.md are updated to match, with the live answers re-recorded on 2026-10-06.

Risk Assessment

✅ Low: The change is a small, well-bounded widening of one eligibility gate (agent state dead now offered alongside alive) plus question wording, matching the intent's failing sequence (handle_paused_stale is reached for an ordinary crew only when the agent is dead, which the old gate always refused), with the deterministic pull-request read, confidence floor, and streak cap unchanged and behavioral tests covering the new path.

Testing

Ran the targeted triage test file (passes), then drove a real bin/fm-watch.sh process over an isolated firstmate home with a real tmux server on a private socket, the real bin/fm-pr-state.sh reading the real JewelTrek pull requests, and real Jev calls using the key from the firstmate home. On the base commit the exited-agent merge-word recheck was delivered with no Jev line, exactly as reported; on the target it was absorbed at confidence 0.99 with nothing queued, for both PR 228 and PR 222 using the real tasks' own status lines. Adversarial cases were all delivered: PR 230 (now conflicting) and a task with no recorded pull request without asking Jev, and an exited worker waiting on a choice, on a question, or on CI after asking. A running agent on the merge word was also absorbed. The live watcher ran on the tmux backend while the real fleet uses herdr; a read-only check with this change's backend library showed the three real herdr endpoints sampled read dead, the state the fix now offers. No UI surface, so no screenshots; the evidence is the watcher's own output, wake queue and triage log.

  • Live validation: ✅ go - 8 of 10 scenarios driven live against the product
Scenario Result Live Evidence
Worker's agent has exited, its recorded pull request is open with no checks, and its last status waits for the captain's merge word: the stale recheck is closed without waking the supervisor ✅ pass live live-transcript.txt cases A and J: watcher kept running (timeout exit 124), 0 wake-queue lines added, triage log absorbed jev-routine declared-pause-recheck (confidence=0.99 ...)
Reported failure reproduces before the fix: the same recheck on base commit d0dd8b5 wakes the supervisor and logs no Jev triage line ✅ pass live live-transcript.txt case B: watcher exited printing stale: fmlive:fm-park (paused 14526s, awaiting external ...), 1 wake-queue line added, empty triage log
Adversarial: exited agent beside a pull request that now reads conflicting (real PR 230) is delivered without asking Jev ✅ pass live live-transcript.txt case C: stale wake printed and queued, triage log has no Jev line
Adversarial: exited agent with an open pull request but a worker waiting on a choice or a question is delivered ✅ pass live live-transcript.txt cases D and F: jev triage delivered declared-pause-recheck (choice=needs_firstmate confidence=1.0 / 0.91), wake queued
Adversarial: exited agent waiting on CI it says it will resume from is delivered, since it cannot resume by itself ✅ pass live live-transcript.txt case E: jev triage delivered ... (choice=routine confidence=0.22 floor=0.6), wake queued. Delivered only by the confidence floor, as the verification doc already records.
Adversarial: exited agent on the merge word but with no pull request recorded in the task metadata is delivered without asking Jev ✅ pass live live-transcript.txt case G: stale wake printed and queued, triage log has no Jev line
Running agent waiting for the captain's merge word on an open pull request is closed without a wake (the wording change) ✅ pass live live-transcript.txt case H: backend read alive, absorbed jev-routine declared-pause-recheck (confidence=0.99 ...), 0 wake-queue lines added
The real JewelTrek endpoints named in the intent read as an exited agent, the state the fix now offers ✅ pass live live-transcript.txt final block: fm_backend_agent_state herdr prints dead for jt-diamond-consignor-statement, jt-diamond-cert-mismatch-flag and jt-diamond-memo-return-by-date
The live supervisor's own watcher stops being woken for the open JewelTrek merge-word rechecks, with jev triage absorb lines appearing in the firstmate home's state/.watch-triage.log ⏸️ untested no This needs the change deployed to the running firstmate home at ~/p/firstmate and its herdr fleet, which is outside this worktree and not mine to modify. After merge, restart the watcher there…
An endpoint whose window is missing is delivered without asking Jev ⏸️ untested no With a real tmux server a missing window never reaches the stale recheck path in the watcher, so this gate could not be reached live; no Jev call was made in the attempt. Reaching it needs a backend s…
Evidence: Live watcher transcript (all driven cases, base and target)

Source: Live watcher transcript (all driven cases, base and target)

\### B (base d0dd8b5, the reported failure): exited agent, open PR 228, captain's merge word
watcher: /tmp/fm-base-p7Dx9d/bin/fm-watch.sh @ d0dd8b5
agent state read by the backend: dead
pull request read (https://github.com/talktejas/jeweltrek2627-slim/pull/228): [CHECKS: none reported yet|]
newest status: paused: [2026-09-28T06:08:12Z] pull request https://github.com/talktejas/jeweltrek2627-slim/pull/228 open, waiting for the captain's merge word
-- run 1 (no key; the new paused: line is reported): exit=0
   stdout: signal: /tmp/fm-live-pgpRqp/state/park.status
-- run 2 (key present; the stale recheck; 124 = still watching, nothing delivered): exit=0
   stdout: stale: fmlive:fm-park (paused 14526s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)
   wake-queue lines added: 1
   triage log:

\### A (target): exited agent, open PR 228, captain's merge word
watcher: ~/.no-mistakes/worktrees/3605d2c32b02/01M46R9QPE4ZQGA9REDD1CDGAE/bin/fm-watch.sh @ 5ea1dcd
agent state read by the backend: dead
pull request read (https://github.com/talktejas/jeweltrek2627-slim/pull/228): [CHECKS: none reported yet|]
newest status: paused: [2026-09-28T06:08:12Z] pull request https://github.com/talktejas/jeweltrek2627-slim/pull/228 open, waiting for the captain's merge word
-- run 1 (no key; the new paused: line is reported): exit=0
   stdout: signal: /tmp/fm-live-tjj4eo/state/park.status
-- run 2 (key present; the stale recheck; 124 = still watching, nothing delivered): exit=124
   wake-queue lines added: 0
   triage log:
     [2026-10-06T02:28:12+0700] absorbed jev-routine declared-pause-recheck (confidence=0.99, routine=0.99 needs_firstmate=0.01, 326 ms): stale: fmlive:fm-park (paused 14527s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)
     [2026-10-06T02:28:12+0700] absorbed stale (paused, awaiting external, age 14527s): fmlive:fm-park
     [2026-10-06T02:28:15+0700] absorbed stale (paused, awaiting external, age 14532s): fmlive:fm-park
     [2026-10-06T02:28:17+0700] absorbed stale (paused, awaiting external, age 14534s): fmlive:fm-park
     [2026-10-06T02:28:19+0700] absorbed stale (paused, awaiting external, age 14536s): fmlive:fm-park
     [2026-10-06T02:28:21+0700] absorbed stale (paused, awaiting external, age 14538s): fmlive:fm-park

\### C: exited agent, PR 230 reads conflicting, merge word
watcher: ~/.no-mistakes/worktrees/3605d2c32b02/01M46R9QPE4ZQGA9REDD1CDGAE/bin/fm-watch.sh @ 5ea1dcd
agent state read by the backend: dead
pull request read (https://github.com/talktejas/jeweltrek2627-slim/pull/230): [MERGEABILITY: conflicting|CHECKS: none reported yet|]
newest status: paused: [2026-09-28T19:04:12Z] pull request 230 open, waiting for the captain's merge word
-- run 1 (no key; the new paused: line is reported): exit=0
   stdout: signal: /tmp/fm-live-Qv6E5q/state/park.status
-- run 2 (key present; the stale recheck; 124 = still watching, nothing delivered): exit=0
   stdout: stale: fmlive:fm-park (paused 14526s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)
   wake-queue lines added: 1
   triage log:

\### D: exited agent, open PR 228, worker waits on a choice
watcher: ~/.no-mistakes/worktrees/3605d2c32b02/01M46R9QPE4ZQGA9REDD1CDGAE/bin/fm-watch.sh @ 5ea1dcd
agent state read by the backend: dead
pull request read (https://github.com/talktejas/jeweltrek2627-slim/pull/228): [CHECKS: none reported yet|]
newest status: paused: two ways to fix the schema, waiting to be told which
-- run 1 (no key; the new paused: line is reported): exit=0
   stdout: signal: /tmp/fm-live-dvwrBx/state/park.status
-- run 2 (key present; the stale recheck; 124 = still watching, nothing delivered): exit=0
   stdout: stale: fmlive:fm-park (paused 14526s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)
   wake-queue lines added: 1
   triage log:
     [2026-10-06T02:28:43+0700] jev triage delivered declared-pause-recheck (choice=needs_firstmate confidence=1.0 floor=0.6): stale: fmlive:fm-park (paused 14526s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)

\### E: exited agent, open PR 228, worker waits on CI it would resume from
watcher: ~/.no-mistakes/worktrees/3605d2c32b02/01M46R9QPE4ZQGA9REDD1CDGAE/bin/fm-watch.sh @ 5ea1dcd
agent state read by the backend: dead
pull request read (https://github.com/talktejas/jeweltrek2627-slim/pull/228): [CHECKS: none reported yet|]
newest status: paused: CI is running on the pull request, about 25 minutes, resuming on my own when it finishes
-- run 1 (no key; the new paused: line is reported): exit=0
   stdout: signal: /tmp/fm-live-88Cy0f/state/park.status
-- run 2 (key present; the stale recheck; 124 = still watching, nothing delivered): exit=0
   stdout: stale: fmlive:fm-park (paused 14525s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)
   wake-queue lines added: 1
   triage log:
     [2026-10-06T02:28:53+0700] jev triage delivered declared-pause-recheck (choice=routine confidence=0.22 floor=0.6): stale: fmlive:fm-park (paused 14525s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)

\### F: exited agent, open PR 228, PR open but worker asks a question
watcher: ~/.no-mistakes/worktrees/3605d2c32b02/01M46R9QPE4ZQGA9REDD1CDGAE/bin/fm-watch.sh @ 5ea1dcd
agent state read by the backend: dead
pull request read (https://github.com/talktejas/jeweltrek2627-slim/pull/228): [CHECKS: none reported yet|]
newest status: paused: PR is open, waiting for you to decide whether the migration should also ship in this PR
-- run 1 (no key; the new paused: line is reported): exit=0
   stdout: signal: /tmp/fm-live-Zjh7Zc/state/park.status
-- run 2 (key present; the stale recheck; 124 = still watching, nothing delivered): exit=0
   stdout: stale: fmlive:fm-park (paused 14525s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)
   wake-queue lines added: 1
   triage log:
     [2026-10-06T02:29:03+0700] jev triage delivered declared-pause-recheck (choice=needs_firstmate confidence=0.91 floor=0.6): stale: fmlive:fm-park (paused 14525s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)

\### G: exited agent, no recorded pull request, merge word
watcher: ~/.no-mistakes/worktrees/3605d2c32b02/01M46R9QPE4ZQGA9REDD1CDGAE/bin/fm-watch.sh @ 5ea1dcd
agent state read by the backend: dead
newest status: paused: [2026-09-28T06:08:12Z] pull request https://github.com/talktejas/jeweltrek2627-slim/pull/228 open, waiting for the captain's merge word
-- run 1 (no key; the new paused: line is reported): exit=0
   stdout: signal: /tmp/fm-live-pas444/state/park.status
-- run 2 (key present; the stale recheck; 124 = still watching, nothing delivered): exit=0
   stdout: stale: fmlive:fm-park (paused 14524s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)
   wake-queue lines added: 1
   triage log:

\### H: running agent, open PR 228, captain's merge word
watcher: ~/.no-mistakes/worktrees/3605d2c32b02/01M46R9QPE4ZQGA9REDD1CDGAE/bin/fm-watch.sh @ 5ea1dcd
agent state read by the backend: alive
pull request read (https://github.com/talktejas/jeweltrek2627-slim/pull/228): [CHECKS: none reported yet|]
newest status: paused: [2026-09-28T06:08:12Z] pull request https://github.com/talktejas/jeweltrek2627-slim/pull/228 open, waiting for the captain's merge word
-- run 1 (no key; the new paused: line is reported): exit=0
   stdout: signal: /tmp/fm-live-j8vv4N/state/park.status
-- run 2 (key present; the stale recheck; 124 = still watching, nothing delivered): exit=124
   wake-queue lines added: 0
   triage log:
     [2026-10-06T02:30:16+0700] absorbed jev-routine declared-pause-recheck (confidence=0.99, routine=1.0 needs_firstmate=0.0, 465 ms): stale: fmlive:fm-park
     [2026-10-06T02:30:16+0700] absorbed non-terminal stale (declared wait or open captain call already re-surfaced this window): fmlive:fm-park
     [2026-10-06T02:30:17+0700] absorbed stale (paused, awaiting external, age 14533s): fmlive:fm-park
     [2026-10-06T02:30:19+0700] absorbed stale (paused, awaiting external, age 14535s): fmlive:fm-park
     [2026-10-06T02:30:21+0700] absorbed stale (paused, awaiting external, age 14537s): fmlive:fm-park

\### J: exited agent, open PR 222, the real task's own last two status lines
watcher: ~/.no-mistakes/worktrees/3605d2c32b02/01M46R9QPE4ZQGA9REDD1CDGAE/bin/fm-watch.sh @ 5ea1dcd
agent state read by the backend: dead
pull request read (https://github.com/talktejas/jeweltrek2627-slim/pull/222): [CHECKS: none reported yet|]
newest status: paused: [2026-09-28T05:45:23Z] pull request 222 open, waiting for the captain's merge word
-- run 1 (no key; the new paused: line is reported): exit=0
   stdout: signal: /tmp/fm-live-WLiknD/state/park.status
-- run 2 (key present; the stale recheck; 124 = still watching, nothing delivered): exit=124
   wake-queue lines added: 0
   triage log:
     [2026-10-06T02:30:41+0700] absorbed jev-routine declared-pause-recheck (confidence=0.99, needs_firstmate=0.01 routine=0.99, 318 ms): stale: fmlive:fm-park (paused 14526s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)
     [2026-10-06T02:30:41+0700] absorbed stale (paused, awaiting external, age 14526s): fmlive:fm-park
     [2026-10-06T02:30:45+0700] absorbed stale (paused, awaiting external, age 14532s): fmlive:fm-park
     [2026-10-06T02:30:47+0700] absorbed stale (paused, awaiting external, age 14533s): fmlive:fm-park
     [2026-10-06T02:30:48+0700] absorbed stale (paused, awaiting external, age 14535s): fmlive:fm-park
     [2026-10-06T02:30:50+0700] absorbed stale (paused, awaiting external, age 14537s): fmlive:fm-park

\### Read-only: agent state of the real (herdr) endpoints named in the intent, read with this change's backend library
jt-diamond-consignor-statement default:w93:p2 -> dead
jt-diamond-cert-mismatch-flag default:w8K:p2 -> dead
jt-diamond-memo-return-by-date default:w8Z:p2 -> dead
Evidence: Live driver script

Source: Live driver script

#!/usr/bin/env bash
# Live drive of the routine-wake triage: a real bin/fm-watch.sh process over an
# isolated firstmate home, a real tmux server on a private socket, the real
# bin/fm-pr-state.sh reading real GitHub pull requests, and real Jev calls.
# Usage: live-drive.sh <repo-root> <label> <agent: exited|running|missing> <pr-url-or-empty> <status-line>...
set -u
ROOT=$1 LABEL=$2 AGENT=$3 PR=$4; shift 4
KEY_HOME=${KEY_HOME:-/home/tds/p/firstmate}
D=$(mktemp -d /tmp/fm-live-XXXXXX)
mkdir -p "$D/state" "$D/config" "$D/tmux" "$D/agentbin"
export TMUX_TMPDIR="$D/tmux"; unset TMUX
# A stand-in agent: a process the backend's own classifier names as the claude harness.
printf '#!/bin/bash\nwhile :; do sleep 1000; done\n' > "$D/agentbin/claude"; chmod +x "$D/agentbin/claude"
case "$AGENT" in
  exited)  tmux new-session -d -s fmlive -n fm-park -x 120 -y 30 'exec bash --norc' ;;
  running) tmux new-session -d -s fmlive -n fm-park -x 120 -y 30 "exec $D/agentbin/claude" ;;
  missing) tmux new-session -d -s fmlive -n other -x 120 -y 30 'exec bash --norc' ;;
esac
sleep 1
. "$ROOT/bin/fm-backend.sh" 2>/dev/null
{ printf 'window=fmlive:fm-park\nkind=ship\n'; [ -z "$PR" ] || printf 'pr=%s\n' "$PR"; } > "$D/state/park.meta"
printf '%s\n' "$@" > "$D/state/park.status"
touch -d "@$(( $(date +%s) - 14520 ))" "$D/state/park.status"
printf '### %s\n' "$LABEL"
printf 'watcher: %s @ %s\n' "$ROOT/bin/fm-watch.sh" "$(git -C "$ROOT" rev-parse --short HEAD 2>/dev/null || cat "$ROOT/.rev")"
printf 'agent state read by the backend: %s\n' "$(fm_backend_agent_state tmux fmlive:fm-park)"
[ -z "$PR" ] || printf 'pull request read (%s): [%s]\n' "$PR" "$("$ROOT/bin/fm-pr-state.sh" "$PR" 2>&1 | tr '\n' '|')"
printf 'newest status: %s\n' "$(tail -n 1 "$D/state/park.status")"
run() {  # <seconds> [env...]
  local secs=$1; shift
  env FM_HOME="$D" FM_STATE_OVERRIDE="$D/state" FM_CONFIG_OVERRIDE="$D/config" \
    FM_POLL=1 FM_SIGNAL_GRACE=1 FM_CHECK_INTERVAL=999999 FM_HEARTBEAT=999999 "$@" \
    timeout "$secs" "$ROOT/bin/fm-watch.sh" > "$D/watch.out" 2> "$D/watch.err"
  printf 'exit=%s\n' "$?"
}
# Run 1, no key: the paused: status line is delivered as the news it is.
printf -- '-- run 1 (no key; the new paused: line is reported): '; run 25
sed 's/^/   stdout: /' "$D/watch.out"
cp "$D/state/.wake-queue" "$D/queue.1" 2>/dev/null || : > "$D/queue.1"
: > "$D/state/.watch-triage.log"
# Run 2, with the key: the successor watcher meets the stale recheck.
KEY=$(sed -n 's/^TYPESAFE_API_KEY=//p' "$KEY_HOME/.env" | head -n 1)
printf -- '-- run 2 (key present; the stale recheck; 124 = still watching, nothing delivered): '
run "${RUN2_SECS:-25}" TYPESAFE_API_KEY="$KEY" FM_WATCH_HANDLING_SUCCESSOR=1
sed 's/^/   stdout: /' "$D/watch.out"
printf '   wake-queue lines added: %s\n' "$(( $(wc -l < "$D/state/.wake-queue" 2>/dev/null || echo 0) - $(wc -l < "$D/queue.1") ))"
printf '   triage log:\n'; sed 's/^/     /' "$D/state/.watch-triage.log"
grep -Fq "$KEY" -r "$D/state" "$D/watch.out" "$D/watch.err" 2>/dev/null && printf '   KEY LEAKED INTO STATE OR OUTPUT\n'
tmux kill-server 2>/dev/null
[ -n "${KEEP:-}" ] && printf 'kept %s\n' "$D" || rm -rf "$D"
echo
Evidence: Before and after on the reported case (exited agent, open PR 228, captain's merge word)
BASE d0dd8b5, run 2: exit=0
stdout: stale: fmlive:fm-park (paused 14526s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)
wake-queue lines added: 1
triage log: (empty)

TARGET 5ea1dcd, run 2: exit=124 (still watching, nothing delivered)
wake-queue lines added: 0
triage log: absorbed jev-routine declared-pause-recheck (confidence=0.99, routine=0.99 needs_firstmate=0.01, 326 ms): stale: fmlive:fm-park (paused 14527s, awaiting external - declared pause, rechecked on a long cadence not a wedge; confirm the wait still holds)
Evidence: Guards still deliver (target, exited agent)
PR 230 conflicting -> stale wake queued, no Jev call
no pr= recorded -> stale wake queued, no Jev call
waiting to be told which -> jev triage delivered (choice=needs_firstmate confidence=1.0 floor=0.6)
PR open but asks question -> jev triage delivered (choice=needs_firstmate confidence=0.91 floor=0.6)
CI running, will resume -> jev triage delivered (choice=routine confidence=0.22 floor=0.6)
- Outcome: ⚠️ 1 info across 1 run (7m15s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ℹ️ bin/fm-watch.sh:1724 - Admitting dead agents means an exited worker with a non-merge wait (for example paused: CI is running, resuming on my own) is now kept from being absorbed only by the model's answer; no code gate separates it from a merge wait. The branch's own live table (docs/verification/jev-wake-triage.md, row ci / exited) shows the model still chooses routine there (routine probability 0.56, confidence 0.10-0.12), so only the 0.6 confidence floor delivers it. This is bounded: a required check that is pending or failing is refused in code by the pull-request read, the six-in-a-row streak cap forces a delivery, and the doc records the tradeoff. Noted as an acknowledged tradeoff; no change requested.
⚠️ **Test** - 1 info
  • ℹ️ Of the ten pull requests in the intent, PR 230 (jt-diamond-cert-mismatch-flag) now reads MERGEABILITY: conflicting, so its recheck will keep waking the supervisor after this ships. That is the designed behaviour for a blocked pull request, not a defect; the other nine (222 to 229, 231) read open with no blocker and are eligible to be closed.
  • Live validation: ✅ go - 8 of 10 scenarios driven live against the product
Scenario Result Live Evidence
Worker's agent has exited, its recorded pull request is open with no checks, and its last status waits for the captain's merge word: the stale recheck is closed without waking the supervisor ✅ pass live live-transcript.txt cases A and J: watcher kept running (timeout exit 124), 0 wake-queue lines added, triage log absorbed jev-routine declared-pause-recheck (confidence=0.99 ...)
Reported failure reproduces before the fix: the same recheck on base commit d0dd8b5 wakes the supervisor and logs no Jev triage line ✅ pass live live-transcript.txt case B: watcher exited printing stale: fmlive:fm-park (paused 14526s, awaiting external ...), 1 wake-queue line added, empty triage log
Adversarial: exited agent beside a pull request that now reads conflicting (real PR 230) is delivered without asking Jev ✅ pass live live-transcript.txt case C: stale wake printed and queued, triage log has no Jev line
Adversarial: exited agent with an open pull request but a worker waiting on a choice or a question is delivered ✅ pass live live-transcript.txt cases D and F: jev triage delivered declared-pause-recheck (choice=needs_firstmate confidence=1.0 / 0.91), wake queued
Adversarial: exited agent waiting on CI it says it will resume from is delivered, since it cannot resume by itself ✅ pass live live-transcript.txt case E: jev triage delivered ... (choice=routine confidence=0.22 floor=0.6), wake queued. Delivered only by the confidence floor, as the verification doc already records.
Adversarial: exited agent on the merge word but with no pull request recorded in the task metadata is delivered without asking Jev ✅ pass live live-transcript.txt case G: stale wake printed and queued, triage log has no Jev line
Running agent waiting for the captain's merge word on an open pull request is closed without a wake (the wording change) ✅ pass live live-transcript.txt case H: backend read alive, absorbed jev-routine declared-pause-recheck (confidence=0.99 ...), 0 wake-queue lines added
The real JewelTrek endpoints named in the intent read as an exited agent, the state the fix now offers ✅ pass live live-transcript.txt final block: fm_backend_agent_state herdr prints dead for jt-diamond-consignor-statement, jt-diamond-cert-mismatch-flag and jt-diamond-memo-return-by-date
The live supervisor's own watcher stops being woken for the open JewelTrek merge-word rechecks, with jev triage absorb lines appearing in the firstmate home's state/.watch-triage.log ⏸️ untested no This needs the change deployed to the running firstmate home at ~/p/firstmate and its herdr fleet, which is outside this worktree and not mine to modify. After merge, restart the watcher there…
An endpoint whose window is missing is delivered without asking Jev ⏸️ untested no With a real tmux server a missing window never reaches the stale recheck path in the watcher, so this gate could not be reached live; no Jev call was made in the attempt. Reaching it needs a backend s…
  • bash tests/fm-jev-wake-triage.test.sh (all cases pass, including the new exited-agent case)
  • live-drive.sh &lt;target&gt; exited .../pull/228 &lt;real status lines&gt;: real watcher, real tmux, real PR read, real Jev call; absorbed, nothing queued
  • live-drive.sh &lt;base d0dd8b5 via git archive&gt; exited .../pull/228 ...: same setup on the base commit; stale wake delivered, no Jev line
  • live-drive.sh &lt;target&gt; exited .../pull/222 &lt;jt-diamond-memo-return-by-date&#39;s last two status lines&gt;: absorbed
  • live-drive.sh &lt;target&gt; exited .../pull/230 ...: PR reads conflicting; delivered without a Jev call
  • live-drive.sh &lt;target&gt; exited .../pull/228 with a waiting-to-be-told-which, a decide-the-migration, and a CI-running status line: Jev asked each time, wake delivered each time
  • live-drive.sh &lt;target&gt; exited &#34;&#34; ... (no pr= in metadata): delivered without a Jev call
  • live-drive.sh &lt;target&gt; running .../pull/228 ...: stand-in agent process the backend classifies as alive; absorbed
  • bin/fm-pr-state.sh against jeweltrek2627-slim pull requests 222 to 231 (read-only)
  • fm_backend_agent_state herdr &lt;window&gt; for three real task endpoints in the firstmate home (read-only): all dead
  • Checked the Jev key appears in no evidence file, watcher output or state; worktree left clean
⚠️ **Document** - 1 info
  • ℹ️ docs/verification/jev-wake-triage.md:12 - The live-answers record says its calls were sourced from bin/fm-watch.sh at commit 1f534a99, which exists locally but is not an ancestor of the target commit 5ea1dcd, so it will not be reachable from main after merge. I left it unchanged because it is the commit the recorded run actually used, and bin/fm-watch.sh differs between 1f534a99 and the target only by one added shellcheck comment line, so the question text and criteria the record vouches for are identical. Re-pointing it at a branch commit would misstate where the evidence was taken; the author can re-run and restamp if a reachable reference is wanted.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…to routine-wake triage

The pause recheck is reached for an ordinary crew only once its agent is
confidently dead, and the triage refused every agent that was not alive, so the
recheck it was wired to could never be offered. A worker whose agent has exited
beside an open pull request is now offered, the model is told which, and the
question reads a wait for the merge word of that pull request as the already
reported merge whoever gives it.
@talktejas

Copy link
Copy Markdown
Owner Author

Superseded by #48, the same change on a fresh branch.

@talktejas talktejas closed this Oct 5, 2026
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