On this page

On this page

Developer and self-hosted

A2A

The A2A channel plugin connects OpenClaw to other agents through the Linux Foundation Agent2Agent protocol. External agents discover the gateway through a public Agent Card and submit authenticated text tasks using the A2A 1.0 JSON-RPC binding. OpenClaw can also send messages to configured peer agents.

Quick setup

Add the bundled plugin to your OpenClaw configuration and define a separate bearer token for each trusted peer:

json5
{  channels: {    a2a: {      enabled: true,      advertisedUrl: "https://openclaw.example.com",      peers: {        hermes: {          token: "${A2A_HERMES_TOKEN}",        },      },    },  },}

Set A2A_HERMES_TOKEN to a strong, unique secret in the gateway environment, then restart the gateway. Use your externally reachable HTTPS origin as advertisedUrl when the gateway runs behind a reverse proxy. If omitted, the plugin derives the advertised origin from the incoming discovery request.

Discover the Agent Card

Fetch the public A2A Agent Card without authentication:

bash
curl http://127.0.0.1:18789/.well-known/agent-card.json

The card advertises the gateway JSON-RPC endpoint, supported text input and output, and one skill for each exposed OpenClaw agent. Set channels.a2a.exposeAgents to an array of agent IDs to limit which agents appear. If unset or empty, all configured agents are advertised.

/.well-known/agent.json returns the same card for older A2A clients.

Send a task

Send an authenticated SendMessage JSON-RPC request to /a2a/v1:

bash
curl http://127.0.0.1:18789/a2a/v1 \  -H "Authorization: Bearer $A2A_HERMES_TOKEN" \  -H "Content-Type: application/json" \  -d '{    "jsonrpc": "2.0",    "id": "request-1",    "method": "SendMessage",    "params": {      "message": {        "messageId": "message-1",        "role": "ROLE_USER",        "parts": [{ "text": "Summarize my latest project updates." }]      }    }  }'

By default, the request waits for the agent response. A completed response contains a task with the reply in its artifact:

json
{  "jsonrpc": "2.0",  "id": "request-1",  "result": {    "task": {      "id": "<task-id>",      "contextId": "<context-id>",      "status": {        "state": "TASK_STATE_COMPLETED",        "timestamp": "2026-01-01T12:00:00.000Z"      },      "artifacts": [        {          "artifactId": "<artifact-id>",          "parts": [{ "text": "Here are your latest project updates..." }]        }      ],      "history": []    }  }}

Include message.contextId on subsequent requests to continue the same conversation with a new task. Omit message.taskId: continuing an existing task is not supported, including while that task is working. Context IDs can contain letters, numbers, periods, underscores, colons, and hyphens, and must not exceed 128 characters.

If message.taskId is supplied, unknown, expired, or other peers' task IDs return JSON-RPC error -32001. An owned task with a conflicting message.contextId returns -32602; otherwise, the request returns -32004 for unsupported task continuation. None of these requests starts new work or changes the referenced task.

To return immediately while the agent continues working, add "configuration": { "returnImmediately": true } alongside "message" in params. The task initially reports TASK_STATE_WORKING. Requests that exceed replyTimeoutMs also return the current working task instead of canceling it.

When another turn is active in the same context, the new task queues behind it. Its reply and terminal state remain attached to the original task ID, so the peer can keep polling GetTask. Queued replies do not require an outbound peer URL or start a reverse-direction message.

Older clients can use message/send as an alias for SendMessage.

Poll a task

Poll a task by sending its ID to GetTask:

bash
curl http://127.0.0.1:18789/a2a/v1 \  -H "Authorization: Bearer $A2A_HERMES_TOKEN" \  -H "Content-Type: application/json" \  -d '{    "jsonrpc": "2.0",    "id": "poll-1",    "method": "GetTask",    "params": { "id": "<task-id>" }  }'

The task transitions from TASK_STATE_WORKING to TASK_STATE_COMPLETED, TASK_STATE_FAILED, or TASK_STATE_REJECTED. Older clients can use tasks/get as a compatibility alias.

CancelTask is refused with JSON-RPC error -32004 rather than acknowledged. A dispatched agent run has no plugin API for stopping it, so reporting TASK_STATE_CANCELED would tell the peer the work stopped while the run kept using tools. Refusing keeps the reported state honest.

Configure outbound peers

Add a peer URL when OpenClaw should send messages to another A2A agent. Set outboundToken when the remote agent requires its own bearer token:

json5
{  channels: {    a2a: {      enabled: true,      peers: {        hermes: {          token: "${A2A_HERMES_TOKEN}",          url: "https://hermes.example.com/a2a/v1",          outboundToken: "${A2A_HERMES_OUTBOUND_TOKEN}",        },      },    },  },}

Address outbound messages to a2a:hermes. The plugin sends SendMessage directly to the configured URL without performing Agent Card discovery. Outbound messages reuse a stable conversation context per peer. A peer without a configured url cannot receive outbound messages.

