Skip to content

Python: clear service_session_id on full-history replay - #7682

Open
Ruiming Zhao (uuzzrm) wants to merge 2 commits into
microsoft:mainfrom
uuzzrm:fix/clear-service-session-id-on-full-replay
Open

Python: clear service_session_id on full-history replay#7682
Ruiming Zhao (uuzzrm) wants to merge 2 commits into
microsoft:mainfrom
uuzzrm:fix/clear-service-session-id-on-full-replay

Conversation

@uuzzrm

Copy link
Copy Markdown
Contributor

Motivation & Context

When a workflow coordinator sends an AgentExecutorRequest that replays a prior conversation — including the function calls and results from an earlier run — the executor's session still carries the previous run's service_session_id. The provider then receives both the previous_response_id pointer and the same items inline, which the Responses API rejects with Duplicate 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

  • What are the major changes?
    • AgentExecutor now clears session.service_session_id before running when an explicit input replays prior conversation turns (messages that include assistant or tool roles). This applies to the run, from_str, from_message, and from_messages handlers.
    • from_response is untouched: chained executors keep their pointer so the API can continue via previous_response_id.
    • A single new user turn also keeps the pointer — only full-history replays are cleared.
    • The strict xfail covering 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.
  • What is the impact of these changes?
    • Replaying a full conversation to an AgentExecutor no longer risks a Duplicate item found provider error, and multi-turn continuation via service_session_id keeps working for plain new turns.
  • What do you want reviewers to focus on?
    • The role-based detection of a full-history replay (assistant/tool messages) and whether the explicit-input handlers are the right places for it.

Related Issue

Fixes #4292

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_id when inbound messages contain assistant/tool roles (interpreted as a full-history replay).
  • Convert the previously xfail replay 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.

Comment on lines +331 to +342
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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +264 to 268
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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: AgentExecutor keeps service_session_id when replaying full conversation

2 participants