Python: fix: clear service_session_id in _agent_wrapper when propagate_session=True - #5875
Conversation
Python Test Coverage Report •
Python Unit Test Overview
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Pull request overview
Fixes a Python agent-as-tool session propagation issue where a child agent can inherit the parent’s session.service_session_id, causing invalid previous_response_id usage and 400 errors with Responses-style APIs.
Changes:
- Update
Agent.as_tool(... propagate_session=True)wrapper to temporarily clearsession.service_session_idduring the child agent run and restore it afterward. - Add tests covering save/clear/restore behavior (including restore on child error).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| python/packages/core/agent_framework/_agents.py | Clears and restores service_session_id around delegated run() when propagate_session=True. |
| python/packages/core/tests/core/test_agents.py | Adds tests validating service_session_id isolation/restoration behavior for agent-as-tool. |
Comments suppressed due to low confidence (1)
python/packages/core/agent_framework/_agents.py:577
- The finally block only restores service_session_id when the original value was non-None. If the parent session starts with service_session_id=None, the child run can still set session.service_session_id (e.g., from streaming conversation_id propagation) and it will leak back to the parent. Consider always restoring the original value (including None), using a sentinel to distinguish “no session” vs “saved None”.
finally:
if session is not None and saved_service_session_id is not None:
session.service_session_id = saved_service_session_id
|
Hi Evan Mattson (@moonbox3) Eduard van Valkenburg (@eavanvalkenburg) — I've addressed both Copilot review comments in eb7bc4e (replaced in-place mutation with an isolated child session copy to avoid races under concurrent asyncio.gather). Could you take a look when you get a chance? |
…n=True When propagate_session=True, the child agent inherits the parent's service_session_id. After the parent's first LLM call, MAF auto-populates this from the Responses API conversation_id. The child sends it as previous_response_id which the server rejects because the parent's tool_call is still pending (400 error). This fix saves and clears service_session_id before calling the child agent and restores it in a finally block, preserving session.state sharing while isolating the server-side conversation pointer. Fixes microsoft#5874
Address Copilot review comments: - Create a child AgentSession with shared state dict but isolated service_session_id, avoiding race conditions under concurrent asyncio.gather tool invocations. - Update tests to verify child gets a separate session object and that child-set service_session_id does not leak to parent.
eb7bc4e to
81c3144
Compare
|
Benke Qu (@benke520) sorry for the delay. Can you please address the failing CI/CD checks? Thanks. |
…session isolation The existing test asserted captured_session is parent_session, but since we now create a separate child AgentSession (to avoid racing under concurrent asyncio.gather), the child is a different object. Updated assertions to verify: - child is NOT the parent object (isolation) - child shares the same session_id and state dict (by reference) - child's service_session_id is None (isolated)
Add 'assert captured_session is not None' before attribute access to satisfy mypy/pyright type checking on Optional values.
|
Helping get this past the finish line: I updated the branch with the latest main and fixed the test-typing errors. The refreshed CI is now running. |
|
Eduard van Valkenburg (@eavanvalkenburg) Sorry for the accidental re-request — no changes since your approval. The PR keeps getting kicked from the merge queue due to a flaky durabletask integration test ( |
Summary
When propagate_session=True, the child agent inherits the parent's session.service_session_id. After the parent's first LLM call, MAF auto-populates this from the Responses API's conversation_id. The child sends it as previous_response_id, which the server rejects because the parent's ool_call is still pending — resulting in a 400 error.
Changes
Tests
Added 3 new tests:
All 106 tests in est_agents.py pass.
Fixes #5874