Skip to content

fix: avoid stale object-id dedupe in server conversation tracker#3621

Open
chutch wants to merge 1 commit into
openai:mainfrom
chutch:fix/oai-tracker-stable-keys-no-object-id
Open

fix: avoid stale object-id dedupe in server conversation tracker#3621
chutch wants to merge 1 commit into
openai:mainfrom
chutch:fix/oai-tracker-stable-keys-no-object-id

Conversation

@chutch

@chutch chutch commented Jun 11, 2026

Copy link
Copy Markdown

Summary

OpenAIServerConversationTracker kept long-lived set[int] state for object ids in
sent_items and server_items. prepare_input used those ids to skip generated items:

raw_item_id = id(raw_item)
if raw_item_id in self.sent_items or raw_item_id in self.server_items:
    continue

That is unsafe after the original object is gone, because CPython can reuse the same address for a
later allocation. A fresh function_call_output can then be treated as already sent and omitted
from the next server-managed continuation request, which leaves the provider with a function call
but no output.

This changes the in-process identity tracking to keep object references instead of raw id()
integers:

  • sent_items and server_items now store the tracked objects.
  • Identity membership uses is, not equality.
  • Keeping the reference alive prevents the tracked identity from becoming a stale address.
  • rewind_input removes the exact tracked object when an input is rewound.

The existing stable dedupe layers are unchanged: provider item ids, server tool-call ids, and
fingerprints still handle their existing retry/resume cases.

This is related to #2798 / #2800, but covers a different live path. #2800 removed one hydrated
initial-input identity seeding path during resume; it did not change the live prepare_input
identity check, mark_input_as_sent, or track_server_items.

Tradeoffs

The tracker now retains the tracked objects for the tracker's lifetime, rather than retaining only
integer ids. That is intentional for correctness, but it can keep large tool-output objects alive
longer in very long runs. Identity membership is also a linear scan instead of a set lookup; the
tracked collections are expected to be small in normal runs.

Test plan

  • Added test_prepare_input_keeps_fresh_tool_output_when_stale_identity_matches, parametrized over
    sent_items and server_items. The test fails on current origin/main with
    AssertionError: assert 'call_FRESH' in [] and passes with this change.
  • Added test_prepare_input_dedupes_same_delivered_tool_output_object to preserve the existing
    same-object dedupe behavior after delivery.
  • PYTHONPATH=/private/tmp/oai-agents-stable-keys/src python -m pytest tests/test_server_conversation_tracker.py
    • 23 passed
  • PYTHONPATH=/private/tmp/oai-agents-stable-keys/src python -m pytest tests/test_oaiconv_resume_response_id.py tests/test_agent_runner.py::test_conversation_id_only_sends_new_items_multi_turn tests/test_agent_runner.py::test_conversation_id_only_sends_new_items_multi_turn_streamed
    • 3 passed
  • PYTHONPATH=/private/tmp/oai-agents-stable-keys/src python -m pytest tests/test_agent_runner.py
    • 150 passed
  • python -m ruff check .
    • passed
  • python -m ruff format --check .
    • passed
  • PYTHONPATH=/private/tmp/oai-agents-stable-keys/src python -m mypy src/agents/run_internal/oai_conversation.py tests/test_server_conversation_tracker.py
    • passed
  • pyright --project pyrightconfig.json src/agents/run_internal/oai_conversation.py tests/test_server_conversation_tracker.py
    • 0 errors

Closes #3620

@chutch

chutch commented Jun 11, 2026

Copy link
Copy Markdown
Author

Design note: a pure call_id-based key would be simpler and would avoid the object-retention tradeoff in this PR. I did not use it because I could not find a public guarantee that call_id is unique across the relevant conversation/session scope, and the existing repo tests include multi-turn fixtures that reuse call_id values. If the API contract can guarantee uniqueness for the scope this tracker needs, a call_id-based implementation would be worth considering.

@chutch

chutch commented Jun 12, 2026

Copy link
Copy Markdown
Author

@seratch Do you think we can rely on the uniqueness of call_id and simplify the code, or it can't be guaranteed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenAIServerConversationTracker can drop a fresh tool output after id() reuse

2 participants