internal/website/docs/guides/a2a-protocol.md
Go Micro speaks the Agent2Agent (A2A) protocol — the open standard for agents on different frameworks to discover and call each other over HTTP. The A2A gateway is the agent-side analogue of the MCP gateway: MCP exposes your services as tools, A2A exposes your agents as agents.
There is nothing to add to an agent. An agent already registers in the registry with type=agent metadata; the gateway discovers it, generates an Agent Card from that metadata, and translates incoming A2A tasks to the agent's existing Agent.Chat RPC — the same call delegate and flows use.
micro a2a serve --address :4000 --base_url https://agents.example.com
micro a2a list # agents and their Agent Card URLs
Or embed the gateway next to a service:
go a2a.Serve(a2a.Options{
Registry: service.Options().Registry,
Address: ":4000",
BaseURL: "https://agents.example.com",
})
A2A is JSON-RPC over HTTP — a different wire protocol from go-micro's RPC — so something always translates between the two. That something doesn't have to be a separate process. There are two ways to run it:
A gateway (above) fronts every agent in the registry behind one endpoint. Use it for a single front door, centralized discovery, and shared policy.
Directly on the agent. AgentA2A(addr) makes the agent serve its own A2A endpoint when it runs — no separate gateway, and the task is handled in-process (no extra RPC hop):
agent := micro.NewAgent("task-mgr",
micro.AgentServices("task"),
micro.AgentProvider("anthropic"),
micro.AgentA2A(":4000"), // also reachable at http://host:4000 over A2A
)
agent.Run()
The agent stays a normal go-micro service; this adds a second, A2A-native HTTP endpoint. Now any A2A client can curl it directly. Use it when each agent should be independently addressable without a gateway.
Both reuse the same handler; the only difference is whether the agent is reached over RPC (gateway) or in-process (embedded).
Every registered agent gets an Agent Card, generated from its registry metadata (name, the services it manages). Cards are not published by the agent — they are derived, the same way MCP tools are derived from service endpoints.
| Endpoint | Returns |
|---|---|
GET /agents | a directory of all Agent Cards |
GET /agents/{name} | one agent's card |
GET /agents/{name}/.well-known/agent.json | one agent's card (well-known path) |
POST /agents/{name} | the agent's JSON-RPC endpoint |
GET /.well-known/agent.json | the single agent's card, when exactly one is registered |
A card looks like:
{
"name": "task-mgr",
"description": "Go Micro agent managing: task,project",
"url": "https://agents.example.com/agents/task-mgr",
"version": "1.0.0",
"protocolVersion": "0.3.0",
"capabilities": { "streaming": true, "pushNotifications": true },
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/plain"],
"skills": [
{ "id": "task", "name": "Task", "tags": ["task"] },
{ "id": "project", "name": "Project", "tags": ["project"] }
]
}
Each managed service is advertised as its own typed skill. Clients can call the
whole agent at /agents/task-mgr, or address one skill directly at
/agents/task-mgr/skills/task; the skill endpoint serves a focused card and
routes the request to the same agent with that skill selected.
A2A uses JSON-RPC 2.0 over HTTP. Send a message with message/send; the gateway runs the agent and returns a completed Task:
curl -s https://agents.example.com/agents/task-mgr \
-H 'content-type: application/json' \
-d '{
"jsonrpc": "2.0", "id": 1, "method": "message/send",
"params": { "message": {
"role": "user", "kind": "message", "messageId": "m1",
"parts": [{ "kind": "text", "text": "What tasks are overdue?" }]
}}
}'
{
"jsonrpc": "2.0", "id": 1,
"result": {
"id": "…", "contextId": "…", "kind": "task",
"status": { "state": "completed", "timestamp": "…" },
"artifacts": [{ "artifactId": "…", "parts": [{ "kind": "text", "text": "Two: …" }] }]
}
}
Retrieve a task later with tasks/get (params: { "id": "…" }). To continue
the same piece of work, send another message/send with the previous taskId
and contextId. The gateway preserves the task id, context id, and prior
history, then appends the new user turn and agent reply. That makes a remote
A2A task fit the Go Micro lifecycle: services are still invoked through the
agent's normal tools, the agent keeps task context across turns, and a workflow
can poll one task id as the conversation progresses.
Operators can register a task callback with
tasks/pushNotificationConfig/set:
curl -s https://agents.example.com/agents/task-mgr \
-H 'content-type: application/json' \
-d '{
"jsonrpc": "2.0", "id": 2,
"method": "tasks/pushNotificationConfig/set",
"params": {
"id": "task-id",
"pushNotificationConfig": {
"url": "https://workflow.example.com/a2a/tasks",
"token": "optional-bearer-token"
}
}
}'
The gateway stores one callback per retained task and POSTs the latest task
snapshot to that URL whenever the task changes. Delivery is best effort: failures
do not fail the agent turn, and there is no retry queue in the in-memory gateway.
Use tasks/get as the source of truth after a missed callback or receiver
outage. If a token is configured, it is sent as Authorization: Bearer <token>.
The gateway makes your agents reachable from the A2A ecosystem. The
client (a2a.Client) is the other direction: it lets a Go Micro agent or
flow call an agent on any framework, by URL.
reply, err := a2a.NewClient("https://other.example.com/agents/research").
Send(ctx, "Summarize the latest on X")
It's wired into the two places that hand off work:
A flow step — flow.A2A(url) is the cross-framework counterpart to
flow.Dispatch(name) (which dispatches to a local agent):
flow.Step{Name: "research", Run: flow.A2A("https://other.example.com/agents/research")}
Agent delegate — when an agent's delegate target is an http(s)
URL, the subtask is sent to that external agent over A2A instead of to a
locally registered one. Nothing else changes; the model just delegates
to a URL.
Send handles the task lifecycle: if the remote returns a task that isn't
yet terminal, it polls tasks/get until it completes.
This is the JSON-RPC binding for task execution:
message/send runs the agent and returns a completed Task.message/stream streams the completed Task as an SSE data: event, giving A2A clients a streaming-compatible path while the underlying agent call remains synchronous.tasks/get returns a recent task by id.taskId.tasks/pushNotificationConfig/set / get stores and reads a task callback for best-effort update delivery.tasks/resubscribe reconnects to an existing task stream, immediately emits the current task snapshot, then streams subsequent updates until the task reaches a terminal state.input-required task state carries human-input handoffs (for example checkpointed approval pauses) in task status, artifacts, and history; continue the task by sending a follow-up message with the same taskId and contextId.Both directions work: the gateway exposes your agents, and a2a.Client (via flow.A2A or delegate to a URL) calls external ones. The task binding is what makes a Go Micro agent both reachable from, and able to reach, the A2A ecosystem today.
AP2 sits above A2A as a verifiable-intent and audit layer. Go Micro keeps the
A2A envelope separate from payment settlement: an A2A message can carry signed
AP2 checkout or payment mandates, and the resulting task can retain the stable
mandate reference plus verification result. Payment settlement state remains in
the payment rail. For x402, use an AP2 payment mandate with an x402 rail
reference to name the payment requirement; the existing x402 facilitator still
performs verification and settlement.