e071084ebe
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
204 lines
8.6 KiB
Markdown
204 lines
8.6 KiB
Markdown
---
|
|
layout: default
|
|
---
|
|
|
|
# Agent2Agent (A2A)
|
|
|
|
Go Micro speaks the [Agent2Agent (A2A) protocol](https://a2a-protocol.org) — 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.html): 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.
|
|
|
|
## Run it
|
|
|
|
```bash
|
|
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
|
|
go a2a.Serve(a2a.Options{
|
|
Registry: service.Options().Registry,
|
|
Address: ":4000",
|
|
BaseURL: "https://agents.example.com",
|
|
})
|
|
```
|
|
|
|
## Gateway, or directly on the agent
|
|
|
|
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):
|
|
|
|
```go
|
|
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).
|
|
|
|
## Discovery: cards from the registry
|
|
|
|
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:
|
|
|
|
```json
|
|
{
|
|
"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.
|
|
|
|
## Calling an agent
|
|
|
|
A2A uses JSON-RPC 2.0 over HTTP. Send a message with `message/send`; the gateway runs the agent and returns a completed `Task`:
|
|
|
|
```bash
|
|
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?" }]
|
|
}}
|
|
}'
|
|
```
|
|
|
|
```json
|
|
{
|
|
"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.
|
|
|
|
## Push notifications
|
|
|
|
Operators can register a task callback with
|
|
`tasks/pushNotificationConfig/set`:
|
|
|
|
```bash
|
|
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>`.
|
|
|
|
## Calling out to other agents
|
|
|
|
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.
|
|
|
|
```go
|
|
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):
|
|
|
|
```go
|
|
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.
|
|
|
|
## Scope
|
|
|
|
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.
|
|
- **Multi-turn continuation** keeps task state when a new message includes the previous `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`.
|
|
- **Agent Card** discovery, generated from the registry.
|
|
|
|
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 mandate layer (opt-in)
|
|
|
|
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.
|
|
|
|
## See also
|
|
|
|
- [MCP & AI Agents](../mcp.html) — exposing services as tools
|
|
- [Agents and Workflows](agents-and-workflows.html) — the agent model
|
|
- [A2A protocol specification](https://a2a-protocol.org)
|