Skip to content

Python: Propagate thread_id and forwarded_props through AG-UI to A2A context_id#5383

Merged
moonbox3 merged 3 commits intomicrosoft:mainfrom
moonbox3:agent/fix-5345-1
Apr 22, 2026
Merged

Python: Propagate thread_id and forwarded_props through AG-UI to A2A context_id#5383
moonbox3 merged 3 commits intomicrosoft:mainfrom
moonbox3:agent/fix-5345-1

Conversation

@moonbox3
Copy link
Copy Markdown
Contributor

Motivation and Context

When using the AG-UI protocol to front an A2A agent, the client-supplied thread_id was never forwarded as the A2A context_id, and forwarded_props from the AG-UI input payload were silently dropped. This broke session continuity and history tracing across the AG-UI → MAF → A2A boundary.

Fixes #5345

Description

In A2AAgent, _prepare_message_for_a2a now accepts an optional context_id fallback that is populated from session.service_session_id when the message itself does not carry an explicit context_id. In the AG-UI layer, both run_agent_stream and run_workflow_stream extract forwarded_props (or its camelCase variant forwardedProps) from the input payload and propagate it—via session metadata for the agent path and via function_invocation_kwargs for the workflow path—with a signature check to safely skip workflows whose run() method doesn't accept the extra kwarg. The _build_safe_metadata helper was also hardened to drop oversized values instead of truncating them into invalid JSON.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? If yes, add "[BREAKING]" prefix to the title of the PR.

Note: PR autogenerated by moonbox3's agent

When A2AAgent is used behind the AG-UI protocol, the client thread_id is
stored in session.service_session_id but was never forwarded as the A2A
context_id. This broke session continuity across the AG-UI → A2A boundary.

Add an optional context_id keyword argument to _prepare_message_for_a2a()
and pass session.service_session_id from run(). The explicit
message.additional_properties["context_id"] still takes precedence.

Fixes microsoft#5345

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 21, 2026 05:19
@moonbox3 moonbox3 self-assigned this Apr 21, 2026
@moonbox3 moonbox3 enabled auto-merge April 21, 2026 05:21
@moonbox3
Copy link
Copy Markdown
Contributor Author

moonbox3 commented Apr 21, 2026

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/a2a/agent_framework_a2a
   _agent.py2381693%355, 360, 362, 521, 538, 546, 567, 588, 616, 636, 650, 664, 676–677, 717–718
TOTAL28982347688% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
5756 30 💤 0 ❌ 0 🔥 1m 32s ⏱️

Copy link
Copy Markdown
Contributor Author

@moonbox3 moonbox3 left a comment

Choose a reason for hiding this comment

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

Automated Code Review

Reviewers: 4 | Confidence: 91%

✓ Correctness

This PR correctly threads session.service_session_id as a fallback context_id into _prepare_message_for_a2a, so that A2A messages get a context_id even when the message itself doesn't carry one explicitly. The or fallback pattern at line 678 properly prefers message-level context_id over the session-level fallback. additional_properties defaults to {} (confirmed at _types.py:1749-1751), so .get("context_id") safely returns None when absent, and None or context_id correctly falls through. The two new tests cover both the fallback and precedence scenarios. The pyright ignore comments in the sample files are appropriate for an optional import inside try/except. No correctness issues found.

✓ Security Reliability

This PR adds a fallback context_id parameter to _prepare_message_for_a2a so that when the message itself doesn't carry a context_id in additional_properties, the session's service_session_id is used instead. The logic is correct: message.additional_properties.get('context_id') or context_id correctly prefers the explicit message-level value over the fallback. The two sample file changes are trivial pyright suppression comments on an optional import already guarded by try/except. Tests cover both the fallback and the precedence behavior. No security, reliability, or resource-leak concerns were found.

✓ Test Coverage

The two new unit tests (test_prepare_message_for_a2a_uses_fallback_context_id and test_prepare_message_for_a2a_message_context_id_takes_precedence) adequately cover the core logic change in _prepare_message_for_a2a — both the fallback behavior and the precedence semantics. However, there is no integration-level test verifying that session.service_session_id is actually threaded through to the A2A message during a run() call. The existing integration tests (e.g., test_run_invokes_context_providers) use a MockA2AClient whose send_message does not capture the message argument, so the end-to-end wiring from session to context_id is untested. The sample-file pyright-ignore changes are non-functional and need no tests.

