feat(reconcile): wake the lead when ready work has no actor - #63
Merged
Conversation
A task whose blocker clears becomes ready and nothing dispatches it. The reconciler may not — it reconciles and does not decide — and the lead, which may, is an interactive session that acts when spoken to. On 2026-09-10 a blocker cleared at 01:37 and the task went out at 08:05, because the operator typed "Status". So the pass after `collect` reads `plan`, and when the ready set has grown it types one line into the lead's terminal: which tasks are ready, and the command that sends them. Notifying is not deciding — nothing moves, and the choice is still the lead's. Three rules keep it from being worse than the silence it replaces, and `scripts/lib/notify_lead.py` owns all three: it fires on the transition and not on the pass, it refuses to type into a lead that is not at rest (the wake waits rather than being dropped), and it is silent when no lead session is running. What has been told is held in the reconciler's own runtime directory, so the loop still writes no record. `plan` makes five verbs where the selftest asserted four. The assertion is widened deliberately and argues in place why: it lists what the loop may WANT, and `plan` is a read that moves nothing, while `dispatch` and its neighbours change what runs and never may join it. Claude-Session: https://claude.ai/code/session_01Jff2KWdWmVMhCFAdZS6iRP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Close the gap where a fleet task that becomes ready has no actor and the lead is never woken. Measured failure: in orchestration/reconcile/reconcile.log, forge-agnostic/01-01-forge-seam landed at 2026-09-10T01:37:16 (PR #62 merged), clearing the semantic-dependency blocker on forge-agnostic/02-02-gitlab-adapter; that task was only dispatched at 08:05:07, because the operator typed 'Status' and the lead looked. Six hours 28 minutes ready and unclaimed.
This is NOT a bug in the reconciler and must not be 'fixed' by giving it the power to act. AGENTS.md states the loop reconciles and does not decide; scripts/reconcile-selftest.sh asserts the queue verbs it calls. The lead is the only actor that may dispatch, and it is an interactive session that acts when spoken to. reconcile.sh nudge wakes the LOOP; nothing went the other way.
Deliberate design decisions a reviewer reading only the diff would not know:
Also asked for by the brief: FLEET.md must tell the lead that landing a task is not the end of the work but what unblocks the next, and that queue.sh plan is checked before going quiet — one or two sentences, not an essay. That is the new paragraph after step 8 of '## The loop'. The brief noted voice-doom/01 recently rewrote FLEET.md's '## How you report', so this branch was rebased on main first and the sentence placed where the current structure wants it.
Method: the repo's own test-first rule was followed — the eighth promise and its assertions were added to scripts/reconcile-selftest.sh and confirmed to fail for the right reason (no plan call, no sends) before scripts/lib/notify_lead.py existed. Verified with ./scripts/check.sh (all checks green, exit 0) and the lead-resolution path was confirmed read-only against the live control plane at /home/magicletur/fleet (manifest names the session, session list resolves it to a live id and state) without ever sending to the operator's real Mission Control session.
Documentation touched deliberately and should not be flagged as scope creep: AGENTS.md (runtime dir contents, the four->five verb claim, a new load-bearing bullet), FLEET.md (the two sentences the brief asked for, plus a note in '## What you are not' so an unprompted line in the lead's terminal is explained rather than read as a bug), scripts/check.sh comments, and the fleet-queue, fleet-onboarding and update-fleet skills, each of which made a claim this change makes stale.
What Changed
scripts/lib/notify_lead.py, loadingscripts/lib/queue.pyviaimportlib(aliasfleet_queue, matchingfleet_status.py's pattern) to resolve the lead session's name and send it one line viathurbox-cli session sendwhen the ready set grows on a transition — never into a lead that isn'tidle/done, and never dropping a wake that couldn't be delivered (only records "told" on a successful send).scripts/reconcile.shadds anotify_lead()step that runs aftercollect(on its clock, not a new one), callingqueue.sh plan --jsonand piping it tonotify_lead.py --state-dir "$RT"; every branch exits 0 so a failed notification never fails the pass. AddsFLEET_LEAD_SESSIONenv override and expands the header/usage docs (including thenotifycadence entry) to describe the new behavior.scripts/reconcile-selftest.shwidens its asserted reconciler-verb set from four to five (watch,collect,shepherd,refuel,plan) and adds a test assertingnotify_leadfires once per ready-set transition, not on every pass;scripts/check.shupdates its reconciler check comment/message to match.AGENTS.mdandFLEET.mdto describe theorchestration/reconcile/notified.jsonruntime file, the five-verb reconciler contract, and that the loop now wakes the lead (a read-only notification, not a dispatch decision); also touches thefleet-onboarding,fleet-queue, andupdate-fleetskill docs and FLEET.md's## What you are notsection to keep their claims about the reconciler consistent with the new behavior.Risk Assessment
✅ Low: The change adds a notify-only capability (reconcile.sh -> notify_lead.py) that strictly reads the queue's plan and types into the lead's session; it never dispatches, never writes a queue record, and every claimed behavior (transition-only firing, pruning on re-entry, AT_REST allowlist excluding blocked/working, retry-until-delivered semantics, dedup of log notes, never failing the pass) is both implemented correctly (traced through concrete state sequences) and covered by real behavioral tests in reconcile-selftest.sh test 8, which drives the actual tick loop against stubbed queue.sh/thurbox-cli rather than asserting on source text. Documentation (AGENTS.md, FLEET.md, three skill files, check.sh) was updated consistently with the code and with each other, and shellcheck/py_compile are clean.</risk_rationale>
Testing
Baseline
./scripts/check.shalready passed; on top of that I ran the focusedscripts/reconcile-selftest.shsuite, which exercises the notify-on-ready behavior end-to-end through a stubbed queue and stubbed thurbox-cli (no real thurbox/network needed) — all 49 assertions pass at the target commit, and I additionally verified the new promise-8 assertions fail for the correct reason at the pre-fix base commit, giving genuine before/after regression evidence. No issues found; worktree left clean.Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
✅ **Review** - passed
✅ No issues found.
✅ **Test** - passed
✅ No issues found.
./scripts/check.sh./scripts/reconcile-selftest.sh (full run at target commit 9929dac — all 49 assertions pass, including the new promise-8 block: transition-only wake, one-line message naming ready tasks and the dispatch command, silence on repeated passes, growing ready set as fresh transition, a working lead not interrupted and the wake held rather than dropped, a held wake landing once the lead returns to idle, no-lead-session producing no send/no error, and the five-verb (watch/collect/shepherd/refuel/plan) — never dispatch — assertion)./scripts/reconcile-selftest.sh run against base commit bd8aca623cfa8c64bf9a077dbc3cee35632df72b in an isolated temp worktree with the new test script copied over, to confirm the new assertions fail for the right reason before the fix (no plan verb called, no wake delivered) and pass after it✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.