mu-z9ol solo: settle queued interjections by Done receipts; Esc recovers stale queued state#488
Merged
Merged
Conversation
added 2 commits
July 13, 2026 15:07
…ers stale queued state Absorbed mid-turn asks share one terminal Done (spec mu-046 WP4), but mu-solo assumed one session.done per queued interjection ask and wedged permanently: off-by-one response attribution, every prompt routed as queued, AwaitingFirstToken spin between turns, Esc refusing to clear. - wire: DoneEvent gains command_receipts (one DoneCommandReceipt per satisfied ask: command_event_id, request_id, method); forwarder maps the loop's CommandTickets onto it. Omitted when empty. - solo: queued interjections settle by receipt id, exactly — replaces the one-per-terminal decrement heuristic. Absorbed prompts commit without being awaited; deferred ones await their own receipted done. Receiptless-daemon fallback (persist_events_to_disk=false) keeps the old oldest-first decrement. - solo: Esc escape hatch — cancel returning canceled=false (daemon idle) with queued state armed commits pending prompt text and resets the bridge instead of leaving the session wedged.
…hing on receipted daemons Panel reviewer finding on the mu-z9ol PR gate (gpt-5.5, round 1): while the busy bridge is armed, finished_main can hold via streaming_route, so a receiptless session.done (wakeup/autonomous turn, clean shutdown) could blind-settle the oldest awaited interjection even on the default persisted path — marking a prompt settled without its response. Latch done_receipts_seen on the first terminal that carries command_receipts: a daemon that has minted one ticket mints them for every ask (WP4), so from then on the receiptless-terminal fallback is disabled and only exact receipt-id settlement applies. Never-receipted daemons (persist_events_to_disk = false) keep the legacy oldest-first behavior unchanged.
sahuagin
approved these changes
Jul 14, 2026
sahuagin
approved these changes
Jul 14, 2026
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.
Fixes mu-z9ol: a prompt submitted while a main-session ask was in flight wedged mu-solo permanently — every subsequent response rendered attached to the previous prompt, every new prompt was treated as "queued" even with an idle daemon, the status line spun in
AwaitingFirstTokenforever, and Esc refused to clear any of it (daemon reports idle →canceled=false→ no clear). Diagnosed from live sessiona98446dc48f6bd71/session-1and reproduced live.Root cause
A user message arriving mid-ask is absorbed into the running ask (
loop_/mod.rs:1266, spec mu-046 WP4): its ticket joinspending_tickets, the message is spliced into the nextInvokeLlmcontext, and both asks share one terminalDone(each ticket still gets its own receipt row). The solo TUI's queued-interjection bookkeeping (rpmkrmykzklo, 07-01) assumed onesession.doneper queued ask, so on the shared done it armed an "awaiting queued response" counter that nothing could ever pay off — and the receipts that would disambiguate never reached the wire (DoneEventdroppedcommand_receipts).Change
DoneEventgainscommand_receipts— oneDoneCommandReceipt { command_event_id, request_id, method }per ask the Done satisfied; the forwarder maps the loop'sCommandTickets onto it. Field omitted when empty (serde skip), so receiptless dones are wire-identical to before.queued_interjection_response_count→queued_interjection_awaiting_done_ids). An absorbed interjection commits below the response it was absorbed into without being awaited; a deferred one (mu-wf5w fresh-ask path) awaits its own receipted done. Daemons runningpersist_events_to_disk = falsemint no tickets, so that mode keeps the legacy oldest-first decrement.canceled=false(daemon idle) while queued state is armed now commits pending prompt text, clears the bridge, and says so — any future desync becomes one keypress instead of a client restart.Tests
pipeline_smoke::z9ol_wire_done_names_the_ask_it_satisfies— end-to-end over JSON-RPC on the WP4 journaled path: the wire done's receipt names the ask and pairscommand_event_idwith the session-logCommandReceived.forwarder::translate_done_carries_command_receipts— multi-ticket wire mapping; params not echoed; empty receipts omit the field.Gate
just ci-aipr: pre-pr-check green; consensus panel approve 3–0 with zero findings in all 4 rounds (ornith-q4-r0, glm-5.2, ornith-q4-r1). The gpt-5.5 rank0 seat was down with an openai-codex OAuth refresh failure (timeout r1, unparseable r2–4), so the panel could not reach unanimity and escalated; operator adjudicated and overrode (MU_REVIEW_OVERRIDE=1, logged). Seat outage and panel-topology follow-ups filed as mu-3ajg; presence shutdown hang found during verification filed as mu-ad5x (pre-existing on main).🤖 Generated with Claude Code
https://claude.ai/code/session_01REB3QZvP3arA2cLMVRgYam