Python: Fix A2AAgent to use session.service_session_id as authoritative context_id#5431
Python: Fix A2AAgent to use session.service_session_id as authoritative context_id#5431giles17 wants to merge 3 commits intomicrosoft:mainfrom
Conversation
…osoft#5353) AgentSession.service_session_id now takes precedence over message.additional_properties['context_id'] in _prepare_message_for_a2a(), making the session the authoritative source for context_id. The message property is still used as a fallback when no session is provided. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
giles17
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 94%
✓ Correctness
The diff correctly flips the precedence of
context_idin_prepare_message_for_a2a()so that the session-level identifier (passed via thecontext_idkeyword argument, sourced fromAgentSession.service_session_idat line 300) takes priority over the per-messageadditional_properties["context_id"]. The code change is a single-line swap frommessage.additional_properties.get("context_id") or context_idtocontext_id or message.additional_properties.get("context_id"), the docstring is updated to match, and the tests are correctly updated to assert the new precedence. A new test (test_run_message_context_id_used_when_no_session) covers the fallback path where no session is provided. Theorshort-circuit semantics are correct for this use case since both sides are eitherNoneor a non-empty string.
✓ Security Reliability
This is a clean, minimal, and correct fix. The change flips the precedence of
context_idin_prepare_message_for_a2a()so that the session-derivedcontext_idparameter (fromAgentSession.service_session_id) takes priority overmessage.additional_properties["context_id"]. The call site at line 300 correctly passessession.service_session_id if session else None, so the session is now the authoritative source. The docstring update matches the new behavior. Tests are well-structured: they cover session-wins-over-message, fallback-when-no-session, and the existing fallback-when-no-additional-properties case. No security or reliability concerns — no new trust boundaries, no injection surface, no resource leaks.
✓ Test Coverage
The code change correctly flips the precedence so that
context_idfrom the session takes priority overmessage.additional_properties. The existing tests were updated to match the new behavior, and a new integration test (test_run_message_context_id_used_when_no_session) covers the backward-compatible fallback path. However, one edge case is missing: when a session is provided but itsservice_session_idisNone, the fallback toadditional_properties["context_id"]should still work. This scenario is untested at the integration level.
✓ Design Approach
The change is correct, minimal, and addresses the right root cause. Flipping the precedence order from
message.additional_properties.get('context_id') or context_idtocontext_id or message.additional_properties.get('context_id')makes the session-levelservice_session_idthe authoritative source of truth forcontext_id, while retainingadditional_propertiesas a backward-compatible fallback. The call site at line 300 already threadssession.service_session_idin ascontext_id, so no further plumbing changes are needed. Tests are updated to match the new semantics and a new test covers the fallback path. No design issues found.
Suggestions
- Add a test for the case where a session is provided but
service_session_idisNone— this should still fall back tomessage.additional_properties["context_id"]. Currentlytest_run_message_context_id_used_when_no_sessiononly tests the no-session path, butAgentSession(service_session_id=None)passed assessionis a different and realistic scenario (line 300 of_agent.pyevaluates toNonewhensession.service_session_idisNone).
Automated review by giles17's agents
There was a problem hiding this comment.
Pull request overview
This PR fixes A2AAgent session consistency by using AgentSession.service_session_id as the authoritative A2A context_id, rather than letting message.additional_properties["context_id"] override it.
Changes:
- Update
A2AAgent.run()to passsession.service_session_idinto_prepare_message_for_a2a()as the primarycontext_id. - Change
_prepare_message_for_a2a()to prefer the session-derivedcontext_idand only fall back tomessage.additional_properties["context_id"]when no session context is available. - Update/add tests to verify precedence (session wins), fallback behavior (message used when no session), and integration through
run().
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| python/packages/a2a/agent_framework_a2a/_agent.py | Ensures session-level service_session_id is used as the authoritative A2A context_id and adjusts precedence resolution. |
| python/packages/a2a/tests/test_a2a_agent.py | Updates/extends unit + integration tests to cover new precedence and fallback behavior. |
…t-service-id test Address review feedback: - Use explicit 'is not None' check instead of 'or' for context_id precedence so empty strings don't incorrectly fall through - Add test for AgentSession() with no service_session_id, verifying fallback to message additional_properties context_id Fixes microsoft#5353 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
giles17
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 92%
✓ Correctness
The change correctly fixes the context_id resolution logic. Previously,
context_id or message.additional_properties.get('context_id')would incorrectly fall through to the additional_properties fallback when context_id was an empty string". The newcontext_id if context_id is not None else ...properly distinguishes between 'not provided' (None) and 'explicitly set' (including empty string). More importantly, it fixes the case where a session exists but has no service_session_id — the call site at line 300 passessession.service_session_id if session else None, which yields None when the session has no service_session_id, and the old truthy check would correctly fall through. However, the semantic intent is clearer withis not None, and the new test at line 955 properly covers the edge case. The change is minimal, correct, and well-tested.
✓ Security Reliability
The diff changes the context_id fallback logic from truthiness-based (
or) to an explicit None check (is not None), which is semantically more correct. With the old code, an empty stringservice_session_idwould be silently overidden by theadditional_propertiesfallback; the new code preserves any explicitly-set value. For the common case (None vs actual string), both old and new code behave identically. The new test correctly validates the scenario where a session exists butservice_session_idis None, ensuring the fallback toadditional_propertiesstill works. No security, reliability, or resource leak issues found.
✗ Test Coverage
The code change from
ortois not Noneis correct and prevents falsy-but-valid values (like empty string) from incorrectly falling through to theadditional_propertiesfallback. However, the new test only covers theservice_session_id=Nonecase, which produces identical behavior under both the old and new code. The distinguishing edge case — whereservice_session_idis an empty string"— is not tested, meaning the new test doesn't actually validate the behavioral change introduced by this PR.
✓ Design Approach
The
or→is not Nonechange at line 686 is a correct precision fix: whenservice_session_idis an empty string", the oldoroperator would incorrectly discard it and fall back toadditional_properties, whileis not Nonepreserves it. The new test, however, only exercises theNonepath (anAgentSession()with noservice_session_id), which behaved identically under both the old and new code. The actual changed behaviour — preserving an empty string as a validcontext_id— is not covered by the new test.
Flagged Issues
- The new test
test_run_message_context_id_used_when_session_has_no_service_iddoes not exercise the behavioral change. Whenservice_session_id=None, bothNone or fallbackandNone if None is not None else fallbackevaluate tofallback. A test withservice_session_id=""is needed to distinguish the newis not Nonelogic from the oldorlogic.
Suggestions
- Add a companion test where
AgentSession(service_session_id="")is passed and verify that the empty string is preserved as thecontext_id(not overridden byadditional_properties). This is the only scenario where the behavior actually differs between the old and new code, and it would have failed before this fix.
Automated review by giles17's agents
…ft#5353) Add test_run_empty_service_session_id_preserved to verify that an empty string service_session_id is preserved as context_id and not overridden by the additional_properties fallback. This is the only scenario where the 'is not None' check behaves differently from the old 'or' logic. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
giles17
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 92%
✓ Correctness
The new test correctly validates that an empty string service_session_id is preserved (not overridden by additional_properties fallback). The implementation at _agent.py:686 uses
context_id is not None, which correctly distinguishes empty string from None. The test follows the exact same pattern as the adjacent session context_id tests. No issues found.
✓ Security Reliability
This diff adds a single test case that validates an edge case: when AgentSession.service_session_id is an empty string, it should be preserved as the context_id rather than being overridden by additional_properties. The test is well-structured, consistent with neighboring tests, and correctly exercises the existing code path (empty string is not None, so it passes the
is not Nonecheck in _prepare_message_for_a2a). No security or reliability concerns.
✓ Test Coverage
The new test
test_run_empty_service_session_id_preservedis a well-constructed edge case test that validates theis not Nonecheck at_agent.py:686. It ensures an empty stringservice_session_idis preserved rather than falling back toadditional_properties['context_id']. The assertions are meaningful (checking exact value"rather than just no-exception). The ``@mark.asynciodecorator is unnecessary given `asyncio_mode = "auto"` in `pyproject.toml:44`, but it is consistent with the four sibling tests in this section (lines 907, 920, 938, 955), so this is not a blocking concern. Overall the context_id test suite provides good coverage of the priority hierarchy: session id present > fallback to additional_properties > session exists but service_session_id is None > empty string preserved.
✗ Design Approach
The test documents and locks in a questionable design choice: treating
AgentSession(service_session_id=")as an intentionally set, validcontext_idthat overides theadditional_propertiesfallback. The implementation'sis not Noneguard (line 686 of_agent.py) causes an empty string to be forwarded to the A2A server ascontext_id=", which is not a meaningful A2A identifier. The existing test at line 955–970 already covers the correct fallback path for the unset case (AgentSession()withservice_session_id=None); the semantically equivalent case ofservice_session_id=""should behave identically. The ``@mark.asynciodecorator is also unnecessary given `asyncio_mode = "auto"` in pyproject.toml, consistent with the rest of the test file (the prior four tests in this region also carry the redundant decorator, so this is a pre-existing inconsistency rather than a new one).
Flagged Issues
- The test asserts that
AgentSession(service_session_id="")producescontext_id=""on the outgoing A2A message, even when a non-empty fallback exists inadditional_properties. An empty string is not a valid A2A context identifier andservice_session_id=""is semantically 'not set', identical toNone. The implementation at_agent.py:686usesis not None, so this test encodes and pins a bug: the A2A client will sendcontext_id=""to the remote server. The fix should use a truthiness check instead (context_id if context_id else …) and this test should instead assert thatservice_session_id=""falls back toadditional_properties["context_id"], matching theNonebehaviour already tested at line 955–970.
Suggestions
- The ``@mark
.asynciodecorator on all five session context_id integration tests (lines 907, 920, 938, 955, 973) is redundant since `asyncio_mode = "auto"` is configured in `pyproject.toml:44`. Consider removing all five decorators in a follow-up for consistency with the rest of the test file, which relies on auto-detection. - The ``@mark
.asynciodecorator on line 973 is redundant because `asyncio_mode = "auto"` is configured in pyproject.toml. This is consistent with how the four preceding tests (lines 907, 920, 938, 955) were written, but consider cleaning up all five decorators in a follow-up.
Automated review by giles17's agents
Motivation and Context
A2AAgent extracted
context_idfrommessage.additional_propertiesinstead of usingAgentSession.service_session_id, which is the intended authoritative source for session identity. This broke session consistency when both were present, as the message property incorrectly took precedence.Fixes #5353
Description
The root cause was in
_prepare_message_for_a2a, which unconditionally readcontext_idfrommessage.additional_propertieswith no way to pass in the session-level identifier. The fix adds acontext_idkeyword argument to_prepare_message_for_a2a(sourced fromsession.service_session_idat the call site inrun()) and changes the resolution order so the session-level value takes precedence, falling back toadditional_properties["context_id"]only when no session identifier is available. Tests cover the precedence behavior, the fallback path, and full integration throughrun().Contribution Checklist