← Back to Plugins
Integration

A2a Peer

eugenedw By eugenedw 👁 48 views ▲ 0 votes

An A2A v0.3.0 plugin for OpenClaw that connects two named peers over HTTPS through Cloudflare Tunnels.

GitHub

Install

openclaw plugins install openclaw-a2a-peer

Configuration Example

{
  "auth": {
    // One entry per peer allowed to call inbound. Token is read from the named env var.
    "inbound_tokens": [
      { "peer": "Meridian", "token_env": "A2A_INBOUND_TOKEN_MERIDIAN" }
    ]
  },

  // This agent's published identity (served at /.well-known/agent-card.json).
  "card": {
    "name": "Cassian",
    "description": "Mac Mini agent — local dev, macOS tooling",
    "url": "https://cassian.example.com/a2a",
    "version": "1.0.0",
    "tags": ["macos", "home"]
  },

  // Skills this agent advertises to peers. Default is [] — peers see the agent
  // but cannot call any skills until explicitly listed here.
  "advertised_skills": [],

  // Named peers this agent is allowed to call outbound.
  "peers": [
    {
      "name": "Meridian",
      "url": "https://meridian.example.com/a2a",
      "card_url": "https://meridian.example.com/.well-known/agent-card.json",
      "token_env": "A2A_OUTBOUND_TOKEN_MERIDIAN",
      "card_refresh_seconds": 300   // how often to re-fetch the peer's Agent Card (default: 300)
    }
  ],

  // Optional tuning.
  "limits": {
    "max_hops": 5,                  // reject inbound calls that have hopped too many times
    "request_timeout_ms": 60000,    // timeout for both inbound agent runs and outbound calls
    "inbound_concurrency": 10       // max concurrent inbound calls (future; not yet enforced)
  },

  // Audit configuration.
  "audit": {
    "jsonl_path": "~/.openclaw/logs/a2a-audit.jsonl",  // ~ is expanded; created automatically
    "full_payloads": false,         // set true to log full message text (noisy; off by default)
    "mongo": {
      "enabled": true,
      "url_env": "A2A_MONGO_URL",   // env var holding the MongoDB connection string
      "collection": "a2a_audit",    // collection name (default: "a2a_audit")
      "fail_open": true,            // if true, Mongo failures never affect A2A calls
      "queue_max": 1000             // max in-memory queue depth before oldest records are dropped
    }
  },

  // Optional Discord webhook notifications.
  "discord_visibility": {
    "enabled": false,
    "webhook_url": null,            // Discord webhook URL (set to enable posting)
    "events": ["inbound", "outbound"],  // which directions to announce
    "format": "compact"             // "compact" or "verbose"
  }
}

README

# openclaw-a2a-peer

An A2A v0.3.0 plugin for OpenClaw that connects two named peers over HTTPS through Cloudflare Tunnels.

Built for a two-peer setup: **Cassian** (Mac Mini) and **Meridian** (AWS EC2). Both instances run the same plugin in symmetric configuration — no client/server asymmetry.

## What it does

- Publishes this agent's identity at `/.well-known/agent-card.json`
- Accepts peer messages at `/a2a` (JSON-RPC 2.0) and bridges them to the local OpenClaw agent
- Exposes `a2a_call_peer` as an agent tool for outbound calls to named peers
- Fetches and caches peer Agent Cards; injects discovered skills into the agent's system context
- Audits every inbound and outbound call to JSONL (always-on, synchronous) and MongoDB (fail-open, off critical path)
- Optionally posts call notifications to a Discord webhook

---

## Installation

```bash
openclaw plugins install openclaw-a2a-peer
```

---

## Configuration

Add to `openclaw.json` under `plugins.entries.a2a-peer.config`. All sensitive values are loaded from environment variables — never written to config files.

