diff --git a/agent/conversation_loop.py b/agent/conversation_loop.py index 9e58baf635579..e125281f4d14a 100644 --- a/agent/conversation_loop.py +++ b/agent/conversation_loop.py @@ -7613,25 +7613,23 @@ def run_conversation( continue messages.append(final_msg) - # Make the completed answer durable before leaving the loop. - # The text already reached the user through the streaming / - # interim display path, which is display-only and never writes - # to state.db; the next durable write would otherwise be - # finalize_turn's _persist_session, behind post-turn work that - # can include micro-compaction's aux-LLM call. A session torn - # down inside that window (ws_orphan_reap after a remote 1006 - # close, container stop) lost a reply the user had already - # seen (#81641). Same contract the tool-call exit gets from - # #49045 and the verify-on-stop exits above; the - # _DB_PERSISTED_MARKER dedup keeps _persist_session idempotent. - # - # Unlike the tool-call exit, a failed flush must NOT abort the - # turn: no side effect runs after this point, the answer is - # already produced, and _persist_session retries the write. + # Make the completed answer durable before leaving the loop — + # a session torn down before finalize_turn's _persist_session + # otherwise loses a reply the user already saw (#81641). Same + # contract as the tool-call exit (#49045) and the verify exits + # above; _DB_PERSISTED_MARKER keeps _persist_session idempotent. + # Unlike the tool-call exit, failure must NOT abort the turn: + # no side effect follows and _persist_session retries the write. + # Full incident narrative: tests/run_agent/test_81641_*.py. try: agent._flush_messages_to_session_db(messages, conversation_history) except Exception: - logger.debug("final text-turn flush failed", exc_info=True) + logger.warning( + "final text-turn flush failed (session=%s) — reply is " + "not yet durable; relying on finalize_turn retry", + getattr(agent, "session_id", None) or "none", + exc_info=True, + ) _turn_exit_reason = f"text_response(finish_reason={finish_reason})" if not agent.quiet_mode: diff --git a/tests/run_agent/test_81641_text_turn_incremental_persistence.py b/tests/run_agent/test_81641_text_turn_incremental_persistence.py index 56fde07cbc94e..271ce4bc1841d 100644 --- a/tests/run_agent/test_81641_text_turn_incremental_persistence.py +++ b/tests/run_agent/test_81641_text_turn_incremental_persistence.py @@ -138,9 +138,14 @@ class TestCompletedTextTurnIncrementalPersistence: f"transcript tail; observed events: {events!r}" ) - final_persist = max( + persist_indices = [ i for i, (kind, _) in enumerate(events) if kind == "persist_session" + ] + assert persist_indices, ( + "finalize_turn must call _persist_session after the loop exits; " + f"observed events: {events!r}" ) + final_persist = persist_indices[-1] assert answer_flushes[0] < final_persist, ( "The assistant row must be durable BEFORE post-loop finalization, " f"but the answer flush at index {answer_flushes[0]} did not precede "