Repository navigation
fix(chat): restore history after failed or cancelled turns - #3861
Conversation
Verdict: Approve with suggestionsA failed or cancelled chat turn no longer leaves the user's unanswered question (and any retrieved RAG passages) stuck in the conversation, and the rollback correctly brings back entries that the bounded history buffer had already evicted. The fix is small, the ordering is right, and the new tests cover the cases that matter. The one thing worth doing before merge: the whole fix hinges on a subtle ordering detail — the turn is marked finished just before the final chunk is handed to the caller — and nothing in the test suite pins that down. Several callers (the CLI, Telegram, the voice SDK) stop reading as soon as they see the final chunk, so if someone later moves that one line, those callers would silently start losing history again while every test stayed green. One extra test closes that door. Smaller points: the same "snapshot and restore" block now appears three times in this one file and is worth pulling into a helper, the two public methods still don't mention the new guarantee in their own docs, and the new documentation note landed inside an unrelated example rather than in the section about conversation history. No security concerns. Real-world evidenceN/A — no evidence bundle was produced for this run, so my verdict rests on static review plus a standalone replication of the new control flow. I could not execute the repo's tests in this environment (the runner is missing the project's Python dependencies), so I am not claiming they pass; the PR description states the new unit tests and eight live Lemonade integration tests were run locally. The change is SDK-internal with no new CLI flag, route, or UI surface, so there are no pixels or endpoints pending. What I did verify directly: I replicated the exact generator/ 🔍 Technical detailsIssues found🟢 The
def test_history_survives_break_at_final_chunk(sdk):
sdk.llm_client.generate.return_value = iter(["one", "two"])
for chunk in sdk.send_stream("new question"):
if chunk.is_complete:
break
assert list(sdk.chat_history) == ["user: new question", "assistant: onetwo"]🟢 Third copy of the snapshot/restore block (
@contextmanager
def _history_rollback(self):
"""Restore the pre-turn history unless the caller commits."""
snapshot = list(self.chat_history)
turn = SimpleNamespace(committed=False)
try:
yield turn
finally:
if not turn.committed:
self.chat_history.clear()
self.chat_history.extend(snapshot)Call sites become 🟢 New guarantee is absent from the method docstrings (
🟢 Doc note landed in the wrong section ( The new line sits as a second stacked comment above 🟢 Rollback assumes one turn at a time per instance The restore is a whole-buffer Strengths
|
Failed requests and cancelled streams no longer leave unanswered messages or retrieved passages in conversation history. Rollback restores entries evicted from a full history buffer; closing at the final streamed chunk preserves the completed turn.
Fixes #3512.
Test plan: