diff --git a/agent/conversation_loop.py b/agent/conversation_loop.py index bcd84a373bb..3a687fe7138 100644 --- a/agent/conversation_loop.py +++ b/agent/conversation_loop.py @@ -602,6 +602,14 @@ def run_conversation( repaired_seq, agent.session_id or "-", ) + # Clamp the SessionDB flush cursor after compaction. If repair + # merged or dropped messages, _last_flushed_db_idx may now point + # past the new end of `messages`, causing turn-end flush to skip + # the assistant/tool chain entirely (#44837). + if hasattr(agent, "_last_flushed_db_idx"): + agent._last_flushed_db_idx = min( + agent._last_flushed_db_idx, len(messages) + ) api_messages = [] for idx, msg in enumerate(messages): diff --git a/run_agent.py b/run_agent.py index 8026a602c68..0be8b1763fa 100644 --- a/run_agent.py +++ b/run_agent.py @@ -1560,7 +1560,12 @@ class AIAgent: if not self._session_db_created: self._ensure_db_session() start_idx = len(conversation_history) if conversation_history else 0 - flush_from = max(start_idx, self._last_flushed_db_idx) + # Guard against the flush cursor overshooting the message list. + # This can happen when repair_message_sequence compacts the list + # (merging consecutive users, dropping stray tools) after the + # cursor was set. Fall back to start_idx so we don't skip + # persisting the assistant/tool chain (#44837). + flush_from = max(start_idx, min(self._last_flushed_db_idx, len(messages))) for msg in messages[flush_from:]: role = msg.get("role", "unknown") content = msg.get("content")