The FIFO is (created_at, job_id) at second resolution; B and C registered
within the same second once in the full suite, C sorted first by id and
cleared while B's signal was still fresh, so the stale line never printed.
C7 already keeps the 1.2 s gap; C8 now does too and asserts the order.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
MAM_ONBOARD_WAIT_S (min 1, 600) and MAM_DISPATCH_WAIT_S (min 0, 600) in
test_issue26_ints.INT_KNOBS, which pins every mam_int_knob call. The
real-herdr box's teardown removes its HOME once more after a second:
herdr writes .local/state/herdr/agent-detection a moment after the
server is gone, which left an empty /tmp/mam50-* behind.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Reproduction first (herdr 0.9.3, a real claude, one-off server, job
51ccd800): after a job that left a background subagent running
(`← 1 agent` on screen for 5 min), herdr reported `working` only while
the job ran and `idle` ~10 s after `completed`; a second submit with a
20 s idle wait was injected, not cancelled. The 2026-10-04 observation
(0.9.1, 10+ min of `working`) does not reproduce on 0.9.3, so the herdr
idle wait stays and the registry wait runs before it (plan §4.3 (나)).
- lib_py/delegation/wrapper.py: `open-jobs` (diagnostic) and
`wait-open-jobs`. An open job of the session is live when the #18
contract says so: `running` with an event in events.log within
idle_timeout_sec; `pending` with a dispatch signal (dispatch_wait_at,
else created_at) within ack_timeout_sec. A dead record is reported
(`stale open job <id> ignored (...)`) and never waited for. The waiter
signals every 10 s by merging dispatch_wait_at into its own pending
record through the registry's transition boundary (status untouched, no
event), so later waiters see it alive; a waiter whose wrapper is gone
stops and its signal goes stale (Creator's challenge, job bf8863e1).
Release is FIFO by (created_at, job_id): `clear` when no live running
job remains and this job is the earliest live pending one; `busy <ids>`
at the bound min(MAM_DISPATCH_WAIT_S, remaining wall clock).
- the wrapper: _registry_wait after the preflight in both dispatch paths;
`busy` closes the job as cancelled/session_busy (phase registry_wait),
exit 5 - the existing contract.
- tests/test_issue50_dispatch.py: the open-job table (status x age x
signal x spelling), FIFO and the tie, the signal as a registry merge,
the bound, a dead wrapper, finalize-end's new phase, both call sites.
- tests/test_issue50_dispatch_herdr.py: a real herdr 0.9.3 server per
test (short mkdtemp HOME, fake claude the server reports idle, the real
publish path over the suite's broker): C1 wait-then-run, C2 the bound,
C3 a dead running record, C4 the loop runner's first job behind an
onboarding, C5 a session without a row, C7 two waiters in FIFO order
with no cancellation, C8 a killed waiter skipped once stale; and three
concurrent `create --onboard` / `worktree_prepare.sh --onboard` in one
server (one server process survives the shims' concurrent bootstrap,
so no bootstrap lock is needed).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>