fix(history): treat mid-turn absorption as delivery, not withdrawal - #14
fix(history): treat mid-turn absorption as delivery, not withdrawal#14Antisophy wants to merge 1 commit into
Conversation
d6153fc to
880e2c3
Compare
Recent Claude CLIs absorb queued messages into the running turn at a tool boundary instead of holding them for the next one: the entry is removed from the queue *after* delivery, and the delivery is recorded as a queued_command attachment rather than as a user line. Every remove was reported as consumed_as "removed", so messages the model demonstrably received were badged "not delivered", and the delivery itself was invisible because attachment records were never translated. Translate queued_command attachments into user messages and pair each one strictly with a remove. The pair's records land in either order, so an attachment against a still-queued entry is held until the entry's fate is known: a remove completes the absorption and releases it, a dequeue discards it (the echo carries the message in that flow, where recent CLIs also write the attachment as presentation), and one matching nothing renders nothing. An entry removed with no delivery record keeps the not-delivered outcome, so a genuine queue clear still reports honestly. Queue records are matched resiliently, because content is an unreliable key: the CLI omits it entirely for array-valued messages and texts can repeat. Matching prefers an exact text match, then empty-to-empty (an empty search names an array message, whose entry is stored empty too), and a remove finally settles the oldest unsettled entry, since the queue really did remove one. Echo pairing follows the same principle: dequeue records do not map enqueues one to one (the CLI can drain several entries under one dequeue, joining their texts into one echo), so the echo prefers the awaiting entry whose text it carries and a joined echo settles every queued entry whose text appears in it. Any entry left permanently unsettled would shift every later pairing behind by one, confirming each echo against the previous message's provisional and rendering every turn opener twice after reload. The delivery record is now the visible message, so it anchors checkpoints like any user line, and undo truncation takes its queue records with it, matched by the text they carry since the enqueue is not adjacent to the delivery. An absorbed delivery carries no file checkpoint (there is no user record for the CLI to checkpoint at), so its boundary offers conversation-only undo instead of a file revert the CLI would reject.
880e2c3 to
8aaef2c
Compare
|
Sorry, I can't make any sense of this change.
|
|
Without this change, user messages queued in the middle of the agent's turn are falsely marked "not delivered" after being later delivered. With it, they are shown as delivered (white text) after the agent's next tool use or response. |
|
I understand, and I don't deny that there is an improvement to be made here, but the technical claims of this PR do not add up. It cannot be merged as is, as that would imply permanently codifying untruths. |
|
Untruths are codified before this change, such as messages being marked "not delivered" which actually were delivered. My opinion for the path forward is to first resolve the user-facing issue, then refactor as desired. Edit: To clarify, I'm not saying you should blindly merge this. I'm just stating my opinion as a user. I intend to look into this further soon. |
No, that would be a bug, not an untruth. Do you see any code, documentation, or comments which makes false statements about this or other systems' behavior? This PR, as is, adds a bunch of them - and these are much worse than a bug, because they compromise the project's future. All future work builds on the assumption that stated claims are true.
Sorry, we really can't afford to go into technical debt. |
Recent Claude CLIs absorb queued messages into the running turn at a tool boundary instead of holding them for the next one. The queue entry is removed after delivery, and the delivery itself is recorded as a
queued_commandattachment rather than as a user line: the CLI's own absorption path removes the entry only once it has been folded into the running request, and marks each folded command's lifecycle as started. The queue-primary pipeline read every remove as "removed without being consumed", so absorbed messages were badged "not delivered" while the model demonstrably received them, and the delivery record was invisible because attachment lines were never translated.This PR translates
queued_commandattachments into user messages and pairs each one strictly with a remove. The pair's records land in either order, so an attachment against a still-queued entry is held until the entry's fate is known: a remove completes the absorption and releases it; a dequeue discards it, since in that flow the echo carries the message and recent CLIs write the attachment as presentation only; one matching nothing renders nothing. An entry removed with no delivery record keeps the not-delivered outcome, so a genuine queue clear still reports honestly.The delivery record becomes the visible message, so it anchors checkpoints like any user line, and undo truncation takes its queue records with it, matched by the text they carry because the enqueue is not adjacent to the delivery (the turn's own output sits between them). An absorbed delivery carries no file checkpoint, as there is no user record for the CLI to checkpoint at, so its boundary offers conversation-only undo instead of a file revert the CLI would reject with "not a user message in this session".
Covered by a replay unit test exercising both pairing orders, the dequeue-flow attachment, and an undelivered queue clear, plus reducer tests for the provisional handover. Full check set run; a handful of contention flakes passed on serial retry.
(Rebased onto 50cd0f9; the previously stacked undo fix is superseded by it and dropped.)
Update: real-world replay surfaced two hardening requirements now folded in. Queue records are matched resiliently rather than by content text alone (the CLI omits content for array-valued messages and texts can repeat): exact match, then empty-to-empty, and a remove finally settles the oldest unsettled entry. Echo pairing likewise prefers the awaiting entry whose text the echo carries, and a joined echo (several entries drained under one dequeue record, an older CLI behavior still present in long-lived sessions) settles every queued entry whose text appears in it. Without these, one stranded entry shifts every later pairing behind by one, and each turn opener renders twice after reload. The replay unit test covers the array-message flow and the pairing after it.