Client
A2AConfig is a ModelConfig — pass it to a regular Agent and the remote A2A server becomes that agent's LLM provider. Conversation history, tool calls and streaming are negotiated through the protocol; calling code keeps the familiar agent.ask(...) / reply.ask(...) shape.
Minimal Client#
card_url is the HTTP(S) base where the server publishes /.well-known/agent-card.json. The client fetches the card on first use, picks a binding from supported_interfaces, and uses the URL declared in the card for every subsequent request — you don't pass transport-specific URLs.
Selecting a Transport#
When the card declares multiple bindings, prefer=... forces a choice:
prefer=None (default) auto-picks: if exactly one declared interface URL matches card_url it wins; otherwise the first server-listed interface is used.
Tip
card_url is always an HTTP URL — even when the resolved transport is gRPC. The card is served over HTTP per spec; only the actual message exchange uses the resolved binding.
gRPC TLS#
The default gRPC channel factory selects security from the interface URL published in the agent card. grpcs:// and grpc+tls:// create a TLS channel using the system CA roots; grpc://, grpc+insecure:// and a bare host:port keep the backwards-compatible insecure channel.
For a server certificate issued by a public CA, the client needs no TLS-specific configuration. It fetches the card over HTTPS and follows the secure gRPC interface declared there:
Private CAs, self-signed certificates and mTLS need custom grpc.ChannelCredentials. Wrap those credentials with secure_grpc_channel_factory(...) and pass the returned callable through the existing grpc_channel_factory escape hatch:
The secure factory always creates a TLS channel, regardless of whether the card uses a secure, insecure or bare gRPC URL spelling. It also accepts gRPC channel options via options=. For mTLS, construct the credentials with root_certificates=, private_key= and certificate_chain=; AG2 passes the resulting object to gRPC unchanged.
Warning
TLS protects the gRPC connection only. Card discovery is a separate HTTP request, so publish the card over HTTPS as well and keep card_url on that HTTPS origin.
Multi-turn — reply.ask#
A2A servers are stateless from AG2's perspective: every call ships the full conversation history as a application/vnd.ag2.history+json DataPart attached to the outgoing message. The continuation API is the regular reply.ask(...):
The remote agent recovers the entire prior context on every turn — there is no server-side session id to manage.
Local Tools (forwarded from the server)#
Tools declared on the client are advertised to the server in the AG2 client-tools extension. When the remote LLM picks one, the call is routed back to the client and executed locally; the tool result is sent back into the server-side LLM loop. The server LLM never sees your local environment.
The same agent can mix client-side and server-side tools — server tools execute remotely, client tools execute locally, and the LLM picks freely between them within one turn.
Warning
If the remote AgentCard does not advertise the urn:ag2:client-tools:v1 extension, passing tools=... raises A2AClientToolsNotSupportedError. Only AG2-backed servers support client-side tool forwarding today.
Remote Agent as a Sub-tool — as_tool()#
A remote A2A agent plugs into a local Agent like any other delegate via Agent.as_tool(). The local LLM decides when to delegate; the wrapper exposes a task_<name> tool that takes an objective (and optional context).
This composes naturally with several remotes — give each as_tool() a distinct name= and let the local LLM route by capability. See Subagents for the general as_tool() semantics.
Note
Each task_<name> call spawns a fresh sub-agent stream; history between calls is not preserved on the sub-task side. For a remote that remembers prior turns, prefer the reply.ask(...) pattern above instead of as_tool().
Extensions#
A2A extensions are activated per connection by URI. List them on A2AConfig.extensions; every URI must be advertised in the server card's capabilities.extensions.
Activated URIs travel on both channels the spec allows — the Message.extensions field and the A2A-Extensions header (gRPC metadata on the gRPC binding) — and stay attached across continuation legs, so a tool-result or HITL round-trip does not silently deactivate them mid-task.
The card and the activation list are reconciled before the first request goes out, so a mismatch surfaces as A2AExtensionNotSupportedError at connect time instead of an opaque failure mid-task. It is raised in both directions:
- a URI the card does not advertise — a client-side mistake;
- an extension the card marks
required=Truethat the client did not activate — the server expects behaviour AG2 will not provide.
urn:ag2:client-tools:v1 is the exception to the second rule: AG2 implements it natively, so a card may require it without the client listing it explicitly.
Activation covers every request the config makes, not just ask. The one-shot task and push-notification helpers open their own connection from the same A2AConfig, so they activate the same URIs and run the same reconciliation — a server enforcing a required=True extension sees a consistent client on the conversational and the administrative path alike.
See Declaring Extensions for the server side.
A2AConfig Reference#
| Field | Type | Default | Purpose |
|---|---|---|---|
card_url | str | required | Base URL where /.well-known/agent-card.json is served |
prefer | Optional[Literal["jsonrpc", "rest", "grpc"]] | None | Force a specific binding when the card declares more than one |
streaming | bool | True | Use sendStreaming when the server's card opts in. Falls back to polling otherwise |
headers | Optional[Mapping[str, str]] | None | Extra HTTP headers (auth, tracing) |
timeout | Optional[float] | 60.0 | Per-request timeout in seconds |
max_reconnects | int | 3 | Streaming reconnect attempts (see Advanced) |
reconnect_backoff | float | 0.5 | Backoff between reconnect attempts (seconds) |
polling_interval | float | 0.5 | Poll interval when streaming is off |
input_required_timeout | Optional[float] | None | Cap how long the client waits on a HITL hook |
httpx_client_factory | Optional[Callable[[], AsyncClient]] | None | Custom httpx.AsyncClient (proxies, custom TLS, etc.) |
interceptors | Sequence[ClientCallInterceptor] | () | A2A SDK call interceptors |
grpc_channel_factory | Optional[Callable[[str], Channel]] | None | Custom gRPC channel builder. The default follows the card URL scheme: grpcs:// / grpc+tls:// use system CA roots; other gRPC URL forms stay insecure. See gRPC TLS |
preset_card | Optional[AgentCard] | None | Skip the discovery round-trip when the card is already known |
card_signature_verifier | Optional[Callable[[AgentCard], None]] | None | Verify the JWS signature on every card consumed — fetched, preset and extended (see Card Signing) |
tenant | Optional[str] | None | Multi-tenancy scope on a shared backend |
history_length | Optional[int] | None | Server-side hint to truncate echoed Task.history |
extensions | Sequence[str] | () | A2A extension URIs to activate on the connection (see Extensions) |
Constructing From a Pre-fetched Card#
When the card has already been resolved (discovery service, on-disk cache), A2AConfig.from_card(...) skips the network round-trip on connect:
card_url defaults to the first interface URL on the card; pass card_url=... to override.