Skip to content

mu-z9ol solo: settle queued interjections by Done receipts; Esc recovers stale queued state#488

Merged
sahuagin merged 2 commits into
mainfrom
cc/mu-z9ol-interjection-receipts
Jul 14, 2026
Merged

mu-z9ol solo: settle queued interjections by Done receipts; Esc recovers stale queued state#488
sahuagin merged 2 commits into
mainfrom
cc/mu-z9ol-interjection-receipts

Conversation

@tcovert-c137

@tcovert-c137 tcovert-c137 Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

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 AwaitingFirstToken forever, and Esc refused to clear any of it (daemon reports idle → canceled=false → no clear). Diagnosed from live session a98446dc48f6bd71/session-1 and 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 joins pending_tickets, the message is spliced into the next InvokeLlm context, and both asks share one terminal Done (each ticket still gets its own receipt row). The solo TUI's queued-interjection bookkeeping (rpmkrmykzklo, 07-01) assumed one session.done per 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 (DoneEvent dropped command_receipts).

Change

  • Wire: DoneEvent gains command_receipts — one DoneCommandReceipt { command_event_id, request_id, method } per ask the Done satisfied; the forwarder maps the loop's CommandTickets onto it. Field omitted when empty (serde skip), so receiptless dones are wire-identical to before.
  • mu-solo: queued interjections settle by receipt id instead of the old one-per-terminal decrement (queued_interjection_response_countqueued_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 running persist_events_to_disk = false mint no tickets, so that mode keeps the legacy oldest-first decrement.
  • Esc escape hatch: cancel returning 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 pairs command_event_id with the session-log CommandReceived.
  • forwarder::translate_done_carries_command_receipts — multi-ticket wire mapping; params not echoed; empty receipts omit the field.
  • mu-solo unit tests for receipt extraction (i64-only id matching), absorbed-vs-deferred settlement, and the stale-state recovery predicate.

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

claude (aiteam) 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 sahuagin merged commit 43087b4 into main Jul 14, 2026
5 checks passed
@sahuagin sahuagin deleted the cc/mu-z9ol-interjection-receipts branch July 14, 2026 18:25
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