✓ Design Approach

The change is minimal and well-targeted. It maps session.service_session_id to the A2A context_id as a fallback when the message has no explicit context_id in additional_properties, which is the correct semantic mapping — service_session_id is how the framework tracks service-managed conversation identity across all providers (OpenAI, Anthropic, etc.), and A2A context_id serves the same role on the A2A side. The precedence logic (message.additional_properties.get("context_id") or context_id) correctly lets per-message overrides win. Tests cover both the fallback and the override cases. The pyright: ignore additions in the sample files silence a known type-checker false positive for an optional import guarded by try/except, which is appropriate. No design concerns found.

Suggestions

  • Consider adding an integration-level test that calls agent.run("Hello", session=session) with a session carrying a known service_session_id, captures the A2A message passed to MockA2AClient.send_message, and asserts message.context_id == session.service_session_id. This would verify the end-to-end wiring at line 300 of _agent.py. Currently MockA2AClient.send_message discards its message argument, so the mock would need a small enhancement (e.g., self.last_message = message) to support this assertion.

Automated review by moonbox3's agents

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves session continuity across the Agent Framework → A2A boundary by ensuring an A2A context_id can be derived from the framework AgentSession.service_session_id when the inbound message doesn’t explicitly provide one, and it tightens typing/linting in related Python samples.

Changes:

  • Add a kw-only context_id fallback parameter to A2AAgent._prepare_message_for_a2a() and wire it up from session.service_session_id in A2AAgent.run().
  • Add unit tests validating fallback context_id behavior and precedence rules.
  • Update optional orjson imports in conversation samples to satisfy Pyright strict mode.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
python/packages/a2a/agent_framework_a2a/_agent.py Adds session-derived fallback context_id wiring when preparing A2A messages.
python/packages/a2a/tests/test_a2a_agent.py Adds tests for fallback context_id and precedence over message-specified context_id.
python/samples/02-agents/conversations/file_history_provider.py Adds Pyright missing-import suppression for optional orjson.
python/samples/02-agents/conversations/file_history_provider_conversation_persistence.py Adds Pyright missing-import suppression for optional orjson.
Comments suppressed due to low confidence (1)

python/packages/a2a/agent_framework_a2a/_agent.py:599

  • The _prepare_message_for_a2a docstring wasn’t updated to document the new context_id kw-only parameter and its precedence rules (message.additional_properties['context_id'] vs fallback). Please update the docstring so callers and future maintainers understand when/why the fallback is applied.
    def _prepare_message_for_a2a(self, message: Message, *, context_id: str | None = None) -> A2AMessage:
        """Prepare a Message for the A2A protocol.

        Transforms Agent Framework Message objects into A2A protocol Messages by:
        - Converting all message contents to appropriate A2A Part types
        - Mapping text content to TextPart objects
        - Converting file references (URI/data/hosted_file) to FilePart objects
        - Preserving metadata and additional properties from the original message
        - Setting the role to 'user' as framework messages are treated as user input
        """

Comment thread python/packages/a2a/agent_framework_a2a/_agent.py
Copilot and others added 2 commits April 21, 2026 05:27
…ft#5345)

- Enhance MockA2AClient.send_message to capture last_message for assertions
- Add test_run_passes_session_service_session_id_as_context_id: verifies
  run() passes session.service_session_id through to A2A message context_id
- Add test_run_message_context_id_takes_precedence_over_session: verifies
  explicit message context_id wins over session fallback
- Update _prepare_message_for_a2a docstring to document context_id param
  and its precedence rules

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nt passing of context_id / thread_id in A2A/AG-UI implementations
Copy link
Copy Markdown
Contributor Author

@moonbox3 moonbox3 left a comment

Choose a reason for hiding this comment

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

Automated Code Review

Reviewers: 4 | Confidence: 95% | Result: All clear

Reviewed: Correctness, Security Reliability, Test Coverage, Design Approach


Automated review by moonbox3's agents

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: Inconvenient passing of context_id / thread_id in A2A/AG-UI implementations

4 participants