Python: clear service_session_id on full-history replay - #7682
Python: clear service_session_id on full-history replay#7682Ruiming Zhao (uuzzrm) wants to merge 2 commits into
Conversation
When a coordinator sends an AgentExecutorRequest that replays a prior conversation (including function calls and results), the executor session still carries the previous run's service_session_id. Providers then receive both previous_response_id and the same items inline, which the Responses API rejects with a Duplicate item found error. Clear the pointer only when the explicit input replays prior turns. A plain new user turn keeps it so providers can continue via previous_response_id, and from_response() is untouched so chained executors keep their pointer.
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Updates AgentExecutor to prevent provider “Duplicate item found” errors by clearing service_session_id when a request appears to replay prior assistant/tool turns, and expands workflow tests to cover both replay and incremental-turn behavior.
Changes:
- Clear
service_session_idwhen inbound messages contain assistant/tool roles (interpreted as a full-history replay). - Convert the previously
xfailreplay test into a passing assertion. - Add a new test ensuring a “new user turn only” path preserves
service_session_id.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| python/packages/core/tests/workflow/test_full_conversation.py | Updates coordinator/test coverage for “replay history clears session id” and adds “new turn preserves session id”. |
| python/packages/core/agent_framework/_workflows/_agent_executor.py | Clears service_session_id when detecting replayed history across multiple input handlers; introduces _replays_full_history(). |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
| def _replays_full_history(messages: list[Message]) -> bool: | ||
| """Return True when the input explicitly replays prior conversation turns. | ||
|
|
||
| A full-history replay contains assistant and/or tool messages that carry | ||
| server-issued response items (function calls, reasoning, results). Running | ||
| such a replay while the session still holds a service_session_id makes | ||
| the provider receive both the previous_response_id pointer and the same | ||
| items inline, which the Responses API rejects with a "Duplicate item found" | ||
| error. Incremental turns (user messages only) keep the pointer so providers | ||
| can continue the conversation via previous_response_id. | ||
| """ | ||
| return any(message.role in ("assistant", "tool") for message in messages) |
There was a problem hiding this comment.
Good catch — the docstring overpromised. Updated it to describe the role-based heuristic as written (cda92a5): assistant/tool roles are the signal that a prior turn is being replayed, since those are the turns a previous run produced. Kept the name as-is since that still matches the intent.
| messages = normalize_messages_input(text) | ||
| self._cache.extend(messages) | ||
| if self._replays_full_history(messages): | ||
| self._session.service_session_id = None | ||
| await self._run_agent_and_emit(ctx) |
There was a problem hiding this comment.
Fair point. Added coverage for all three handlers in cda92a5: from_messages with a full-history list (clears), from_messages with only a user turn (preserves), from_message with a replayed assistant message (clears), and from_str with a plain prompt (preserves).
The docstring now describes the role-based heuristic accurately, and tests exercise from_messages/from_message/from_str alongside run() for both replay (clears) and incremental (preserves) inputs.
Motivation & Context
When a workflow coordinator sends an
AgentExecutorRequestthat replays a prior conversation — including the function calls and results from an earlier run — the executor's session still carries the previous run'sservice_session_id. The provider then receives both theprevious_response_idpointer and the same items inline, which the Responses API rejects withDuplicate item found.The chat client already strips server-issued response items from the wire input when service-side storage is in play (#3295), but the executor wiring still forwarded a stale continuation pointer for full-history replays. This closes that gap so the two layers agree.
Description & Review Guide
AgentExecutornow clearssession.service_session_idbefore running when an explicit input replays prior conversation turns (messages that includeassistantortoolroles). This applies to therun,from_str,from_message, andfrom_messageshandlers.from_responseis untouched: chained executors keep their pointer so the API can continue viaprevious_response_id.xfailcovering the executor-layer half of Python: [Bug]: "Duplicate item found" error when using AzureAIClient with tools in workflows #3295 is removed and now passes, and a new test pins the incremental-turn boundary.AgentExecutorno longer risks aDuplicate item foundprovider error, and multi-turn continuation viaservice_session_idkeeps working for plain new turns.assistant/toolmessages) and whether the explicit-input handlers are the right places for it.Related Issue
Fixes #4292
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.