A2A

Connect agents across processes and services with the Agent-to-Agent protocol.

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

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",
        a2a.WithVersion("1.0"),
        a2a.WithStreaming(),
    )
    if err != nil {
        panic(err)
    }

    mux := http.NewServeMux()
    srv.Mount(mux)  // registers /.well-known/agent-card.json and /

    _ = http.ListenAndServe(":8080", mux)
}

Mount(mux) is the one-call convenience that registers all active handlers on the mux. It is equivalent to calling AgentCardHandler() and JSONRPCHandler() separately, and also registers the REST handler when WithRESTTransport() is used.

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",
        a2a.WithAcceptedOutputModes("text/plain"),
    )
    if err != nil {
        panic(err)
    }

    // Inspect the discovered AgentCard
    fmt.Println(remoteClient.Card().Name)

    // 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. Card() returns the resolved *AgentCard so callers can inspect metadata before use.

The client handles both synchronous (inline Message) and asynchronous (Task-based) responses transparently. For async tasks it subscribes to the event stream, or falls back to polling GetTask at a configurable interval (default 500 ms) until the task reaches a terminal state.

Multi-agent A2A pipeline

For a production-style example, see examples/a2a/multi-agent. It shows three specialised agents (researcher, writer, editor) each running as a separate A2A server, coordinated by a local orchestrator that calls each one as an llm.Tool.

# Terminal 1
OPENAI_API_KEY=<key> go run ./examples/a2a/multi-agent/researcher

# Terminal 2
OPENAI_API_KEY=<key> go run ./examples/a2a/multi-agent/writer

# Terminal 3
OPENAI_API_KEY=<key> go run ./examples/a2a/multi-agent/editor

# Terminal 4 — orchestrator
OPENAI_API_KEY=<key> go run ./examples/a2a/multi-agent/orchestrator -topic "quantum computing"

API reference

Server

Server options

Client

Client options

Errors

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

Related packages