Report fleet progress to the operator unprompted - #430
Merged
Conversation
TheGreatAxios
force-pushed
the
cl-5761-orchestrator-reporting
branch
from
August 9, 2026 01:34
be3abea to
760af89
Compare
Interrupting discarded every pending queue and steer item. The common gesture is to type a correction and then interrupt so it lands sooner, which meant the interrupt destroyed exactly the input the operator most wanted delivered. Pending items now survive the stop and hand over at the next drain boundary.
Interrupting was the only way to ask whether a fleet was progressing, and it stopped the work to answer. /status reads the live session store and prints one row -- running lanes with their clocks, which of them have gone quiet, and the finished tally -- from the palette, which does not touch the run.
Every lane completion, stall and failure already passed through the parent session, which said nothing about any of it unless interrupted and asked -- an operator watched half a fleet finish with the remaining work undispatched and had to stop the run to find out. Lane changes now drive the report directly, so a completion lands with what it produced and a failure with what went wrong, at the moment it happens. The noise boundary is the whole design: only transitions are reported, a quiet lane is announced once rather than on every tick it stays quiet, more than three changes in one observation collapse into a single tally so a dozen lanes cost a line rather than a screen, and the fleet running dry gets its own line because that is the silence that started this.
A stop does not reliably produce an idle event to drain against, so a message the operator had queued sat in the badge with nothing left to release it. The interrupt delivers it directly; the host serialises the send behind the agent rebuild the same interrupt started.
Running a real fleet showed both halves of the noise boundary leaking. A parallel dispatch arrives as one store change per lane, so six lanes printed six dispatch lines instead of the one decision they were; the report now settles briefly before observing, and six lanes read as "6 dispatched". Long worker summaries also wrapped, costing two rows an update, so a line is clipped to the width a transcript row has.
The deliver path caught failures silently on the theory a dropped message was harmless. It wasn't: the shell had already popped the queue item and painted it as delivered, and a failed rebuild left currentAgent pointing at the closed agent, so deliver threw and the catch ate it. Extracted the guard into a small testable function that checks fatalBuildError up front (mirroring the send path) and routes any failure through the system notice.
…ty lands Rebasing onto #429's call-id-keyed tool clock left FleetLane's structural type one field short of SubAgentSession, so the fleet report and the agents panel were reading two different lane shapes.
docs/TUI.md described Ctrl+C as discarding the queue, which stopped being true once the interrupt started keeping pending input. It also had no mention of the fleet-report notice channel added alongside the agents panel, so the two surfaces read as unrelated when they draw from the same store and stall definition. Also suppress the fleet's coalesced tally line when the idle line already says the same thing verbatim.
TheGreatAxios
force-pushed
the
cl-5761-orchestrator-reporting
branch
from
August 9, 2026 04:53
760af89 to
03c5ec2
Compare
Ctrl+C JSDoc still said the path cleared pending items after the implementation started keeping them. The fleet-report section credited the 400ms settle timer with quiet detection and named /fleet; quiet is polled on the 5s stall interval, settle only coalesces burst store changes, and the command is /status. Kept-queue drain is at the interrupt itself, not a later boundary via applyShellInterrupt.
Docs already split FLEET_REPORT_SETTLE_MS from FLEET_STALL_POLL_MS; the runner wiring comment still blamed quiet detection on the settle timer. Match the two-timer design so the next doc pass cannot re-poison from this call site.
5 tasks
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.
The orchestrator had a complete picture of the fleet at all times and said nothing about it unless interrupted and asked. An operator watched half a fleet finish with the remaining work undispatched, stopped the run to find out whether it was stuck, and lost a queued message doing it.
Three changes, smallest first.
Interrupting no longer discards queued input. The queue survives the stop and is handed over at the interrupt itself. Delivering it there rather than waiting on an idle boundary matters: a stop does not reliably produce one, so the alternative was a message stranded in the badge forever. The host serialises the send behind the agent rebuild the same interrupt started.
/statusanswers where the fleet is without touching the run. One row, read from the live session store: running lanes with their clocks, which of them have gone quiet, and the finished tally. Palette commands do not go through the prompt queue, so asking costs nothing.The fleet reports itself, unprompted. Lane changes drive the report directly, so a completion lands with what it produced and a failure with what went wrong at the moment it happens, not at the next turn boundary. A five-second poll covers the one change that produces no event at all, a lane going quiet.
The noise boundary is the design, not a detail on top of it:
fleet · 6 dispatchedrather than six linesStalled is not re-derived here;
agentProgressis the one definition and this asks it.Observed on a real six-lane fleet:
Eight rows for six lanes, and the last one is the answer to the question that forced the interrupt.
Closes CL-5761.