Skip to content

Python: Add A2A channel for agent-framework-hosting#6699

Open
eavanvalkenburg wants to merge 3 commits into
microsoft:mainfrom
eavanvalkenburg:eavan/python-hosting-a2a
Open

Python: Add A2A channel for agent-framework-hosting#6699
eavanvalkenburg wants to merge 3 commits into
microsoft:mainfrom
eavanvalkenburg:eavan/python-hosting-a2a

Conversation

@eavanvalkenburg

Copy link
Copy Markdown
Member

Motivation & Context

Adds Agent-to-Agent (A2A) protocol support for the agent-framework-hosting stack, allowing an AgentFrameworkHost to be exposed as an A2A-compatible agent endpoint. This enables agents built with Agent Framework to participate in multi-agent A2A workflows as callable service agents.

This is part of the channel-by-channel split of the hosting work tracked in #6265.

Description & Review Guide

Major changes:

  • agent-framework-hosting-a2a package — two key classes:

    • A2AChannel — mounts the A2A SDK's Starlette app onto the AgentFrameworkHost, registering the agent card and routing A2A task requests through the channel pipeline (run_hook, context providers, history providers)
    • HostAgentExecutor — A2A AgentExecutor implementation that bridges A2A task lifecycle (submit → working → completed/failed) to the AF hosting HostedRunResult
    • Multi-modal content: _contents_to_parts converts AF Content items (text, URI, data) to A2A Part objects
    • Streaming: A2A progress notifications dispatched while consuming ResponseStream; final response published via TaskUpdater on completion
    • Session continuity: A2A context_id and task_id used as isolation keys for the AF session
  • Test layout already uses tests/hosting_a2a/ with no __init__.py

Impact: New alpha package with no changes to existing packages. Adds agent-framework-hosting-a2a to the workspace and lockfile.

Reviewer focus: A2A task lifecycle mapping (submit/working/completed/failed) in HostAgentExecutor, content-to-parts conversion completeness, session isolation key choice.

Related Issue

Fixes #6591
Refs #6265

Contribution Checklist

  • I have read the CONTRIBUTING guidelines.
  • My changes include tests.
  • My changes include documentation (README).
  • This is not a breaking change.

- Add agent-framework-hosting-a2a package with A2AChannel exposing
  an AgentFrameworkHost as an A2A-compatible agent endpoint
- HostAgentExecutor routes A2A task requests through the hosting
  channel pipeline (run_hook, context providers, history providers)
- Multi-modal content support: text, URI, and data content items
  converted to A2A Parts via _contents_to_parts
- Streaming: A2A progress notifications sent while consuming
  ResponseStream; TaskUpdater publishes final response on completion
- Test layout uses tests/hosting_a2a/ (no tests/__init__.py)
- Remove old [tool.mypy] section and mypy poe task; remove empty
  [dependency-groups] section; source type-checking via pyright
- Update uv.lock, pyproject.toml workspace sources, and PACKAGE_STATUS.md

Fixes microsoft#6591
Refs microsoft#6265

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 24, 2026 07:23
@moonbox3 moonbox3 added documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python labels Jun 24, 2026
@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/hosting-a2a/agent_framework_hosting_a2a
   _channel.py40197%88
   _executor.py1061783%56–57, 60, 67, 71, 124–127, 134, 154–158, 192, 214
TOTAL42294499888% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
8314 37 💤 0 ❌ 0 🔥 2m 6s ⏱️

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

This PR adds a new Python hosting channel package, agent-framework-hosting-a2a, enabling an AgentFrameworkHost to expose its hosted target (agent/workflow) over the A2A (Agent-to-Agent) protocol by publishing an agent card and JSON-RPC routes and bridging A2A task execution through the hosting pipeline.

Changes:

  • Adds the new agent-framework-hosting-a2a package with A2AChannel (route contribution + agent card) and HostAgentExecutor (A2A task lifecycle → host run/run_stream).
  • Introduces unit/integration tests for the channel/executor and a small content conversion helper (Content → A2A Part).
  • Registers the new package in the Python workspace and package status list.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
