What it is
The a2a package implements the Agent-to-Agent (A2A) protocol,
which defines a standard HTTP interface for agents to call each other across process and network boundaries.
A Phero agent can be exposed as an A2A server so other agents (or systems) can call it over HTTP.
Conversely, any remote A2A server can be consumed from a Phero agent as an ordinary llm.Tool.
The two sides
a2a.Server: wraps a Pheroagent.Agentand exposes it as an A2A-compliant HTTP handler set (AgentCard + JSON-RPC).a2a.Client: resolves a remote AgentCard by URL and exposes the remote agent as anllm.Toolyou can attach to a local agent.
Example: exposing an agent as an A2A server
See the full code in examples/a2a/server.
package main
import (
"net/http"
"os"
"github.com/henomis/phero/a2a"
"github.com/henomis/phero/agent"
"github.com/henomis/phero/llm/openai"
)
func main() {
client := openai.New(os.Getenv("OPENAI_API_KEY"))
a, err := agent.New(
client,
"math-assistant",
"You are a helpful math assistant. Answer concisely.",
)
if err != nil {
panic(err)
}
srv, err := a2a.New(a, "http://localhost:8080")
if err != nil {
panic(err)
}
mux := http.NewServeMux()
mux.Handle("/.well-known/agent.json", srv.AgentCardHandler())
mux.Handle("/", srv.JSONRPCHandler())
_ = http.ListenAndServe(":8080", mux)
}
AgentCardHandler() serves the well-known AgentCard endpoint used for discovery.
JSONRPCHandler() handles the A2A JSON-RPC messages that actually drive the agent.
Example: calling a remote A2A agent as a tool
See the full code in examples/a2a/client.
package main
import (
"context"
"fmt"
"os"
"github.com/henomis/phero/a2a"
"github.com/henomis/phero/agent"
"github.com/henomis/phero/llm/openai"
)
func main() {
ctx := context.Background()
// Resolve the remote agent card and create a client
remoteClient, err := a2a.NewClient(ctx, "http://localhost:8080")
if err != nil {
panic(err)
}
// Expose the remote agent as a local llm.Tool
mathTool, err := remoteClient.AsTool()
if err != nil {
panic(err)
}
// Use the tool in a local orchestrator agent
llmClient := openai.New(os.Getenv("OPENAI_API_KEY"))
orchestrator, err := agent.New(
llmClient,
"orchestrator",
"You are an orchestrator. Use the math-assistant tool for any math questions.",
)
if err != nil {
panic(err)
}
if err := orchestrator.AddTool(mathTool); err != nil {
panic(err)
}
result, err := orchestrator.Run(ctx, "What is the square root of 144?")
if err != nil {
panic(err)
}
fmt.Println(result.Content)
}
NewClient fetches the remote AgentCard from the well-known path and builds a transport client.
AsTool() wraps the remote agent as an llm.Tool whose name and description come from
the AgentCard itself — no manual wiring needed.
API reference
Server
a2a.New(agent, baseURL) (*Server, error)— create a server for the given agent and public base URLsrv.AgentCard() *AgentCard— derive the AgentCard from the wrapped agentsrv.AgentCardHandler() http.Handler— handler for the/.well-known/agent.jsonendpointsrv.JSONRPCHandler() http.Handler— handler for the A2A JSON-RPC endpoint
Client
a2a.NewClient(ctx, baseURL, opts...) (*Client, error)— resolve the remote AgentCard and open a clientclient.AsTool() (*llm.Tool, error)— expose the remote agent as an LLM-callable toola2a.WithResolver(r)— option to override the default AgentCard resolver
Errors
ErrAgentRequired— nil agent passed toNewErrBaseURLRequired— empty base URL passed toNewErrURLRequired— empty base URL passed toNewClientErrInvalidBaseURL— base URL is not a well-formed absolute URLErrNoTextContent— remote response contains no text partErrEmptyResponse— remote response is nil
Run the examples
# Start the A2A server in one terminal
go run ./examples/a2a/server
# Call it from another terminal
go run ./examples/a2a/client