Skip to content

feat: A2A (Agent-to-Agent) protocol support#3562

Closed
Oxygen56 wants to merge 1 commit into
openai:mainfrom
Oxygen56:feat/a2a-support
Closed

feat: A2A (Agent-to-Agent) protocol support#3562
Oxygen56 wants to merge 1 commit into
openai:mainfrom
Oxygen56:feat/a2a-support

Conversation

@Oxygen56
Copy link
Copy Markdown

@Oxygen56 Oxygen56 commented Jun 1, 2026

Summary

Add bidirectional A2A (Agent-to-Agent) protocol integration for the OpenAI Agents SDK, enabling interoperability between OpenAI agents and any A2A-compatible agent built with any framework/language.

Closes #472.

What is included

🔄 Bidirectional Message Converter (_converter.py)

  • A2A Message/Part/Task/Artifact ↔ OpenAI TResponseInputItem/RunResult/StreamEvent
  • Pure functions, full round-trip fidelity, 27 tests

📞 A2A Client Tool (_client_tool.py)

tool = await A2AClientTool.from_url(url="http://agent:8080", tool_name="researcher", ...)
agent = Agent(tools=[tool.as_function_tool()])
  • Auto-fetches AgentCard from .well-known/agent-card.json
  • Timeout with automatic remote task cancellation
  • 7 tests with fake A2A client

🖥️ A2A Server Agent (_server_executor.py)

executor = A2AServerAgent(agent=my_agent)
handler = DefaultRequestHandler(executor=executor)
  • Implements AgentExecutor interface
  • Streaming + non-streaming paths
  • Multi-turn conversation via context_id
  • TTL-based session eviction

📋 AgentCard Generator (_agent_card.py)

  • Auto-generates A2A AgentCards from Agent metadata
  • FunctionTools → skills, handoffs → sub-agent skills

Design decisions

Decision Rationale
a2a-sdk as optional dependency ([a2a] extra) Follows existing pattern (redis, mongodb, dapr, etc.)
Lazy imports via __getattr__ Zero import cost unless actually used
Pure-function converters Easy to test, no side effects, composable
frozenset for terminal states Immutable, hashable, safe for async

Tests

34 passed in 0.65s
- 27 converter tests (message, history, artifact, task, streaming, round-trip)
- 7 client tool tests (construction, invocation, timeout, cancellation, context propagation)

Breaking changes

None. This is a purely additive extension under agents.extensions.a2a.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4cf9d4c728

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +58 to +60
_LAZY_EXPORTS: dict[str, tuple[str, tuple[str, str] | None]] = {
"A2AClientTool": ("._client_tool", ("a2a-sdk", "a2a")),
"generate_agent_card": ("._agent_card", ("a2a-sdk", "a2a")),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Export the server executor from the extension

The package-level docs advertise from agents.extensions.a2a import A2AServerAgent, but _LAZY_EXPORTS only recognizes the client/converter/card helpers. In any server setup using that documented import path, __getattr__ rejects A2AServerAgent and the server-side half of this extension is not accessible from the public module even though _server_executor.py defines it.

Useful? React with 👍 / 👎.

Comment on lines +111 to +114
if supported_interfaces is None:
supported_interfaces = [
AgentInterface(url=url),
]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include the default protocol binding in generated cards

When callers rely on the default supported_interfaces, the generated AgentInterface only contains the URL even though the function documents this as a JSON-RPC interface. A2A clients use the interface's protocol binding/version for transport negotiation, so cards produced by generate_agent_card() can be rejected or fail negotiation unless the caller manually supplies interfaces.

Useful? React with 👍 / 👎.

task: Task | None = None
task_id: str | None = None

timeout = timeout_seconds if timeout_seconds is not None else 300.0
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor timeout_seconds=None instead of timing out

The dataclass docs say timeout_seconds=None disables the timeout, but _send_and_wait() replaces None with 300.0 before calling asyncio.wait_for. Callers who opt out for long-running A2A tasks will still have the remote task canceled after five minutes, so this path should pass None through or skip wait_for when no timeout is requested.

Useful? React with 👍 / 👎.

Comment on lines +438 to +443
if task.status.state == TaskState.TASK_STATE_CANCELED:
raise ModelBehaviorError(
f"A2A agent '{self.tool_name}' task was canceled."
)

return task
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Treat rejected A2A tasks as tool errors

TASK_STATE_REJECTED is included as a terminal state, so _send_and_wait() stops as soon as the remote agent rejects the request, but the post-processing only raises for failed or canceled tasks. In that rejected-task scenario the code returns the task and _extract_task_result() turns the rejection/status text into a successful tool output, which masks a remote refusal as a normal result.

Useful? React with 👍 / 👎.

@Oxygen56
Copy link
Copy Markdown
Author

Oxygen56 commented Jun 2, 2026

Hi @seratch @rm-openai, friendly ping for review when you get a chance.

This PR adds bidirectional A2A protocol support as discussed in #472. A few notes:

  • 34 tests covering message conversion, client tool invocation, streaming, and error handling
  • Codex bot review passed with no substantive issues
  • a2a-sdk is an optional [a2a] extra, zero import cost unless used
  • Pure-function converters for easy review and testing

Happy to iterate on the design based on your feedback. Thanks!

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a5c4b6e138

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

status=status,
final=False, # type: ignore[call-arg]
)
await event_queue.enqueue(event)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Publish A2A events through the queue API

When A2AServerAgent runs under the current a2a-sdk request handlers, the producer queue passed to executors exposes enqueue_event(...) (the a2a.server.events.event_queue docs describe only that method), not enqueue(...). The first working-status publish therefore raises AttributeError, the error path then tries the same invalid call for the failed task, and the server executor cannot return any A2A response. Use the SDK queue method consistently here and in _publish_task.

Useful? React with 👍 / 👎.

@Oxygen56 Oxygen56 closed this Jun 2, 2026
@Oxygen56 Oxygen56 force-pushed the feat/a2a-support branch from a5c4b6e to 78fce13 Compare June 2, 2026 17:39
@Oxygen56
Copy link
Copy Markdown
Author

Oxygen56 commented Jun 2, 2026

This PR was closed due to a force push that updated the branch. The changes are now available in #3567 with the same branch and Codex review fixes applied.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A2A (Agent2Agent) support

1 participant