fix(agent): persist recovered final responses - #46183
Conversation
Close a recovery/fallback final_response with an assistant transcript entry before session persistence so durable history cannot end at a tool/user message after the caller receives a final answer. Adds a regression for a tool-tail transcript with a non-empty final_response. Related to NousResearch#46071 / NousResearch#46053, but covers the adjacent case where the assistant message was never appended before persistence.
|
Verification comment — clean review Reviewed the diff (3 files: What was checked:
The fix is minimal (7 lines of logic) and correctly addresses the session-history corruption path: without it, a durable session can end at a tool/user message even though the caller received a completed assistant response, causing the next turn to reload malformed history. |
|
Thanks for the careful writeup and the RED/GREEN validation, @WXBR — the observation is real, but this is a close, because the fix direction here is one we've already evaluated and deliberately did not adopt. The real bug was at the flush layer, not the transcript. The observable failure (a delivered assistant reply missing from
Appending a synthetic It also works against #53987 specifically: on The tool-tail close via Closing as superseded by #46071 and covered by the flush-layer work on current |
|
Merged via PR #56279 — your commit was cherry-picked onto current Live E2E confirmed the fix: recovery-path turn went from persisting |
Summary
Close non-interrupted recovered/fallback
final_responseturns with a durable assistant message before session persistence.This prevents the persisted transcript from ending at a
toolorusermessage after the caller has already received a completed assistant response. If that malformed tail is saved, the next turn can reload stale/incomplete history and appear to repeat or lose the visible final answer.Relation to #46071 / #46053
This is the same persistence-loop bug family as #46071 / #46053, but it is not the same root cause.
_flush_messages_to_session_db()losing newly appended assistant messages whenrepair_message_sequence()shortenedmessagesbelowconversation_historylength.finalize_turn()has a non-emptyfinal_response, but no closing assistant message has been appended tomessagesbefore_persist_session().In other words, #46071 makes flushing robust once an assistant message exists. This PR ensures the recovered final response exists in the transcript before persistence.
Changes
agent/turn_finalizer.py: before_persist_session(), append{role: "assistant", content: final_response}when the turn is not interrupted,final_responseis non-empty, and the transcript tail is not already assistant.tests/agent/test_turn_finalizer_final_response_persistence.py: regression for a transcript ending at a tool result whilefinal_response="Done."is returned.Validation
python -m pytest tests/agent/test_turn_finalizer_final_response_persistence.py::test_final_response_closes_tool_tail_before_persistence -q -o 'addopts='on cleanorigin/maintoolpython -m pytest tests/agent/test_turn_finalizer_final_response_persistence.py tests/run_agent/test_identity_flush.py tests/run_agent/test_compression_persistence.py tests/run_agent/test_860_dedup.py -q -o 'addopts='python -m py_compile agent/turn_finalizer.py tests/agent/test_turn_finalizer_final_response_persistence.pygit diff --checkDuplicate check
Searched existing issues/PRs and code for exact matches using terms including
turn_finalizer final_response,final_response assistant tail,final_response tool tail,pending tool result, and exact code/test markers. Found #46071/#46053 as related persistence repair work, but no exact duplicate for closing a recoveredfinal_responseinagent/turn_finalizer.pybefore persistence.