Agent replies, including queued followups, deliver only terminal reply content to A2A peers. Host status notices, including metadata-only diagnostics, and run-failure messages are not forwarded as new peer messages or task artifacts. A failed agent run instead marks its originating task TASK_STATE_FAILED with the reason Agent run failed, including when the run was queued. If collect mode combines requests, a failed run marks every originating task failed. This does not change error reporting on human-facing channels.

Configuration reference

Key Type Default Description
enabled boolean - Enables or disables the A2A channel.
advertisedUrl string request Public gateway origin used in the Agent Card.
replyTimeoutMs number 120000 Maximum blocking reply wait; allowed range is 5000 to 600000 milliseconds.
rateLimitPerMinute number 30 Sliding-window request limit per peer; 0 disables the limit.
exposeAgents string[] all Agent IDs advertised as Agent Card skills.
peers object {} Trusted peers keyed by lowercase names up to 64 characters.
peers.<name>.token string required Bearer token required when this peer sends requests to OpenClaw.
peers.<name>.url string - Peer JSON-RPC endpoint for outbound messages.
peers.<name>.outboundToken string - Bearer token OpenClaw sends to the configured peer URL.

Peer names must begin with a lowercase letter or number and can also contain periods, underscores, and hyphens.

Session isolation

Each authenticated peer and A2A contextId pair gets its own agent session. A2A pins the most isolated direct-message scope rather than inheriting session.dmScope, so remote peer content never joins the operator's main session and one peer cannot read another peer's conversation history.

For agent bindings, use the configured peer name as the direct-peer ID, such as hermes. A context-specific hermes:<contextId> binding takes precedence over that stable peer binding, and the stable peer binding takes precedence over broader A2A account or channel routes.

Security

Agent Card discovery is intentionally public: anyone who can reach the gateway can read the instance description and exposed agent IDs. Use exposeAgents to limit disclosure, and expose the gateway through HTTPS when it is reachable over an untrusted network.

Every JSON-RPC request requires a configured peer bearer token. There is no unauthenticated mode. Each authenticated peer is also the sender identity used for normal OpenClaw channel ingress policy. Use different high-entropy tokens for each peer, keep tokens out of source control, and rotate tokens by updating the gateway environment and restarting.

A2A peers send tasks, not user commands. Messages beginning with / are rejected with TASK_STATE_REJECTED and an explanation. Command-like text inside an ordinary task stays literal. It cannot change session settings or resolve approvals. This also applies when the peer sets the protocol message role to ROLE_USER: that field does not authenticate a human user.

Ordinary tasks retain the tools and permissions of their routed agent. Work requiring approval still needs an authorized operator's decision through a supported user channel or the Control UI. Existing integrations that sent slash commands over A2A must use plain-text tasks for agent work and an authorized user surface for commands. There is no peer command opt-in.

While an exec approval is pending, the original task stays working. After the operator decides, that same agent run receives the result and completes the task through its original reply path. An inbound peer does not need an outbound url to receive that completion through GetTask.

Requests are limited to 1 MiB, JSON-RPC batches to 30 entries, and serialized JSON-RPC responses to 1 MiB. Extracted message text is capped at 64 KiB and includes an explicit truncation marker when shortened. The default sliding-window limit is 30 requests per minute for each peer. Schema-invalid requests count toward that limit. Set rateLimitPerMinute to 0 only on a separately protected network. Rate-limited requests return a JSON-RPC error while keeping HTTP status 200.

Outbound destinations come only from operator-configured peer URLs. Inbound callers cannot supply a proxy target or redirect OpenClaw to another destination.

A2A 1.0 limitations

The current plugin supports text messages and structured JSON data parts, which are appended as compact JSON text. File URL and raw binary parts are ignored. Streaming, server-sent events, push notifications, task cancellation, task listing, extended Agent Cards, and multi-tenant routing are not supported.

The Agent Card advertises pushNotifications: false. Push notification configuration methods return JSON-RPC error -32003 (PushNotificationNotSupportedError), as required by the A2A 1.0 specification. Other recognized unsupported methods return -32004; unknown method names return -32601.

A repeated messageId from the same authenticated peer returns the existing task and its current state while that task is retained. It does not start another agent turn, even if the retry omits contextId. Use a new messageId for new work. Replies are associated with the task that started the turn, including when messages in one context finish out of order. A finished turn with no reply text completes without an artifact; stopping the channel fails unfinished tasks.

Completed and other terminal tasks are persisted per account before they are returned to peers. They survive Gateway restarts for up to 24 hours from their original completion time, with a maximum of 500 retained entries in finish order. Peer ownership and message deduplication survive with each retained task. Active inference is not resumed after a restart; stopping the channel records unfinished tasks as failed.

A definite storage validation refusal (for example, a result exceeding the plugin-state size limit) becomes a visible failed task whose status explains that it will not survive a restart; any completed artifact remains available until the process stops or retention expires. An uncertain write acknowledgement is not replaced with a synthetic failure, because the original result may already have committed.

Was this useful?