From 51e42efd58b66acc172458cfe556107f6ae6fc77 Mon Sep 17 00:00:00 2001 From: MUHAMMAD SALMAN HUSSAIN <160324527+mshsheikh@users.noreply.github.com> Date: Fri, 5 Jun 2026 01:36:07 +0500 Subject: [PATCH 1/4] fix(session_persistence): count persisted items after filtering unpersistable conversation items ## Summary Fix an accounting mismatch in `save_result_to_session` for OpenAI Conversations sessions. The function currently calculates `saved_run_items_count` before filtering out items that cannot be persisted to the conversations store, especially reasoning items without a valid `id` or `encrypted_content`. As a result, the run state may advance as though items were saved even when they were not actually written. ## Root cause For OpenAI Conversations sessions: 1. `items_to_save` is built from the deduplicated input plus new run items. 2. `saved_run_items_count` is computed from that unfiltered collection. 3. Unpersistable items are removed afterward. That ordering causes the persisted-item count to drift from the actual items stored in the session. ## Fix Move the `_is_unpersistable_for_openai_conversation(...)` filtering step before: - serialization - fingerprint counting - `saved_run_items_count` calculation This ensures the persisted count reflects only the items that are truly eligible to be saved. ## Expected behavior after the fix - Items that cannot be persisted are excluded before accounting. - `run_state._current_turn_persisted_item_count` stays aligned with actual session writes. - Retry and resume logic no longer assumes an item was persisted when it was filtered out. ## Validation Recommended test coverage: - A conversation session containing an unpersistable reasoning item. - Verify that: - the item is excluded from `items_to_save` - `saved_run_items_count` does not include it - `run_state._current_turn_persisted_item_count` matches the actual persisted items ## Notes This is a logic-ordering fix only. It does not change persistence schema, item normalization, or conversation protocol behavior. --- src/agents/run_internal/session_persistence.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/agents/run_internal/session_persistence.py b/src/agents/run_internal/session_persistence.py index f483da13a3..65d4c64eca 100644 --- a/src/agents/run_internal/session_persistence.py +++ b/src/agents/run_internal/session_persistence.py @@ -322,6 +322,11 @@ async def save_result_to_session( if is_openai_conversation_session and items_to_save: items_to_save = [_sanitize_openai_conversation_item(item) for item in items_to_save] + if is_openai_conversation_session: + items_to_save = [ + item for item in items_to_save if not _is_unpersistable_for_openai_conversation(item) + ] + serialized_to_save: list[str] = [ _fingerprint_or_repr(item, ignore_ids_for_matching=ignore_ids_for_matching) for item in items_to_save @@ -336,11 +341,6 @@ async def save_result_to_session( serialized_to_save_counts[serialized] -= 1 saved_run_items_count += 1 - if is_openai_conversation_session: - items_to_save = [ - item for item in items_to_save if not _is_unpersistable_for_openai_conversation(item) - ] - if len(items_to_save) == 0: if run_state: run_state._current_turn_persisted_item_count = already_persisted + saved_run_items_count From cc47d8d4ff8112441c46905543b2c0aa3590476e Mon Sep 17 00:00:00 2001 From: MUHAMMAD SALMAN HUSSAIN <160324527+mshsheikh@users.noreply.github.com> Date: Fri, 5 Jun 2026 02:39:28 +0500 Subject: [PATCH 2/4] fix(session_persistence): keep raw retry offset aligned with persisted count ## Summary Fix a retry-offset accounting mismatch in `save_result_to_session` for `OpenAIConversationsSession`. The function now distinguishes between: - the raw number of turn items consumed for retry alignment, and - the number of items actually persisted to the session store. This prevents retries from re-slicing the raw turn stream at the wrong index when some items are filtered out as unpersistable. ## Root cause `run_state._current_turn_persisted_item_count` was being updated from `saved_run_items_count`, which reflects only items actually persisted. For OpenAI Conversations sessions, some items can be counted for retry progress but still be filtered out before persistence. In that case, using the persisted count as the retry cursor causes the next retry to start at the wrong raw index and can duplicate already-processed items. ## Fix Introduce a separate raw-turn count derived from `new_run_items` after retry-specific merging, and use that value to advance `run_state._current_turn_persisted_item_count`. Keep `saved_run_items_count` as the persisted-item return value, so persistence accounting remains accurate. ## Expected behavior after the fix - Retry alignment tracks how far the raw turn stream advanced. - Persisted-item counts still reflect only items that were actually saved. - A retry after a leading unpersistable reasoning item no longer re-saves the following persistable item. ## Validation Recommended regression coverage: - `new_items = [unpersistable reasoning item, assistant message]` - first save persists only the assistant message - second save with the same raw `new_items` does not duplicate the assistant message - `run_state._current_turn_persisted_item_count` advances by the raw consumed count, not just the persisted count ## Notes This is an accounting and retry-cursor fix only. It does not change session schema, item serialization, or conversation protocol behavior. --- src/agents/run_internal/session_persistence.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/agents/run_internal/session_persistence.py b/src/agents/run_internal/session_persistence.py index 65d4c64eca..78b2ecbe7a 100644 --- a/src/agents/run_internal/session_persistence.py +++ b/src/agents/run_internal/session_persistence.py @@ -280,6 +280,10 @@ async def save_result_to_session( if missing_outputs: new_run_items = missing_outputs + new_run_items + # Raw retry offset: count the run items we consumed from this turn, + # not just the subset that was actually persisted. + new_run_items_raw_count = len(new_run_items) + input_list: list[TResponseInputItem] = [] if original_input: input_list = normalize_input_items_for_api( @@ -343,13 +347,17 @@ async def save_result_to_session( if len(items_to_save) == 0: if run_state: - run_state._current_turn_persisted_item_count = already_persisted + saved_run_items_count + run_state._current_turn_persisted_item_count = ( + already_persisted + saved_run_items_count + ) return saved_run_items_count await session.add_items(items_to_save) if run_state: - run_state._current_turn_persisted_item_count = already_persisted + saved_run_items_count + run_state._current_turn_persisted_item_count = ( + already_persisted + saved_run_items_count + ) if response_id and is_openai_responses_compaction_aware_session(session): has_local_tool_outputs = any( From 3e20b7e7383d5fa2f72492677de7a884c1026e73 Mon Sep 17 00:00:00 2001 From: MUHAMMAD SALMAN HUSSAIN <160324527+mshsheikh@users.noreply.github.com> Date: Fri, 5 Jun 2026 02:57:22 +0500 Subject: [PATCH 3/4] fix(session_persistence): use raw retry offset for conversation turn state ## Summary Fix a retry-offset accounting mismatch in `save_result_to_session` for `OpenAIConversationsSession`. The function now distinguishes between: - the raw number of turn items consumed for retry alignment, and - the number of items actually persisted to the session store. This prevents retries from re-slicing the raw turn stream at the wrong index when some items are filtered out as unpersistable. ## Root cause `run_state._current_turn_persisted_item_count` was being updated from `saved_run_items_count`, which reflects only items actually persisted. For OpenAI Conversations sessions, some items can be counted for retry progress but still be filtered out before persistence. In that case, using the persisted count as the retry cursor causes the next retry to start at the wrong raw index and can duplicate already-processed items. ## Fix Introduce a separate raw-turn count derived from `new_run_items` after retry-specific merging, and use that value to advance `run_state._current_turn_persisted_item_count`. Keep `saved_run_items_count` as the persisted-item return value, so persistence accounting remains accurate. ## Expected behavior after the fix - Retry alignment tracks how far the raw turn stream advanced. - Persisted-item counts still reflect only items that were actually saved. - A retry after a leading unpersistable reasoning item no longer re-saves the following persistable item. ## Validation Recommended regression coverage: - `new_items = [unpersistable reasoning item, assistant message]` - first save persists only the assistant message - second save with the same raw `new_items` does not duplicate the assistant message - `run_state._current_turn_persisted_item_count` advances by the raw consumed count, not just the persisted count ## Notes This is an accounting and retry-cursor fix only. It does not change session schema, item serialization, or conversation protocol behavior. --- src/agents/run_internal/session_persistence.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/agents/run_internal/session_persistence.py b/src/agents/run_internal/session_persistence.py index 78b2ecbe7a..b29a46ecf7 100644 --- a/src/agents/run_internal/session_persistence.py +++ b/src/agents/run_internal/session_persistence.py @@ -348,7 +348,7 @@ async def save_result_to_session( if len(items_to_save) == 0: if run_state: run_state._current_turn_persisted_item_count = ( - already_persisted + saved_run_items_count + already_persisted + new_run_items_raw_count ) return saved_run_items_count @@ -356,7 +356,7 @@ async def save_result_to_session( if run_state: run_state._current_turn_persisted_item_count = ( - already_persisted + saved_run_items_count + already_persisted + new_run_items_raw_count ) if response_id and is_openai_responses_compaction_aware_session(session): From 5f050e4dede0a1691f5f86659034a302e5861498 Mon Sep 17 00:00:00 2001 From: MUHAMMAD SALMAN HUSSAIN <160324527+mshsheikh@users.noreply.github.com> Date: Fri, 5 Jun 2026 03:14:18 +0500 Subject: [PATCH 4/4] fix(session_persistence): return raw offset for resumed OpenAI conversation saves ## Summary Fix a retry-offset mismatch in `save_result_to_session` for resumed OpenAI Conversations saves. For resumed saves, the helper is called with `run_state=None`, and the caller advances its retry cursor using the returned value. In that path, returning only `saved_run_items_count` can undercount raw items when an unpersistable reasoning item is filtered out before persistence. ## Root cause The function now tracks: - `saved_run_items_count`: how many items were actually persisted - `new_run_items_raw_count`: how many raw turn items were consumed for retry alignment That split is correct internally, but resumed callers still need the raw offset when they advance retry state themselves. ## Fix Introduce a `returned_count` value and use raw-offset semantics for resumed OpenAI Conversations saves: - `run_state is None` and `is_openai_conversation_session` -> return `new_run_items_raw_count` - otherwise -> return `saved_run_items_count` Keep the in-function state update on the raw count as well. ## Expected behavior after the fix - Internal state advancement stays aligned with the raw turn stream. - Resumed saves no longer re-slice at an already-consumed assistant item. - Persisted-item accounting remains unchanged. ## Validation Recommended regression coverage: - `new_items = [unpersistable reasoning item, assistant message]` - resumed save returns the raw consumed count - next retry does not re-save the assistant message - persisted count remains accurate for the items actually written ## Notes This is a retry-accounting fix only. It does not change schema, serialization, or conversation protocol behavior. --- src/agents/run_internal/session_persistence.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/agents/run_internal/session_persistence.py b/src/agents/run_internal/session_persistence.py index b29a46ecf7..5aa7108f65 100644 --- a/src/agents/run_internal/session_persistence.py +++ b/src/agents/run_internal/session_persistence.py @@ -345,6 +345,12 @@ async def save_result_to_session( serialized_to_save_counts[serialized] -= 1 saved_run_items_count += 1 + returned_count = ( + new_run_items_raw_count + if is_openai_conversation_session and run_state is None + else saved_run_items_count + ) + if len(items_to_save) == 0: if run_state: run_state._current_turn_persisted_item_count = (