```jsonc
{
  "auth": {
    // One entry per peer allowed to call inbound. Token is read from the named env var.
    "inbound_tokens": [
      { "peer": "Meridian", "token_env": "A2A_INBOUND_TOKEN_MERIDIAN" }
    ]
  },

  // This agent's published identity (served at /.well-known/agent-card.json).
  "card": {
    "name": "Cassian",
    "description": "Mac Mini agent — local dev, macOS tooling",
    "url": "https://cassian.example.com/a2a",
    "version": "1.0.0",
    "tags": ["macos", "home"]
  },

  // Skills this agent advertises to peers. Default is [] — peers see the agent
  // but cannot call any skills until explicitly listed here.
  "advertised_skills": [],

  // Named peers this agent is allowed to call outbound.
  "peers": [
    {
      "name": "Meridian",
      "url": "https://meridian.example.com/a2a",
      "card_url": "https://meridian.example.com/.well-known/agent-card.json",
      "token_env": "A2A_OUTBOUND_TOKEN_MERIDIAN",
      "card_refresh_seconds": 300   // how often to re-fetch the peer's Agent Card (default: 300)
    }
  ],

  // Optional tuning.
  "limits": {
    "max_hops": 5,                  // reject inbound calls that have hopped too many times
    "request_timeout_ms": 60000,    // timeout for both inbound agent runs and outbound calls
    "inbound_concurrency": 10       // max concurrent inbound calls (future; not yet enforced)
  },

  // Audit configuration.
  "audit": {
    "jsonl_path": "~/.openclaw/logs/a2a-audit.jsonl",  // ~ is expanded; created automatically
    "full_payloads": false,         // set true to log full message text (noisy; off by default)
    "mongo": {
      "enabled": true,
      "url_env": "A2A_MONGO_URL",   // env var holding the MongoDB connection string
      "collection": "a2a_audit",    // collection name (default: "a2a_audit")
      "fail_open": true,            // if true, Mongo failures never affect A2A calls
      "queue_max": 1000             // max in-memory queue depth before oldest records are dropped
    }
  },

  // Optional Discord webhook notifications.
  "discord_visibility": {
    "enabled": false,
    "webhook_url": null,            // Discord webhook URL (set to enable posting)
    "events": ["inbound", "outbound"],  // which directions to announce
    "format": "compact"             // "compact" or "verbose"
  }
}
```

---

## Endpoints

Both endpoints are served by the gateway's existing HTTP server. No separate port or listener is needed.

| Path | Method | Auth |
|------|--------|------|
| `/.well-known/agent-card.json` | GET | none |
| `/a2a` | POST | Bearer token (per-peer, from env var) |

### Agent Card

`GET /.well-known/agent-card.json` returns a standard A2A v0.3.0 Agent Card describing this agent, its capabilities, and the skills it has chosen to advertise to peers.

Skill exposure is opt-in: only skills listed in `advertised_skills` appear in the card. The default is an empty list — peers can discover the agent but cannot call any skills until you explicitly add them.

### A2A endpoint

`POST /a2a` accepts JSON-RPC 2.0 requests from authorized peers.

**Implemented methods:**

| Method | Behaviour |
|--------|-----------|
| `message/send` | Runs the local OpenClaw agent with the peer's message; returns the agent's reply as A2A artifacts |
| `tasks/get` | Stubbed — returns not-found for all task IDs (synchronous MVP; no async tasks) |
| `tasks/cancel` | Stubbed — returns "not supported" |

---

## The `a2a_call_peer` tool

Registered as an agent tool. The agent invokes it when it needs to delegate work to a peer and wait for a result.

```
a2a_call_peer(
  peer="Meridian",
  message="Run a Terraform plan for workspace cassian-staging and return the output.",
  parts=[{ "kind": "data", "data": { "workspace": "cassian-staging" } }],
  skill_id="iac.terraform_plan",
  timeout_ms=90000
)
```

| Parameter | Required | Description |
|-----------|----------|-------------|
| `peer` | yes | Peer name as configured (case-insensitive) |
| `message` | yes | Request text |
| `parts` | no | Additional structured A2A parts (DataPart or FilePart objects) |
| `skill_id` | no | Skill ID hint to help the peer route the request |
| `timeout_ms` | no | Per-call timeout override (default: `limits.request_timeout_ms` or 60 000ms) |

The tool returns the peer's text response. If the peer returns multiple text parts they are joined with newlines.

See [SKILL.md](SKILL.md) for the full agent-facing decision guidance.

---

## Peer card caching