python/pyproject.toml Adds agent-framework-hosting-a2a to the workspace package list.
python/packages/hosting-a2a/agent_framework_hosting_a2a/init.py Exposes the public package surface and version.
python/packages/hosting-a2a/agent_framework_hosting_a2a/_channel.py Implements A2AChannel that contributes agent-card + JSON-RPC routes to the host.
python/packages/hosting-a2a/agent_framework_hosting_a2a/_executor.py Implements HostAgentExecutor and AF Content → A2A Part conversion + streaming behavior.
python/packages/hosting-a2a/tests/hosting_a2a/test_channel.py Adds tests covering card defaults, route contribution, executor behavior, and content conversion.
python/packages/hosting-a2a/pyproject.toml Defines the new package metadata, dependencies, and tooling config.
python/packages/hosting-a2a/README.md Documents usage and behavior (routes, card generation, session mapping, task store persistence).
python/packages/hosting-a2a/LICENSE Adds MIT license file for the new package.
python/PACKAGE_STATUS.md Registers the new package as alpha.

Comment thread python/packages/hosting-a2a/agent_framework_hosting_a2a/_executor.py Outdated
Comment thread python/packages/hosting-a2a/pyproject.toml

@github-actions github-actions Bot 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.

Automated Code Review

Reviewers: 5 | Confidence: 60%

✓ Correctness

The A2A channel implementation is well-structured and correct for its stated use cases. The bridging between A2A task lifecycle and AF hosting pipeline is properly implemented: context_id maps to session isolation, streaming publishes incremental artifacts, non-streaming publishes working-state messages, and errors/cancellations are handled. The _contents_to_parts conversion correctly handles text, URI, and data content types for standard media types. No high-severity correctness issues found.

✓ Security Reliability

The new A2A channel package is well-structured and follows established patterns from the existing agent_framework_a2a package. The code correctly validates required inputs (context_id, message), handles cancellation, and maps A2A lifecycle to the host pipeline. The str(exc) error reporting pattern matches the existing A2AExecutor convention.

✓ Test Coverage

The test suite provides good coverage of the happy paths for both streaming and non-streaming execution, content-to-parts conversion, and agent card building. However, there are notable gaps in error-path coverage: the cancel method, the CancelledError/Exception handling branches in execute, the missing-message validation guard, and edge cases in _contents_to_parts (invalid data URIs, unsupported content types) all lack tests.

✓ Failure Modes

No actionable issues found in this dimension.

✗ Design Approach

The overall shape matches the existing hosting and A2A abstractions, but there is one blocking design gap: the executor is documented as exposing hosted Workflows as A2A agents, yet its non-streaming projection only reads .messages, so workflow outputs are dropped and the task completes with no reply content. I also found a smaller streaming design regression versus the existing A2A executor: chunks without message_id are not coalesced into a single artifact.

Flagged Issues

  • _executor.py:159-165 only projects result.result.messages. For hosted Workflow targets the host returns HostedRunResult[WorkflowRunResult], and the host contract explicitly says channels must project workflow outputs themselves. As written, an A2A call to a hosted Workflow completes without any response content.

Suggestions

  • In _run_stream, generate a stable fallback artifact ID when update.message_id is absent (it is optional per AgentResponseUpdate). The existing A2AExecutor already uses a generated default ID so clients can coalesce chunks into one progressive message; without it each chunk becomes a separate artifact.

Automated review by eavanvalkenburg's agents

@github-actions

Copy link
Copy Markdown
Contributor

Flagged issue

_executor.py:159-165 only projects result.result.messages. For hosted Workflow targets the host returns HostedRunResult[WorkflowRunResult], and the host contract explicitly says channels must project workflow outputs themselves. As written, an A2A call to a hosted Workflow completes without any response content.


Source: automated DevFlow PR review

eavanvalkenburg and others added 2 commits June 24, 2026 10:35
- Fix HostAgentExecutor streaming artifact coalescing by using one stable
  per-task artifact id, even when update.message_id is missing or varies
- Add streaming fallback to text updates when contents are empty
- Fix workflow result projection in non-streaming mode by consuming
  get_outputs() values in addition to response.messages
- Add _value_to_parts helper to convert workflow outputs and fallback
  values into A2A Parts
- Expand tests to validate stable artifact ids and workflow output
  projection from HostedRunResult[WorkflowRunResult]
- Harden A2A test fakes for strict typing (agent protocol shape,
  context/request casts, event queue override)
