Skip to content

A2A Transport

A2UI's wire is transport-agnostic, so the same validated A2UI messages can ride an A2A exchange instead of the built-in REST adapter. The autogen.beta.a2ui.a2a submodule wraps a plain autogen.beta.Agent in an A2UIAgentExecutor, plugs it into a standard A2AServer, and carries each turn's A2UI messages as a canonical A2A DataPart.

Note

This page assumes familiarity with the A2A server. The A2UI integration is a drop-in executor for the same A2AServer — everything you know about cards, transports, and tasks still applies.

How It Maps#

A2UI concept A2A representation
Validated server→client messages One DataPart (MIME application/a2ui+json) carrying the full message list, alongside a text Part for the prose
Button click / functionResponse / client error An inbound A2UI DataPart on the request message — a registered action runs its handler on the server; anything else is rewritten into the turn's prompt
Catalog negotiation a2uiClientCapabilities on the A2A message metadata
Protocol support advertised An AgentExtension on the agent card's capabilities.extensions

The executor splits each completed turn into a text Part (the conversational prose) plus a single A2UI DataPart whose data is the JSON array of validated messages — the canonical A2A v1.0 encoding for A2UI.

Public API#

1
2
3
4
5
6
7
from autogen.beta.a2ui.a2a import (
    A2UIAgentExecutor,        # the A2A executor wrapping a plain Agent
    get_a2ui_agent_extension, # build the AgentExtension for the card
    create_a2ui_parts,        # wrap A2UI messages into A2A Parts
    get_a2ui_data,            # decode an A2UI DataPart's payload
    is_a2ui_part,             # check whether a Part carries A2UI data
)

Minimal Server#

Wrap a plain Agent in A2UIAgentExecutor (with the same flat A2UI kwargs as A2UIServer, including actions= for clickable buttons), pass it to A2AServer via executor=, and advertise A2UI support on the card with a card_modifier that appends the extension:

import uvicorn
from a2a.types import AgentCard

from autogen.beta import Agent
from autogen.beta.a2a import A2AServer
from autogen.beta.a2ui import a2ui_action
from autogen.beta.a2ui.a2a import A2UIAgentExecutor, get_a2ui_agent_extension
from autogen.beta.config import AnthropicConfig

@a2ui_action(description="Book the selected dinner reservation")
def book_table(time: str) -> str:
    return f"booked a table for {time}"

def build_app():
    agent = Agent(name="ui_agent", config=AnthropicConfig(model="claude-sonnet-4-6"))
    # Buttons are declared on the executor via actions=, just like A2UIServer —
    # the agent stays plain.
    executor = A2UIAgentExecutor(agent, actions=[book_table], protocol_version="v0.9")

    async def advertise_a2ui(card: AgentCard) -> AgentCard:
        card.capabilities.extensions.append(get_a2ui_agent_extension(version=executor.protocol_version))
        return card

    server = A2AServer(agent, executor=executor, card_modifier=advertise_a2ui)
    return server.build_jsonrpc(url="http://127.0.0.1:8000")

app = build_app()

if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8000)

A client connects by pointing A2AConfig(card_url=...) at the card URL, exactly as for any A2A agent — see the A2A client page.

Tip

get_a2ui_agent_extension() takes supported_catalog_ids= and accepts_inline_catalogs= to describe which catalogs the agent renders. With no arguments it advertises the version's default "basic" catalog.

Reading A2UI From the Client#

The A2UI messages ride a DataPart on the finalization message. Use is_a2ui_part to find it and get_a2ui_data to decode the payload (a list of canonical A2UI messages):

1
2
3
4
5
6
from autogen.beta.a2ui.a2a import get_a2ui_data, is_a2ui_part

# `message` is the A2A agent message from the completed task.
for part in message.parts:
    if is_a2ui_part(part):
        messages = get_a2ui_data(part)  # list[dict] — createSurface, updateComponents, ...

get_a2ui_data decodes both the canonical v1.0 form (a JSON array in one DataPart) and the legacy form (one object per DataPart), returning None for any part that is not A2UI-typed.

The Click Round-Trip#

A button click is sent back as an inbound A2UI DataPart on the next request message — an action envelope, or a functionResponse (v1.0) for a server-initiated callFunction. A click on a registered @a2ui_action runs that handler on the server (its messages lead the DataPart, the agent is not invoked); a click on any other button — plus error and functionResponse envelopes — is rewritten into the turn's prompt so the agent continues. A turn carrying only registered server-action clicks finalizes without invoking the agent. See Actions for the envelope shapes.

v1.0 pause/resume

With protocol_version="v1.0", a callFunction(wantResponse=true) transitions the A2A task to input-required, carrying the callFunction DataPart on the finalization message so the client knows which function to run. The client's functionResponse arrives on the next request and resumes the task — standard A2A task lifecycle, no extra bookkeeping.

Manually Building Parts#

If you serialize A2UI messages onto an A2A message yourself (outside the executor), create_a2ui_parts wraps one or many messages into DataParts:

1
2
3
4
from autogen.beta.a2ui.a2a import create_a2ui_parts

parts = create_a2ui_parts(messages)                    # one DataPart, data = [msg, ...]
parts = create_a2ui_parts(messages, legacy_split=True) # one DataPart per message (pre-v1.0 renderers)