On startup, the plugin fetches the Agent Card for every configured peer and caches it in memory. A background service refreshes each card on the interval set by `card_refresh_seconds` (default 300 seconds).

Cached peer skills are injected into the local agent's system context at the start of every turn and on every heartbeat prompt contribution. This means the agent can see what each peer is capable of without needing to be told explicitly.

---

## Auditing

Every inbound and outbound call writes an audit record with this shape:

```jsonc
{
  "ts": "2026-05-10T12:34:56.789Z",
  "direction": "inbound",           // or "outbound"
  "peer": "Meridian",
  "method": "message/send",
  "request_id": "<uuid>",
  "parent_request_id": null,
  "hops": 0,
  "skill_id": "iac.terraform_plan", // null if not provided
  "latency_ms": 842,
  "status": "ok",                   // "ok" | "error" | "timeout"
  "error": null,
  "request_summary": "first 200 chars of message…",
  "response_summary": "first 200 chars of reply…"
}
```

### JSONL (always-on)

Written synchronously to disk on every call. Path defaults to `~/.openclaw/logs/a2a-audit.jsonl`; override with `audit.jsonl_path`. The parent directory is created automatically. The file is append-only — restarts preserve existing entries.

### MongoDB (fail-open)

When `audit.mongo.enabled` is true, records are also pushed onto an in-memory queue and drained by a background worker every 5 seconds. This path is entirely off the critical path — A2A calls are never blocked or failed due to Mongo issues.

Failure behaviour:
- If the queue exceeds `queue_max`, the oldest record is dropped and a `mongo_audit_dropped` event is written to the JSONL file.
- If a record fails to insert after 3 retries (200ms / 400ms backoff), a `mongo_audit_failure` event is written to the JSONL file with the original `request_id` for manual replay.
- The worker reconnects automatically on the next drain cycle after a connection failure.

**Required environment variable:** `A2A_MONGO_URL` (or the value of `audit.mongo.url_env`).

---

## Discord visibility

When enabled, the plugin posts a one-line notification to a Discord webhook for each A2A call. Three conditions must all be true for any post to occur:

1. `discord_visibility.enabled: true`
2. `discord_visibility.webhook_url` is set to a valid Discord webhook URL
3. The event direction matches `discord_visibility.events` (default: both `inbound` and `outbound`)

Any gate off → silent skip. Discord failures are caught and logged but never affect the A2A call.

**Compact format** (default):
```
<- Meridian `iac.terraform_plan` (842ms)
-> Meridian `message/send` (120ms)
```

**Verbose format** (`format: "verbose"`):
```
**A2A inbound** · peer: `Meridian` · method: `message/send` · skill: `iac.terraform_plan` · latency: 842ms · status: ok
```

---

## Environment variables

| Variable | Used for |
|----------|----------|
| `A2A_INBOUND_TOKEN_<PEER>` | Bearer token to accept inbound calls from a peer (one per peer; name set by `auth.inbound_tokens[].token_env`) |
| `A2A_OUTBOUND_TOKEN_<PEER>` | Bearer token to use when calling a peer outbound (one per peer; name set by `peers[].token_env`) |
| `A2A_MONGO_URL` | MongoDB connection string for the audit worker (name overrideable via `audit.mongo.url_env`) |

Tokens are validated with a constant-time comparison. Never log them.

---

## Auth model

### Inbound

Bearer token in the `Authorization` header. One token per peer (enables per-peer revocation). Cloudflare Tunnel + service auth is the outer perimeter; the bearer token is the application-layer check.

### Outbound

Bearer token per peer, stored as environment variable name indirection in `peers[].token_env`. The token is never written to config files or logs.

---

## Hop limits

The `X-A2A-Hops` request counter prevents infinite ping-pong between peers. When `limits.max_hops` is exceeded (default: 5), the call is rejected with JSON-RPC error `-32000` and the call chain is logged for debugging.

---

## See also

- Design doc: [docs/openclaw-a2a-peer-design.md](docs/openclaw-a2a-peer-design.md)
- Agent tool guidance: [SKILL.md](SKILL.md)
integration

Comments

Sign in to leave a comment

Loading comments...