- Add package-local test dependency group with uvicorn to make test
  execution self-contained

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add _strip_options_hook as the default run_hook for HostAgentExecutor
  so callers cannot inject model options without an explicit override
- Replace getattr(update, 'contents', None) or [] with direct
  update.contents access; remove dead text-fallback branch
- Wire ChannelRunHook type annotation on _run_hook field
- Add test_default_hook_strips_options_when_no_run_hook_supplied
- Add samples/04-hosting/af-hosting/local_a2a/ sample with:
  - app.py: WeatherAgent + A2AChannel + FileHistoryProvider + run_hook
  - call_client.py: A2AAgent client (non-streaming + streaming)
  - pyproject.toml and README.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@eavanvalkenburg eavanvalkenburg mentioned this pull request Jun 24, 2026
9 tasks
@eavanvalkenburg eavanvalkenburg self-assigned this Jun 24, 2026
@eavanvalkenburg eavanvalkenburg marked this pull request as ready for review June 24, 2026 10:49

@github-actions github-actions Bot 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.

Automated Code Review

Reviewers: 3 | Confidence: 66%

✓ Correctness

No actionable issues found in this dimension.

✓ Test Coverage

The test suite covers the happy paths well (non-streaming, streaming, workflow outputs, content conversion, default hook), but mises the error-handling branches of the A2A task lifecycle — specifically the cancel() method, the CancelledErrorTASK_STATE_CANCELED path, and the ExceptionTASK_STATE_FAILED path. Given the PR rationale explicitly highlights 'A2A task lifecycle mapping (submit/working/completed/failed)' as a reviewer focus area, having no tests for the failure and cancellation states is a notable gap.

✗ Design Approach

The A2A channel mostly follows the hosting abstractions correctly, but the streaming executor currently drops the shaped final response returned by run_stream(). That makes response_hook a no-op for streaming A2A calls even though the hook is explicitly threaded through the API and other channels consume the hooked final response.

Flagged Issues

  • python/packages/hosting-a2a/agent_framework_hosting_a2a/_executor.py:204-221 passes response_hook into ChannelContext.run_stream(...) and then discards the value from await stream.get_final_response(). The host only applies response_hook in _HostResponseStream.get_final_response() (python/packages/hosting/agent_framework_hosting/_host.py:376-386), so any streaming A2A request whose hook rewrites or synthesizes the final reply silently loses that output.

Automated review by eavanvalkenburg's agents

append=True if appended else None,
)
appended = True
await stream.get_final_response()

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.

In streaming mode the response_hook is effectively ignored. ChannelContext.run_stream(..., response_hook=...) only applies that hook when get_final_response() is awaited and returned (python/packages/hosting/agent_framework_hosting/_host.py:376-386), but this method drops the shaped result after awaiting it. That means a hook that rewrites or adds the final assistant reply works for other channels (see python/packages/hosting-responses/agent_framework_hosting_responses/_channel.py:398-430) but never reaches A2A clients here.

@github-actions

Copy link
Copy Markdown
Contributor

Flagged issue

python/packages/hosting-a2a/agent_framework_hosting_a2a/_executor.py:204-221 passes response_hook into ChannelContext.run_stream(...) and then discards the value from await stream.get_final_response(). The host only applies response_hook in _HostResponseStream.get_final_response() (python/packages/hosting/agent_framework_hosting/_host.py:376-386), so any streaming A2A request whose hook rewrites or synthesizes the final reply silently loses that output.


Source: automated DevFlow PR review

)


app = _build_app().app

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.

The README/docstring say to run python app.py, but the file ends at app = _build_app().app with no if __name__ == "__main__" block, so it just builds the app and exits without serving. The sibling local_responses/app.py has the launcher.

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.

I noticed these when running the sample with the server"

  1. A single tool-using request floods the log (_executor.py):
A2AChannel does not support content type: function_call. Omitted.  (x5)
A2AChannel does not support content type: usage / function_result / text. Omitted.

In streaming, function_call/function_result/usage logs at WARNING on every tool call

  1. Also, the and content.text guard sends empty-text text content to the else, mislabeling supported text as unsupported.

@giles17 giles17 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.

Automated Code Review

Reviewers: 5 | Confidence: 60% | Result: All clear

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


Automated review by giles17's agents

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

Labels

documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hosting: A2A channel

4 participants