From 29eaa86cda52f519a362b291e4435e571721a24c Mon Sep 17 00:00:00 2001 From: dajiaohuang Date: Wed, 7 Oct 2026 14:48:02 +0800 Subject: [PATCH] Fix duplicate session history on failed-run recovery (fixes #1310) _salvage_stream_to_session previously did pre_run_items + replay, which duplicated items when stream.to_input_list() already included the pre-cycle input (the seeded initial_input is in both). Detect the overlap and avoid duplication by checking if replay suffix matches pre_run_items. --- strix/core/execution.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/strix/core/execution.py b/strix/core/execution.py index 91d94498..aad336ea 100644 --- a/strix/core/execution.py +++ b/strix/core/execution.py @@ -156,7 +156,14 @@ async def _salvage_stream_to_session( stream: Any, agent_id: str, ) -> None: - """Persist a crashed run's full history so a revived agent loses no context.""" + """Persist a crashed run's history so a revived agent loses no context. + + ``stream.to_input_list()`` reconstructs the conversation from this run cycle + as ``original_input + new_items``. After ``seed_initial_input``, the session + (pre_run_items) already contains the initial input on the first cycle, so + blindly concatenating duplicates it. We detect the overlap and append only + the delta. + """ if stream is None: return try: @@ -164,8 +171,12 @@ async def _salvage_stream_to_session( except Exception: logger.exception("could not build salvage history for %s", agent_id) return - desired = list(pre_run_items) + replay - if len(desired) <= len(pre_run_items): + pre_len = len(pre_run_items) + if pre_len > 0 and len(replay) >= pre_len and replay[-pre_len:] == pre_run_items: + desired = replay + else: + desired = list(pre_run_items) + replay + if len(desired) <= pre_len: return try: await replace_session_items(session, desired)