This example demonstrates the Agent-to-Agent (A2A) communication functionality in trpc-agent-go, showing how to create and communicate with remote agents using the A2A protocol.
The A2A system enables distributed agent communication across network boundaries:
- A2A Server: Hosts a remote agent and exposes it via A2A protocol
- A2A Client: Connects to remote agents and communicates seamlessly
- Agent Discovery: Automatic agent card resolution from well-known endpoints
- Protocol Translation: Converts between local agent events and A2A protocol messages
┌─────────────────────┐ A2A Protocol ┌─────────────────────┐
│ Local Client │ ◄─────────────────► │ Remote Server │
│ ️ │ HTTP/JSON │ │
│ │ │ │
│ ┌─────────────────┐ │ │ ┌─────────────────┐ │
│ │ A2A Agent │ │ │ │ LLM Agent │ │
│ │ (Proxy) │ │ │ │ (Actual) │ │
│ └─────────────────┘ │ │ └─────────────────┘ │
└─────────────────────┘ └─────────────────────┘
- Purpose: Hosts and exposes local agents via A2A protocol
- Features:
- Agent card publishing at
/.well-known/agent.json - HTTP endpoint for A2A message handling
- Protocol message conversion
- Agent card publishing at
- Configuration: Host binding and agent registration
- Purpose: Proxy agent that communicates with remote A2A servers
- Features:
- Automatic agent discovery via well-known paths
- HTTP client configuration with timeout support
- Message translation between local events and A2A protocol
- Configuration: Agent card URL or direct agent card object
- Purpose: The actual agent running on the remote server
- Features:
- OpenAI-compatible model integration
- Configurable generation parameters
- Standard agent interface implementation
- Models: Supports various models (DeepSeek, GPT, etc.)
# Build the example
cd examples/a2aagent
go build -o a2a-demo .
# Run with default settings (deepseek-v4-flash model, port 8888)
./a2a-demo
# Run with custom model and port
./a2a-demo -model gpt-4o-mini -host 0.0.0.0:9999
# Run server using WithRunner + WithAgentCard
./a2a-demo -server-mode runner-cardSet the required API keys for your chosen model:
# For DeepSeek
export OPENAI_API_KEY="your-deepseek-api-key"
export OPENAI_BASE_URL="https://api.deepseek.com/v1"
# For OpenAI
export OPENAI_API_KEY="your-openai-api-key"
# OPENAI_BASE_URL not needed for OpenAI$ ./a2a-demo
------- Agent Card -------
Name: agent_joker
Description: i am a remote agent, i can tell a joke
URL: http://0.0.0.0:8888
------------------------
Chat with the agent. Type 'new' for a new session, or 'exit' to quit.
User: tell me a joke
======== remote agent ========
🤖 Assistant: Why don't scientists trust atoms?
Because they make up everything! 😄
======== local agent ========
🤖 Assistant: Here's a joke for you:
Why did the programmer quit his job?
Because he didn't get arrays! (a raise) 😄
- Runs both remote A2A agent and local agent for comparison
- Shows responses from both agents side by side
- Demonstrates the transparency of A2A protocol
- Fetches agent cards from
/.well-known/agent.jsonendpoints - Validates agent metadata and capabilities
- Configures client based on discovered information
- Real-time conversation with agents
- Session management with 'new' command
- Graceful exit with 'exit' command
- Visual separation between remote and local agent responses
- Converts local
model.Messageto A2Aprotocol.Message - Handles different message types (text, tasks, etc.)
- Maintains conversation context across protocol boundaries
- Network timeout configuration (60 seconds for agent requests)
- Connection failure recovery
- Protocol error reporting
- Custom HTTP client support
- Configurable timeouts
- Multiple agent card resolution methods
- Streaming and non-streaming mode support
-
Server Setup (
runRemoteAgent)// Create LLM agent remoteAgent := buildRemoteAgent(*modelName) // Start A2A server server, err := a2a.New( a2a.WithHost(*host), a2a.WithAgent(remoteAgent, *streaming), ) server.Start(*host)
Or use the
runner-cardmode to build the server withWithRunner(...)andWithAgentCard(...):card, _ := a2a.NewAgentCard( remoteAgent.Info().Name, remoteAgent.Info().Description, *host, *streaming, ) server, err := a2a.New( a2a.WithAgentCard(card), a2a.WithRunner(runner.NewRunner(remoteAgent.Info().Name, remoteAgent)), )
-
Client Setup (
startChat)// Create A2A agent client a2aAgent, err := a2aagent.New( a2aagent.WithAgentCardURL(httpURL) ) // Use with runner remoteRunner := runner.NewRunner("test", a2aAgent) localRunner := runner.NewRunner("test", localAgent)
-
Agent Configuration (
buildRemoteAgent)llmAgent := llmagent.New( agentName, llmagent.WithModel(modelInstance), llmagent.WithDescription(desc), llmagent.WithInstruction(desc), llmagent.WithGenerationConfig(genConfig), )
-model: Model name (default: "deepseek-v4-flash")-host: Server host and port (default: "0.0.0.0:8888")-streaming: Enable streaming mode (default: true)-server-mode: Server build mode,agentorrunner-card(default: "agent")-remote-only: Only output remote agent responses (default: false)
WithAgentCard(): Use pre-configured agent cardWithAgentCardURL(): Auto-discover from URLWithHTTPClient(): Custom HTTP clientWithTimeout(): Request timeoutWithUserIDHeader(): Custom HTTP header name for sending UserID to server (default: "X-User-ID")
WithHost(): Server host and port bindingWithAgent(): The agent to expose and streaming modeWithRunner() + WithAgentCard(): Build the server from a custom runner plus explicit public agent metadataWithUserIDHeader(): Custom HTTP header name for reading UserID from client (default: "X-User-ID")WithDebugLogging(): Enable debug loggingWithErrorHandler(): Custom error handler
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 request
agent.WithA2ARequestOptions(
client.WithRequestHeader("X-Custom-Header", "custom-value"),
client.WithRequestHeader("X-Request-ID", "req-12345"),
client.WithRequestHeader("Authorization", "Bearer token"),
),
)Use Cases:
- Authentication: Pass authentication tokens via
Authorizationheader - Tracing: Add request IDs for distributed tracing
Configuring UserID Header:
Both A2A Agent (client) and A2A Server support configuring which HTTP header to use for UserID. The default is X-User-ID.
// Client side: Configure which header to send UserID in
a2aAgent, err := a2aagent.New(
a2aagent.WithAgentCardURL("http://localhost:8888"),
// Default is "X-User-ID", can be customized
a2aagent.WithUserIDHeader("X-Custom-User-ID"),
)
// Server side: Configure which header to read UserID from
server, err := a2a.New(
a2a.WithHost("localhost:8888"),
a2a.WithAgent(agent, true),
// Default is "X-User-ID", can be customized
a2a.WithUserIDHeader("X-Custom-User-ID"),
)The UserID from invocation.Session.UserID will be automatically sent via the configured header to the A2A server.
The following features are planned for the next version:
- Streaming Protocol Support: Enhanced streaming capabilities for real-time agent communication
- tRPC Ecosystem Integration: Native integration with tRPC framework for improved performance and compatibility
- API Key Issues
- Verify environment variables are set
- Check API key validity and permissions
- Confirm base URL for non-OpenAI providers