A2A Channel: Connect External Agents via Agent2Agent Protocol

Learn how to connect OpenClaw to external agents using the A2A 1.0 JSON-RPC protocol. This page covers setup, authentication, and agent discovery for developers integrating peer agents.

Read this when

  • You want an A2A-compliant agent to discover and message OpenClaw
  • You need to configure authenticated A2A peers or outbound agent messages

The A2A channel plugin lets OpenClaw talk to other agents through the Linux Foundation Agent2Agent protocol. Outside agents find the gateway via a public Agent Card and send authenticated text tasks using the A2A 1.0 JSON-RPC binding. OpenClaw can also transmit messages to peer agents that have been set up.

Quick setup

Add the included plugin to your OpenClaw setup and give each trusted peer its own bearer token:

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

Put A2A_HERMES_TOKEN in the gateway environment as a strong, unique secret, then restart the gateway. When the gateway sits behind a reverse proxy, use your externally reachable HTTPS origin for advertisedUrl. If you leave it out, the plugin figures out the advertised origin from the incoming discovery request.

Discover the Agent Card

Grab the public A2A Agent Card with no authentication needed:

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

The card shows the gateway JSON-RPC endpoint, the supported text input and output, and one skill for each OpenClaw agent that is exposed. To restrict which agents show up, set channels.a2a.exposeAgents to an array of agent IDs. When it is unset or empty, every configured agent gets advertised.

For older A2A clients, /.well-known/agent.json provides the same card.

Send a task

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

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." }]
      }
    }
  }'

The request waits for the agent response by default. When it completes, the response holds a task with the reply inside its artifact:

{
  "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": []
    }
  }
}

To keep the same conversation going, include message.contextId on later requests. Context IDs may contain letters, numbers, periods, underscores, colons, and hyphens, and they cannot exceed 128 characters.

If you want an immediate return while the agent keeps working, add "configuration": { "returnImmediately": true } together with "message" in params. The task initially reports TASK_STATE_WORKING. Requests that go over replyTimeoutMs also hand back the current working task instead of canceling it.

Clients on older versions can use message/send in place of SendMessage.

Poll a task

Poll a task by sending its ID to GetTask:

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 moves from TASK_STATE_WORKING to TASK_STATE_COMPLETED, TASK_STATE_FAILED, or TASK_STATE_REJECTED. Older clients can rely on tasks/get as a compatibility alias.

CancelTask gets refused with JSON-RPC error -32004 rather than acknowledged. Since a dispatched agent run has no plugin-facing abort seam, reporting TASK_STATE_CANCELED would make the peer think the work stopped while the run kept using tools. Refusing keeps the reported state honest.

Configure outbound peers

When OpenClaw needs to send messages to another A2A agent, add a peer URL. Set outboundToken if the remote agent requires its own bearer token:

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

Direct outbound messages to a2a:hermes. The plugin sends SendMessage straight to the configured URL without doing Agent Card discovery. Outbound messages reuse a stable conversation context per peer. A peer that lacks a configured url cannot receive outbound messages.

Configuration reference

KeyTypeDefaultDescription
enabledboolean-Turns the A2A channel on or off.
advertisedUrlstringrequestPublic gateway origin shown in the Agent Card.
replyTimeoutMsnumber120000Maximum blocking reply wait; allowed range is 5000 to 600000 milliseconds.
rateLimitPerMinutenumber30Sliding-window request limit per peer; 0 disables the limit.
exposeAgentsstring[]allAgent IDs advertised as Agent Card skills.
peersobject{}Trusted peers keyed by lowercase names up to 64 characters.
peers.<name>.tokenstringrequiredBearer token required when this peer sends requests to OpenClaw.
peers.<name>.urlstring-Peer JSON-RPC endpoint for outbound messages.
peers.<name>.outboundTokenstring-Bearer token OpenClaw sends to the configured peer URL.

Peer names must start with a lowercase letter or number and may also include 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.

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 call must include a bearer token from a configured peer, since no unauthenticated path exists. The authenticated peer doubles as the sender identity for standard OpenClaw channel ingress policy. Assign each peer its own high-entropy token, avoid committing tokens to version control, and refresh tokens by changing the gateway environment and restarting it.

Each request is capped at 1 MiB. Extracted message text cannot exceed 64 KiB, and when truncation occurs an explicit marker is appended. By default, each peer gets a sliding window of 30 requests per minute; set rateLimitPerMinute to 0 only if the network is separately protected. When rate limits are hit, the response is a JSON-RPC error but the HTTP status stays 200.

Outbound destinations are restricted to peer URLs that the operator configures. Incoming callers have no way to specify a proxy target or reroute OpenClaw elsewhere.

A2A 1.0 limitations

This plugin handles text messages and structured JSON data parts, with the latter attached as compact JSON text. File URLs and raw binary parts get ignored. Unsupported features include streaming, server-sent events, push notifications, task cancellation, task listing, extended Agent Cards, and multi-tenant routing.

Tasks live only in memory. Terminal tasks, including completed ones, stay for up to 24 hours with a cap of 500 entries; any gateway restart wipes out all tasks and their history.

1,128 words · updated Aug 28, 2026