tRPC-Agent-Go A2A Integration Guide
Overview
tRPC-Agent-Go provides a complete A2A (Agent-to-Agent) solution with two core components:
- A2A Server: Exposes local Agents as A2A services for other Agents to call
- A2AAgent: A client proxy for calling remote A2A services, allowing you to use remote Agents as if they were local
Core Capabilities
- Zero Protocol Awareness: Developers only need to focus on Agent business logic without understanding A2A protocol details
- Automatic Adaptation: The framework automatically converts Agent information to A2A AgentCard
- Message Conversion: Automatically handles conversion between A2A protocol messages and Agent message formats
A2A Server: Exposing Agents as Services
Concept Introduction
A2A Server is a server-side component provided by tRPC-Agent-Go for quickly converting any local Agent into a network service that complies with the A2A protocol.
Core Features
- One-Click Conversion: Expose Agents as A2A services through simple configuration
- Automatic Protocol Adaptation: Automatically handles conversion between A2A protocol and Agent interfaces
- AgentCard Generation: Automatically generates AgentCards required for service discovery
- Streaming Support: Supports both streaming and non-streaming response modes
Automatic Conversion from Agent to A2A
tRPC-Agent-Go implements seamless conversion from Agent to A2A service through the server/a2a package:
Automatic AgentCard Generation
The framework automatically extracts Agent metadata (name, description, tools, etc.) to generate an AgentCard that complies with the A2A protocol, including: - Basic Agent information (name, description, URL) - Capability declarations (streaming support) - Skill lists (automatically generated based on Agent tools)
It is important to distinguish these two layers:
- The
streamingflag inWithAgent(agent, streaming)is primarily used to declareAgentCard.Capabilities.Streaming. - It is not the global execution switch for the
runner, and it does not directly control the internal message processing pipeline. - If you use
WithRunner(...) + WithAgentCard(...), the streaming capability should be declared directly on theAgentCard, for example viaNewAgentCard(..., streaming). - In
WithRunner(...) + WithAgentCard(...)mode,skillsare owned by the caller.NewAgentCard(...)only gives you a default structure and default skill; it does not infer the full tool list from a customrunner.
Message Protocol Conversion
The framework includes a built-in messageProcessor that implements bidirectional conversion between A2A protocol messages and Agent message formats, so users don't need to worry about message format conversion details.
A2A Server Quick Start
Exposing Agent Services with A2A Server
With just a few lines of code, you can convert any Agent into an A2A service:
Basic Example: Creating A2A Server
Streaming output event type (Message vs Artifact)
When streaming is enabled, A2A allows the server to emit incremental output in different ways:
- TaskArtifactUpdateEvent (default): ADK-style streaming. Chunks are sent
as task artifact updates (
artifact-update). - Message: Lightweight streaming. Chunks are sent as
message, so clients can renderMessage.partsdirectly without treating output as a persisted artifact.
To stream agent output as message instead of artifact-update, configure the
server with:
Task state updates (submitted, completed) are still emitted as
TaskStatusUpdateEvent. If WithStructuredTaskErrors(true) is enabled,
terminal failures are also emitted as failed task status updates, with
machine-readable fields preferred on the outer metadata, mirrored into
status.message.metadata for 0.1 compatibility, and display text in
status.message.parts.
Direct A2A Protocol Client Call
Advanced Configuration
Custom Runner (WithRunner)
By default, A2A Server automatically creates a Runner for you. If you need finer control, such as injecting a MemoryService or customizing SessionService, use WithRunner.
Note: WithRunner is mutually exclusive with WithAgent. When you provide WithRunner, you must also provide the public agent identity explicitly via WithAgentCard:
In this runner-only mode, streaming capability is no longer passed through WithAgent(...); it is declared directly by the AgentCard.Capabilities.Streaming field provided via WithAgentCard(...).
examples/a2aagent also includes an explicit example. Use -server-mode runner-card to switch the server construction path to WithRunner(...) + WithAgentCard(...):
If you only need a default-compliant card quickly, prefer NewAgentCard(...) instead of manually filling in Name, Description, Capabilities, and the default skill. If your runner needs a more accurate skills list, populate and maintain it in your own code.
Dynamically Updating AgentCard
If you need to update the exposed AgentCard at runtime, wire the underlying a2aprotocolserver.WithAgentCardHandler(...) through WithExtraA2AOptions(...), and use NewAgentCardHandler(...) to serve the current snapshot:
This only updates the exposed metadata. It does not modify the underlying runner, taskManager, or message processing pipeline. The caller remains responsible for where currentCard is stored and how it is updated.
Treat fields such as Name and URL as startup-time invariants, because they also participate in identity, routing, or discovery semantics. If those fields must change, rebuilding the server is safer than only updating the card endpoint.
Server-Side Message Processing Hook (WithProcessMessageHook)
WithProcessMessageHook allows you to insert custom logic before/after the A2A Server processes messages. It uses a middleware pattern, wrapping the underlying MessageProcessor:
Typical use cases:
- Read custom metadata injected by the client via BuildMessageHook
- Add logging, monitoring, or auditing before/after message processing
- Modify or validate inbound messages
Client-Side Message Build Hook (WithBuildMessageHook)
WithBuildMessageHook is a Hook on the A2AAgent (client) side that allows injecting custom data before sending messages to a remote A2A Server. It also uses a middleware pattern:
BuildMessageHook + ProcessMessageHook interaction:
The client injects custom data (such as trace_id, business tags) into the A2A message's metadata field via BuildMessageHook, and the server reads and processes this data via ProcessMessageHook.
Append RunOptions (WithRunOptions)
WithRunOptions allows appending additional RunOption to every Agent invocation in the A2A Server:
Graph internal event forwarding
By default, A2A Server filters most internal graph.* runtime events (for
example graph.node.start, graph.node.complete, graph.pregel.*, and
graph.checkpoint.*) to avoid exposing low-level execution details to
downstream consumers.
The terminal graph.execution event is still preserved by default (together
with normal message/error events), so final state reconstruction and
state_delta handoff continue to work.
If you need node-level traces for debugging, extend the graph object allowlist:
Notes:
- If this option is not set, the default allowlist is
["graph.execution"]. - If you explicitly call
WithGraphEventObjectAllowlist()with no arguments, allgraph.*events will be filtered out (includinggraph.execution).
Use this in debug/diagnostic scenarios. Keeping it off by default reduces noise and transport overhead in production.
Rewrite outbound A2A responses
WithResponseRewriter lets the server rewrite outbound A2A results immediately
before they are returned to the caller or sent to the streaming subscriber. It is
useful when you want to hide internal metadata, redact debug fields, or drop
server-generated events that are not part of your public A2A contract.
The rewriter sees the final unary result after server-side aggregation. In streaming mode, it sees every outbound streaming result, including converted agent events, task status updates, final artifact updates, structured task errors, and messages returned by the error handler. The request context is passed to each rewrite call so you can use request-scoped values in logs.
Returning nil from the rewriter drops that outbound result.
Hosting multiple A2A agents on one HTTP port (base paths)
Sometimes you want one service (one port) to expose multiple A2A Agents.
The idiomatic A2A approach is to give each Agent its own base URL, and let
the client select the Agent by choosing the URL (not by passing an agent_name
parameter).
In tRPC-Agent-Go, a2a.WithHost(...) supports URLs with a path segment.
When the host URL contains a path (for example http://localhost:8888/agents/math),
the A2A server will automatically use that path as its base path for routing.
Key idea:
- Create one A2A server per Agent (each with a different base path)
- Mount all A2A servers onto one shared
http.Serverviaserver.Handler()
Example:
After the server starts, each Agent has its own AgentCard endpoint:
http://localhost:8888/agents/math/.well-known/agent-card.jsonhttp://localhost:8888/agents/weather/.well-known/agent-card.json
Full runnable example: examples/a2amultipath.
A2AAgent: Calling Remote A2A Services
Corresponding to A2A Server, tRPC-Agent-Go also provides A2AAgent for calling remote A2A services, enabling communication between Agents.
Concept Introduction
A2AAgent is a special Agent implementation that doesn't directly handle user requests but forwards them to remote A2A services. From the user's perspective, A2AAgent looks like a regular Agent, but it's actually a local proxy for a remote Agent.
Simple Understanding: - A2A Server: I have an Agent and want others to call it → Expose as A2A service - A2AAgent: I want to call someone else's Agent → Call through A2AAgent proxy
Core Features
- Transparent Proxy: Use remote Agents as if they were local Agents
- Automatic Discovery: Automatically discover remote Agent capabilities through AgentCard
- Protocol Conversion: Automatically handle conversion between local message formats and A2A protocol
- Streaming Support: Support both streaming and non-streaming communication modes
- State Transfer: Support transferring local state to remote Agents
- Error Handling: Comprehensive error handling and retry mechanisms
For the recommended structured task-error convention between A2A server and
A2AAgent, see Error Handling.
Use Cases
- Distributed Agent Systems: Call Agents from other services in microservice architectures
- Agent Orchestration: Combine multiple specialized Agents into complex workflows
- Cross-Team Collaboration: Call Agent services provided by other teams
A2AAgent Quick Start
Basic Usage
In multi-agent systems, A2AAgent is often used as a SubAgent of a
local coordinator Agent (for example an LLMAgent). You can combine
A2AAgent with LLMAgent.SetSubAgents to dynamically load and refresh
remote SubAgents from a registry without recreating the coordinator.
Advanced Configuration
Whether the client sends a streaming request follows this priority:
- Per-call override via
agent.WithStream(...) a2aagent.WithEnableStreaming(...)- Remote
AgentCard.Capabilities.Streaming - Default false
In other words, the server-side streaming declaration mainly tells the client whether the remote A2A service supports streaming requests. The client then decides whether to send streaming or non-streaming requests based on that capability.
If the client explicitly sets agent.WithStream(...) or a2aagent.WithEnableStreaming(...), that explicit choice overrides the AgentCard declaration.
Complete Example: A2A Server + A2AAgent Combined Usage
Here's a complete example showing how to run both A2A Server (exposing local Agent) and A2AAgent (calling remote service) in the same program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | |
AgentCard Automatic Discovery
A2AAgent supports automatically obtaining remote Agent information through the standard AgentCard discovery mechanism:
State Transfer
A2AAgent supports transferring local runtime state to remote Agents:
Custom HTTP Headers
You can pass custom HTTP headers to A2A agent for each request using WithA2ARequestOptions:
Common Use Cases:
-
Authentication: Pass authentication tokens
-
Distributed Tracing: Add request/trace IDs
Configuring UserID Header:
Both client and server support configuring which HTTP header to use for UserID, default is X-User-ID:
The UserID from invocation.Session.UserID will be automatically sent via the configured header to the A2A server.
ADK Compatibility Mode
If you need to interoperate with Google ADK (Agent Development Kit) Python clients, you can enable ADK compatibility mode. When enabled, the Server will write additional adk_-prefixed keys (such as adk_type, adk_thought) in metadata to be compatible with ADK's part converter parsing logic:
Custom Converters
For special requirements, you can customize message and event converters:
Graph Interrupt and Resume via A2A
When the remote A2A Server runs a Graph Agent that uses graph.Interrupt, extra configuration is needed:
- Server-side: Allow at least
graph.executionandgraph.pregel.step; the former preserves the graph's terminal completion event, while the latter lets interrupt events carry resume metadata such asstate_deltaandpregel_metadataback through A2A. If you do not want to maintain a narrow allowlist, useWithGraphEventObjectAllowlist("*")instead. - Client-side: Forward at least
graph.CfgKeyLineageID,graph.CfgKeyCheckpointID,graph.CfgKeyCheckpointNS, andgraph.StateKeyCommand, so resume requests can send lineage/checkpoint/command data back to the remote graph. If you want to forward the full RuntimeState, useWithTransferStateKey("*")instead.
Notes:
graph.executionpreserves the graph's terminal completion event.graph.pregel.stepis the key graph event object used to carry interrupt/resume metadata.graph.StateKeyCommandcarriesResume/ResumeMap, which the remote graph uses to continue from the interrupted node."*"remains a convenient catch-all option for debugging or when full state forwarding is acceptable.
Full example: examples/graph/a2a_interrupt
Protocol Interaction Specification
For detailed specifications on how tool calls, code execution, reasoning content, and other events are transmitted through the A2A protocol, as well as Metadata field definitions, ADK compatibility mode, and distributed tracing, please refer to the dedicated document:
A2A Protocol Interaction Specification
This document defines the extension specification of trpc-agent-go on top of the A2A protocol, serving as the standard reference for Client and Server implementations.
Summary: A2A Server vs A2AAgent
| Component | Role | Use Case | Core Functions |
|---|---|---|---|
| A2A Server | Service Provider | Expose local Agent for other systems to call | • Protocol conversion • AgentCard generation • Message routing • Streaming support |
| A2AAgent | Service Consumer | Call remote A2A services | • Transparent proxy • Automatic discovery • State transfer • Protocol adaptation |
Typical Architecture Pattern
Through the combined use of A2A Server and A2AAgent, you can easily build distributed Agent systems.
A2A Server Configuration Reference
| Option | Description |
|---|---|
WithAgent(agent, streaming) |
Set the Agent and declare whether the generated AgentCard supports streaming; mutually exclusive with WithRunner |
WithHost(host) |
Set the service address, supports URLs with path |
WithAgentCard(card) |
Custom AgentCard (overrides auto-generation) |
WithRunner(runner) |
Custom Runner (inject Memory, Session, etc.); requires WithAgentCard |
WithSessionService(service) |
Set the session service used by the default Runner |
WithProcessMessageHook(hook) |
Server-side message processing Hook (middleware pattern) |
WithProcessorBuilder(builder) |
Fully custom message processor |
WithTaskManagerBuilder(builder) |
Custom task manager |
WithGraphEventObjectAllowlist(types...) |
Limit graph object types emitted by Event converters |
WithResponseRewriter(rewriter) |
Rewrite or drop outbound A2A unary/streaming results |
WithRunOptions(opts...) |
Append RunOptions to every invocation |
WithStreamingEventType(type) |
Streaming output event type (Artifact/Message) |
WithUserIDHeader(header) |
Custom UserID HTTP Header |
WithADKCompatibility(enabled) |
ADK compatibility mode (default: enabled) |
WithErrorHandler(handler) |
Custom error handler |
WithA2AToAgentConverter(conv) |
Custom A2A→Agent message converter |
WithEventToA2AConverter(conv) |
Custom Event→A2A message converter |
WithExtraA2AOptions(opts...) |
Pass-through options for underlying A2A Server |
WithDebugLogging(enabled) |
Enable debug logging |
A2AAgent Configuration Reference
| Option | Description |
|---|---|
WithAgentCardURL(url) |
Remote A2A service address |
WithBuildMessageHook(hook) |
Client-side message build Hook (middleware pattern) |
WithTransferStateKey(keys...) |
Specify RuntimeState keys to transfer |
WithEnableStreaming(enabled) |
Explicitly control streaming mode |
WithStreamingChannelBufSize(size) |
Streaming buffer size |
WithUserIDHeader(header) |
Custom UserID HTTP Header |
WithCustomA2AConverter(conv) |
Custom Invocation→A2A message converter |
WithCustomEventConverter(conv) |
Custom A2A Response→Event converter |