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:
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)
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:
packagemainimport("trpc.group/trpc-go/trpc-agent-go/agent/llmagent""trpc.group/trpc-go/trpc-agent-go/model/openai"a2aserver"trpc.group/trpc-go/trpc-agent-go/server/a2a")funcmain(){// 1. Create a regular Agentmodel:=openai.New("gpt-4o-mini")agent:=llmagent.New("MyAgent",llmagent.WithModel(model),llmagent.WithDescription("An intelligent assistant"),)// 2. Convert to A2A service with one clickserver,_:=a2aserver.New(a2aserver.WithHost("localhost:8080"),a2aserver.WithAgent(agent,true),// Enable streaming)// 3. Start the service to accept A2A requestsserver.Start(":8080")}
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 render Message.parts directly without treating output as a persisted
artifact.
To stream agent output as message instead of artifact-update, configure the
server with:
import("trpc.group/trpc-go/trpc-a2a-go/client""trpc.group/trpc-go/trpc-a2a-go/protocol")funcmain(){// Connect to A2A serviceclient,_:=client.NewA2AClient("http://localhost:8080/")// Send message to Agentmessage:=protocol.NewMessage(protocol.MessageRoleUser,[]protocol.Part{protocol.NewTextPart("Hello, please help me analyze this code")},)// Agent will automatically process and return resultsresponse,_:=client.SendMessage(context.Background(),protocol.SendMessageParams{Message:message})}
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.Server via server.Handler()
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
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
packagemainimport("context""fmt""trpc.group/trpc-go/trpc-agent-go/agent/a2aagent""trpc.group/trpc-go/trpc-agent-go/runner""trpc.group/trpc-go/trpc-agent-go/model""trpc.group/trpc-go/trpc-agent-go/session/inmemory")funcmain(){// 1. Create A2AAgent pointing to remote A2A servicea2aAgent,err:=a2aagent.New(a2aagent.WithAgentCardURL("http://localhost:8888"),)iferr!=nil{panic(err)}// 2. Use it like a regular AgentsessionService:=inmemory.NewSessionService()runner:=runner.NewRunner("test",a2aAgent,runner.WithSessionService(sessionService))// 3. Send messageevents,err:=runner.Run(context.Background(),"user1","session1",model.NewUserMessage("Please tell me a joke"),)iferr!=nil{panic(err)}// 4. Handle responseforevent:=rangeevents{ifevent.Response!=nil&&len(event.Response.Choices)>0{fmt.Print(event.Response.Choices[0].Message.Content)}}}
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.
packagemainimport("context""fmt""time""trpc.group/trpc-go/trpc-agent-go/agent/a2aagent""trpc.group/trpc-go/trpc-agent-go/agent/llmagent""trpc.group/trpc-go/trpc-agent-go/model/openai""trpc.group/trpc-go/trpc-agent-go/runner""trpc.group/trpc-go/trpc-agent-go/server/a2a""trpc.group/trpc-go/trpc-agent-go/session/inmemory")funcmain(){// 1. Create and start remote Agent serviceremoteAgent:=createRemoteAgent()startA2AServer(remoteAgent,"localhost:8888")time.Sleep(1*time.Second)// Wait for service to start// 2. Create A2AAgent connecting to remote servicea2aAgent,err:=a2aagent.New(a2aagent.WithAgentCardURL("http://localhost:8888"),a2aagent.WithTransferStateKey("user_context"),)iferr!=nil{panic(err)}// 3. Create local AgentlocalAgent:=createLocalAgent()// 4. Compare local and remote Agent responsescompareAgents(localAgent,a2aAgent)}funccreateRemoteAgent()agent.Agent{model:=openai.New("gpt-4o-mini")returnllmagent.New("JokeAgent",llmagent.WithModel(model),llmagent.WithDescription("I am a joke-telling agent"),llmagent.WithInstruction("Always respond with a funny joke"),)}funccreateLocalAgent()agent.Agent{model:=openai.New("gpt-4o-mini")returnllmagent.New("LocalAgent",llmagent.WithModel(model),llmagent.WithDescription("I am a local assistant"),)}funcstartA2AServer(agentagent.Agent,hoststring){server,err:=a2a.New(a2a.WithHost(host),a2a.WithAgent(agent,true),// Enable streaming)iferr!=nil{panic(err)}gofunc(){server.Start(host)}()}funccompareAgents(localAgent,remoteAgentagent.Agent){sessionService:=inmemory.NewSessionService()localRunner:=runner.NewRunner("local",localAgent,runner.WithSessionService(sessionService))remoteRunner:=runner.NewRunner("remote",remoteAgent,runner.WithSessionService(sessionService))userMessage:="Please tell me a joke"// Call local Agentfmt.Println("=== Local Agent Response ===")processAgent(localRunner,userMessage)// Call remote Agent (via A2AAgent)fmt.Println("\n=== Remote Agent Response (via A2AAgent) ===")processAgent(remoteRunner,userMessage)}funcprocessAgent(runnerrunner.Runner,messagestring){events,err:=runner.Run(context.Background(),"user1","session1",model.NewUserMessage(message),agent.WithRuntimeState(map[string]any{"user_context":"test_context",}),)iferr!=nil{fmt.Printf("Error: %v\n",err)return}forevent:=rangeevents{ifevent.Response!=nil&&len(event.Response.Choices)>0{content:=event.Response.Choices[0].Message.Contentifcontent==""{content=event.Response.Choices[0].Delta.Content}ifcontent!=""{fmt.Print(content)}}}fmt.Println()}
AgentCard Automatic Discovery
A2AAgent supports automatically obtaining remote Agent information through the standard AgentCard discovery mechanism:
// A2AAgent automatically retrieves AgentCard from the following path// http://remote-agent:8888/.well-known/agent.jsontypeAgentCardstruct{Namestring`json:"name"`Descriptionstring`json:"description"`URLstring`json:"url"`CapabilitiesAgentCardCapabilities`json:"capabilities"`}typeAgentCardCapabilitiesstruct{Streaming*bool`json:"streaming,omitempty"`}
State Transfer
A2AAgent supports transferring local runtime state to remote Agents:
a2aAgent,_:=a2aagent.New(a2aagent.WithAgentCardURL("http://remote-agent:8888"),// Specify state keys to transfera2aagent.WithTransferStateKey("user_id","session_context","preferences"),)// Runtime state is passed to remote Agent through A2A protocol metadata fieldevents,_:=runner.Run(ctx,userID,sessionID,message,agent.WithRuntimeState(map[string]any{"user_id":"12345","session_context":"shopping_cart","preferences":map[string]string{"language":"en"},}),)
Custom HTTP Headers
You can pass custom HTTP headers to A2A agent for each request using WithA2ARequestOptions:
import"trpc.group/trpc-go/trpc-a2a-go/client"events,err:=runner.Run(context.Background(),userID,sessionID,model.NewUserMessage("your question"),// Pass custom HTTP headers for this requestagent.WithA2ARequestOptions(client.WithRequestHeader("X-Custom-Header","custom-value"),client.WithRequestHeader("X-Request-ID",fmt.Sprintf("req-%d",time.Now().UnixNano())),client.WithRequestHeader("Authorization","Bearer your-token"),),)
// Client side: Configure which header to send UserID ina2aAgent,_:=a2aagent.New(a2aagent.WithAgentCardURL("http://remote-agent:8888"),// Default is "X-User-ID", can be customizeda2aagent.WithUserIDHeader("X-Custom-User-ID"),)// Server side: Configure which header to read UserID fromserver,_:=a2a.New(a2a.WithHost("localhost:8888"),a2a.WithAgent(agent,true),// Default is "X-User-ID", can be customizeda2a.WithUserIDHeader("X-Custom-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:
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:
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.