@identyclaw

OpenClaw A2A Plugin

IdentyClaw fork of the OpenClaw A2A plugin with RODiT / Passport JWT authentication for peer agents.

当前版本
v0.4.12
code-plugin社区source-linked

IdentyClaw A2A Gateway Component

OpenClaw plugin — Passport JWT peer messaging for IdentyClaw agents

Part of IdentyClaw.

IdentyClaw component service: OpenClaw plugin for A2A peer messaging with RODiT / Passport JWT authentication. JWT login and validation follow the live API contract (via @rodit/rodit-auth-be — not vendored). Protocol docs: MCP discovery · OpenAPI. Forked from @a2anet/openclaw-a2a-plugin. See a2afork.md and UPSTREAM.md.

IdentyClaw A2A Gateway Component

GitHub npm version License A2A Protocol RODiT JWT

<p align="center"> <img src="images/identyclaw-a2a-ecosystem.svg" alt="IdentyClaw stack: OpenClaw gateway, this A2A component, and live Passport API JWT contract" width="960"/> </p>

Role in the IdentyClaw stack

LayerArtifactResponsibility
Identity & HOLAopenclaw-identyclaw-pluginAPI login, DID, HOLA, operator tools
Passport API (live)MCP discovery · OpenAPIJWT issuance contract, POST /api/login, token metadata (webhook_url)
A2A wire protocol (this repo)identyclaw-a2aAgent Card discovery, POST /a2a, inbound JWT validation, outbound P2P login, peer tools
Agent runtimeOpenClaw gatewayChat, hooks, sandbox, tool execution

Install this plugin when Passport-authenticated agents need to call or accept A2A peers — not for IdentyClaw API login or HOLA (use identyclaw-tools for that). Outbound P2P login uses NEAR Passport credentials in the gennearaccount JSON layout under identyclaw-agents secrets/near-credentials/.

Outbound and inbound agents exchange messages and files over the A2A protocol. A2A Utils powers the tool layer. Tailscale or a reverse proxy remains the operator's choice for exposing the gateway.

Your agent gets 6 outbound tools for peer messaging without a third-party chat app or email:

  • a2a_get_agents to list the agents it's connected to
  • a2a_get_agent to view an agent's skills in detail
  • a2a_send_message to send messages and files. The agent will respond with a context_id and task_id, which your agent can use to continue the conversation.
  • a2a_get_task to poll for a response if either agent loses connection or a response hasn't been recieved in over a minute
  • a2a_view_text_artifact to view large text responses that have been minimised
  • a2a_view_data_artifact to view large data responses that have been minimised

The plugin also allows your agent to receive messages and files via Tailscale or reverse proxies (nginx, Caddy, etc.). With RODiT inbound auth, peers authenticate with Passport JWTs (same contract as the live Passport API). Legacy API-key auth remains available for dev — see IdentyClaw usage. Each inbound message creates a separate conversation identified by the sender label and context_id, so your agent can run multiple peer threads at once.

📺 Reference demo (upstream)

IdentyClaw vs upstream: The upstream demo uses API keys (Authorization: Bearer <api-key>). IdentyClaw deployments use Passport JWT via RODiT for peer messaging — do not copy static Bearer header patterns from the upstream video. See IdentyClaw usage below.

The upstream project published a generic OpenClaw A2A walkthrough: OpenClaw A2A Plugin Demo.

📦 Installation

From ClawHub:

openclaw plugins install clawhub:@identyclaw/openclaw-a2a-plugin

From npm:

openclaw plugins install @identyclaw/openclaw-a2a-plugin

During development you can also install from a local path or git URL. Upstream package: @a2anet/openclaw-a2a-plugin (API-key auth only).

Restart the gateway:

openclaw gateway restart

Follow the set up instructions in "🔐 IdentyClaw usage (RODiT peers)", "📤 Sending Messages (outbound)", and/or "📥 Receiving Messages (inbound)".

Related IdentyClaw artifacts

ArtifactInstall / linkRole
This plugin (identyclaw-a2a)openclaw plugins install clawhub:@identyclaw/openclaw-a2a-pluginA2A send/receive, RODiT JWT on POST /a2a, peer /api/login routes
Passport API (live)MCP discovery · OpenAPIJWT contract, POST /api/login, token metadata (webhook_url)
IdentyClaw toolsopenclaw plugins install clawhub:@identyclaw/openclaw-identyclaw-pluginAPI login, HOLA, identity, DID — openclaw-identyclaw-plugin
NEAR credentials layoutgennearaccount · identyclaw-agents secrets/near-credentials/Implicit-account JSON for outbound P2P login
Skill (workflows)openclaw skills install clawhub:identyclawOperator playbooks and reference docs

identyclaw-tools and this plugin can share IDENTYCLAW_ACCOUNT_ID, IDENTYCLAW_NEAR_PRIVATE_KEY, and IDENTYCLAW_BASE_URL. HOLA stays application-layer via identyclaw_* tools; A2A peer calls use Passport JWTs automatically through this component.

Pair with the tools plugin: A2A Passport JWTs authenticate the wire protocol only. Application-layer HOLA and identity tools live in openclaw-identyclaw-pluginA2A JWT ≠ HOLA.

Recommended deploy: For production TLS and nginx routing of /a2a and /.well-known/agent-card.json, use the identyclaw-agents template (./identyclaw.sh init, deploy scripts, smoke tests).

🔐 IdentyClaw usage (RODiT peers)

This component implements the same peer JWT model as the live Passport API: inbound agents validate Passport JWTs on POST /a2a and expose /api/login* for peer outbound login; outbound agents call each peer's login endpoint via @rodit/rodit-auth-be instead of pre-shared A2A API keys.

Legacy API key auth remains available for development or non-RODiT peers (inbound.auth.provider: "apiKey", or allowApiKeyFallback: true with RODiT).

Environment variables

Outbound P2P login uses the NEAR Passport credentials file (gennearaccount JSON layout under identyclaw-agents secrets/near-credentials/):

VariablePurpose
RODIT_NEAR_CREDENTIALS_SOURCEfile (recommended with identyclaw-agents)
NEAR_CREDENTIALS_FILE_PATHPath to Passport JSON, e.g. …/secrets/near-credentials/<hash>.json
NEAR_CONTRACT_IDRODiT contract on mainnet (e.g. genaaaa-identyclaw-com.near)

IDENTYCLAW_* env vars remain used by identyclaw-tools (HOLA, DID, identity) on the same host. This plugin uses IDENTYCLAW_BASE_URL (or outbound.identityApiBaseUrl / Passport subjectuniqueidentifier_url) only for identity-API peer resolution — it does not default to https://api.identyclaw.com.

Outbound login calls login_server against each peer's /api/login (with subjectuniqueidentifier_url set to that peer's gateway base). JWTs are cached per outbound peer and scoped to that receiver's aud.

Keep credentials in env or secrets files — not in openclaw.json.

Embedding in OpenClaw chat / gateway (quiet mode)

When this plugin is enabled, OpenClaw loads it in chat (node dist/index.js chat) as well as the gateway. @rodit/rodit-auth-be logs JSON to stdout and node-config warns on stderr at import time, which can appear inline in the chat TUI.

This plugin lazy-loads rodit-auth-be only on the first inbound JWT validation or outbound login_server call, and applies quiet embed defaults before import:

VariableDefault when unset
LOG_LEVELerror
SUPPRESS_NO_CONFIG_WARNINGtrue
SUPPRESS_STRICTNESS_CHECKtrue

Host env vars always win — IdentyClaw agents can set these in .env (e.g. via sync_quiet_plugin_env on bootstrap). Override per-plugin with inbound.auth.logLevel or outbound.auth.logLevel in openclaw.json until upstream rodit-auth-be defaults to library-quiet mode.

Outbound: call a RODiT peer

Configure the remote agent's Agent Card URL and enable dynamic JWT login. Do not set custom_headers.Authorization for RODiT peers; the plugin obtains and caches the Bearer token automatically (refreshes once on HTTP 401).

{
    "plugins": {
        "entries": {
            "identyclaw-a2a": {
                "enabled": true,
                "config": {
                    "outbound": {
                        "auth": {
                            "provider": "rodit",
                            "jwtCacheTtlSeconds": 300
                        },
                        "agents": {
                            "agent-b": {
                                "url": "http://openclaw-agent-b:18789/.well-known/agent-card.json"
                            }
                        }
                    }
                }
            }
        }
    }
}
FieldTypeDefaultDescription
auth.provider"rodit"Enable P2P peer-issued JWT login for all outbound agents
auth.jwtCacheTtlSecondsnumber300Per-peer in-memory JWT cache TTL before re-login
auth.peerLoginPathstring/api/loginLogin path on the peer gateway
auth.peerTimestampPathstring/api/login/timestampTimestamp challenge path on the peer gateway
resolvePeersByTokenIdbooleantrue (with auth.provider: "rodit")Resolve unknown Passport token_id peers via IdentyClaw API /full, then on-chain metadata.webhook_url fallback
persistResolvedPeersbooleanfalsePersist resolved peers to stateDir/a2a/outbound/peers.json
identityApiBaseUrlstringIdentyClaw identity API base for GET /full peer lookup. Also IDENTYCLAW_BASE_URL or Passport subjectuniqueidentifier_url. No hardcoded default.
auth.logLevelstringerrorWinston level for rodit-auth-be when loaded
agents.*.loginBaseUrlstringderived from Agent Card URLOverride P2P login target when it differs from the card origin

When resolvePeersByTokenId is enabled, a2a_send_message can target a bare Passport token_id not listed in outbound.agents. The plugin tries GET /api/identity/token/{tokenId}/full (authenticated with your NEAR creds), reads metadata.webhook_url, and falls back to NEAR chain lookup (nearorg_rpc_tokenfromroditid) when the API is unavailable or has no webhook. API→chain fallback is logged with the failed source, error, and chosen source. The identity API base must come from outbound.identityApiBaseUrl, IDENTYCLAW_BASE_URL, or Passport subjectuniqueidentifier_url — there is no hardcoded api.identyclaw.com default. It then registers {webhook_url}/.well-known/agent-card.json before normal P2P login. DN contactUri is not used for A2A discovery — it is identity contact metadata (often email:…). Use identyclaw_get_agent_identity when you need DN traits.

Peer identity: key outbound.agents by Passport token_id (recommended). The a2a_* tools expose and accept token_id for peers; legacy config aliases without a Passport token (e.g. dev self-loop self) use agent_id instead.

Non-auth custom_headers on individual agents still work (e.g. tracing headers).

Inbound: accept RODiT peer JWTs

{
    "plugins": {
        "entries": {
            "identyclaw-a2a": {
                "enabled": true,
                "config": {
                    "inbound": {
                        "publicBaseUrl": "https://agent-a.diholai.io",
                        "auth": {
                            "provider": "rodit",
                            "issuer": "https://api.identyclaw.com",
                            "audience": "<own passport owner_id>",
                            "identityClaim": "token_id"
                        },
                        "roditLogin": {
                            "loginMode": "p2p"
                        },
                        "agentCard": {
                            "name": "Juanelo",
                            "description": "IdentyClaw agent"
                        }
                    }
                }
            }
        }
    }
}
FieldTypeDefaultDescription
auth.provider"rodit" | "apiKey" | "none""apiKey"Inbound authentication mode
auth.issuerstringExpected JWT iss
auth.audiencestringOwn passport owner_id — expected JWT aud for P2P-issued peer tokens
auth.identityClaimstring"token_id"JWT claim used as inbound sender label / thread key
auth.allowApiKeyFallbackbooleanfalseWhen provider is rodit, also accept configured apiKeys
auth.logLevelstringerrorWinston level for rodit-auth-be when loaded
roditLogin.enabledbooleantrue when auth.provider is roditExpose GET /api/login/timestamp and POST /api/login for peer outbound login
roditLogin.loginMode"p2p" | "partner" | "promiscuous""promiscuous"Maps to SECURITY_OPTIONS_LOGIN_MODE
publicBaseUrlstringExternal base URL for Agent Card url fields (see below)

Verified peer JWTs map to a sender label (e.g. Passport token_id) used for inbound conversation routing, the same role apiKeys[].label plays for API-key auth.

publicBaseUrl and reverse proxies

By default, Agent Card URLs are derived from the incoming request (Host, X-Forwarded-Host, X-Forwarded-Proto). Behind a reverse proxy or when internal container DNS differs from the public hostname, set inbound.publicBaseUrl so discovery advertises the URL peers actually use:

"inbound": {
    "publicBaseUrl": "https://agent-a.diholai.io"
}

Set auth.audience to your own passport owner_id (the aud your agent mints on /api/login). See docs/jwt-audience-alignment.md for Tier 2 discovery. publicBaseUrl is for Agent Card discovery and is separate from JWT aud.

Example Agent Card endpoint after configuration:

https://agent-a.diholai.io/a2a

(publicBaseUrl + /a2a for single-agent mode; multi-agent mode appends /a2a/<agentId>.)

Internal vs external peer URLs

Use different URLs depending on who is calling:

CallerAgent Card URLNotes
agent-a → agent-b (same Podman network)http://openclaw-agent-b:18789/.well-known/agent-card.jsonContainer DNS; outbound config only
agent-b → agent-a (same Podman network)http://openclaw-agent-a:18789/.well-known/agent-card.jsonContainer DNS; outbound config only
External peer → agent-ahttps://agent-a.diholai.io/.well-known/agent-card.jsonPublic hostname; matches publicBaseUrl

Outbound peers use the discovery URL from their config (outbound.agents.<id>.url). Inbound agents use publicBaseUrl (when set) for the URL embedded in their own Agent Card.

External URL layout (same host)

Same-host layout: A2A shares the gateway with OpenClaw hooks on separate paths. For RODiT-signed webhook ingress on /hooks/wake and /hooks/agent, pair with openclaw-identyclaw-webhooks-plugin. Set inbound.publicBaseUrl to match Passport metadata.webhook_url (gateway base — scheme, host, port — with no path suffix).

agent-a.example.com/hooks/agent              → OpenClaw hooks (unchanged)
agent-a.example.com/hooks/wake               → OpenClaw wake hook (webhooks plugin)
agent-a.example.com/a2a                      → A2A JSON-RPC
agent-a.example.com/.well-known/agent-card.json  → A2A discovery

Do not mount A2A under /hooks/a2a unless you rewrite Agent Card URLs at the proxy.

Development fallback

For local testing without Passport JWTs, use API keys as upstream does, or enable RODiT with API key fallback:

"inbound": {
    "auth": { "provider": "rodit", "issuer": "…", "audience": "…", "allowApiKeyFallback": true },
    "apiKeys": [{ "label": "dev-peer", "key": "…" }]
}

Full rollout plan and staging test tiers: a2afork.md.

💡 Use Cases

  • Connect IdentyClaw-managed OpenClaw agents across hosts using Passport token_id discovery and P2P JWT login
  • Expose an agent's skills on the public A2A wire while keeping authentication aligned with the live Passport API
  • Message peer agents by Passport identity without exchanging static API keys
  • Bridge a sandboxed local agent to a production IdentyClaw agent over HTTPS with RODiT-scoped JWTs
  • Continue multi-turn A2A tasks with file attachments between operator-run Passport peers
  • Fall back to upstream API-key auth for local dev or non-RODiT peers

✨ Features

  • Send messages to remote agents — 6 outbound tools (a2a_get_agents, a2a_get_agent, a2a_send_message, a2a_get_task, a2a_view_text_artifact, a2a_view_data_artifact) for communicating with any A2A agent
  • Receive messages from remote agents — expose your OpenClaw agent as an A2A server with Agent Card discovery, JSON-RPC 2.0 endpoint, and SSE streaming
  • Host multiple agents — expose several agents from one gateway, each with its own /a2a/<agentId> endpoint, Agent Card discovery URL, and card metadata
  • Send and receive files — outbound messages can include local file paths (up to 1MB) or URLs; inbound files are saved locally
  • Multi-turn conversations — continue conversations across multiple messages using context_id
  • Long-running task support — if a2a_send_message times out, use a2a_get_task to monitor until the task reaches a terminal state
  • Automatic artifact minimization — large text and data artifacts are automatically minimized for LLM context windows, with dedicated tools for detailed navigation
  • Inbound authentication — RODiT / Passport JWT validation (IdentyClaw fork), plus API key-based auth with timing-safe comparison, per-key labels, and CLI key management
  • Structured audit logging — opt-in daily NDJSON logs for inbound/outbound A2A and RODiT login (audit.enabled: true; query with openclaw a2a audit; JWTs never logged)
  • Outbound RODiT login — dynamic JWT acquisition and cache refresh for IdentyClaw peers (no static Authorization headers in config)
  • Public base URLinbound.publicBaseUrl for correct Agent Card URLs behind reverse proxies
  • Live Agent Card updates — update your agent's name, description, and skills at runtime with a2a_update_agent_card without restarting
  • Tailscale integration — expose your agent to the internet via Tailscale Funnel, or restrict to your tailnet with Tailscale Serve
  • Custom headers and outbound auth — per-agent custom headers with ${ENV_VAR} substitution for secrets
  • Configurable timeouts and limits — control character limits, timeouts, poll intervals, and whether to enable task and file storage

🧾 Audit logging

Inbound /a2a, Agent Card discovery, RODiT /api/login, and outbound tools can write structured NDJSON to {stateDir}/a2a/audit/a2a-audit-YYYY-MM-DD.jsonl (JWTs and secret-looking fields are never logged). Off by default — enable with:

{
  "plugins": {
    "entries": {
      "identyclaw-a2a": {
        "config": {
          "audit": {
            "enabled": true,
            "retentionDays": 30,
            "includeContentSummary": true
          }
        }
      }
    }
  }
}

Query logs:

openclaw a2a audit --peer <token_id> --limit 20
openclaw a2a audit --errors --since 2026-08-01
openclaw a2a audit --task-id <uuid> --event-type message_sent

🤖 A2A Core Concepts

The A2A protocol is a protocol for agent-to-agent communication supported by AWS, Azure, GCP, and 150+ enterprises.

  • Agent Card — A JSON object at a publicly available URL (e.g. /.well-known/agent-card.json) that describes an agent (name, description, skills, etc).
  • Message — a single communication turn between agents, containing one or more Parts. Each message has a role (user or agent).
  • Part — content within a Message, Task, or Artifact: text (TextPart), JSON data (DataPart), or files (FilePart).
  • Task — a unit of work with a unique ID. Useful for long-running tasks, agents can disconnect and poll intermittently.
  • Artifact — output produced by a task (e.g. generated text, JSON data, files).

📤 Sending Messages (outbound)

Set Up

Configure at least one remote agent in your OpenClaw config. You just need the remote agent's Agent Card URL (and API key, if required). No Tailscale or port exposure needed.

{
    "tools": {
        "profile": "full"
    },
    "plugins": {
        "entries": {
            "identyclaw-a2a": {
                "enabled": true,
                "config": {
                    "outbound": {
                        "agents": {
                            "weather": {
                                "url": "https://weather-agent.example.com/.well-known/agent-card.json"
                            },
                            "search": {
                                "url": "https://example.com/search-agent/agent-card.json",
                                "custom_headers": {
                                    "Authorization": "Bearer ${SEARCH_API_KEY}"
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "sandbox": {
        "tools": {
            "alsoAllow": [
                "a2a_get_agents",
                "a2a_get_agent",
                "a2a_send_message",
                "a2a_get_task",
                "a2a_view_text_artifact",
                "a2a_view_data_artifact"
            ]
        }
    }
}

Note: Header values support ${ENV_VAR} substitution so you can keep secrets out of your config file. The "sandbox" section is only required if sandbox is enabled.

FieldTypeDefaultDescription
agentsRecord<string, {url, custom_headers?}>Named remote agents. Keys are agent IDs used in tool calls.
taskStorebooleantrueEnable persistent task storage.
fileStorebooleantrueEnable persistent file artifact storage.
sendMessageCharacterLimitnumber50000Maximum characters for minimized artifact text.
minimizedObjectStringLengthnumber5000Maximum string length for minimized data objects.
viewArtifactCharacterLimitnumber50000Maximum characters returned by view artifact tools.
agentCardTimeoutnumber15Timeout in seconds for fetching remote agent cards.
sendMessageTimeoutnumber60Timeout in seconds for send message requests.
getTaskTimeoutnumber60Timeout in seconds for get task monitoring.
getTaskPollIntervalnumber5Interval in seconds between task status polls.
auth.provider"rodit"Enable P2P peer-issued JWT login for outbound calls (IdentyClaw fork).
auth.jwtCacheTtlSecondsnumber300Per-peer JWT cache TTL in seconds before re-login.
auth.peerLoginPathstring/api/loginLogin path on peer gateways.
auth.peerTimestampPathstring/api/login/timestampTimestamp challenge path on peer gateways.
auth.logLevelstringerrorWinston level for rodit-auth-be when loaded.
agents.*.loginBaseUrlstringderivedOverride P2P login base when it differs from the Agent Card origin.
resolvePeersByTokenIdbooleantrue with RODiT authResolve unknown peers via API /full, then on-chain metadata.webhook_url.
persistResolvedPeersbooleanfalsePersist resolved peers under stateDir/a2a/outbound/peers.json.
identityApiBaseUrlstringIdentyClaw identity API base for GET /full peer lookup. Also IDENTYCLAW_BASE_URL or Passport subjectuniqueidentifier_url. No hardcoded default.

Tools

The a2a_* tools are registered when at least one agent is configured (agents), or when RODiT outbound auth has resolvePeersByTokenId enabled (send by Passport token_id without preconfigured peers). The plugin is powered by A2A Utils, for example tool usage, results, etc. see A2A Utils JavaScript A2ATools.

a2a_get_agents

List all available remote A2A agents with names and descriptions.

No parameters.

Each Passport peer entry includes token_id (pass to other a2a_* tools). Legacy config aliases without a Passport token include agent_id instead. The Agent Card name is display metadata only — never use it as an identifier.

a2a_get_agent

Get detailed info about a specific agent, including skills.

ParameterTypeRequiredDescription
token_idstringYesPassport token_id from a2a_get_agents (or legacy agent_id alias)

a2a_send_message

Send a message to a remote agent and receive a structured response. The message is sent non-blocking — the tool streams or polls for updates until the task reaches a terminal state or the timeout is reached. If the task is still in progress after the timeout, the current task state is returned. Use a2a_get_task with the returned id to continue monitoring.

ParameterTypeRequiredDescription
token_idstringYesPassport token_id from a2a_get_agents (or legacy agent_id alias)
messagestringYesMessage content to send
context_idstringNoContinue an existing multi-turn conversation
task_idstringNoAttach to an existing task (for input_required flows)
timeoutnumberNoOverride default timeout in seconds
dataarrayNoStructured data to include with the message. Each item is sent as a separate JSON object or array alongside the text.
filesarrayNoFiles to include with the message. Accepts local file paths (read and sent as binary, max 1MB) or URLs (sent as references for the remote agent to fetch).

a2a_get_task

Check the progress of an A2A task that is still in progress. Monitors until the task reaches a terminal state or the timeout is reached. If still in progress, returns the current task state — call again to continue monitoring.

ParameterTypeRequiredDescription
token_idstringYesPassport token_id from a2a_get_agents (or legacy agent_id alias)
task_idstringYesTask ID from a previous a2a_send_message
timeoutnumberNoMonitoring timeout in seconds
poll_intervalnumberNoInterval between status checks in seconds

a2a_view_text_artifact

View text content from an artifact, optionally selecting a line or character range. Can select by line range OR character range, but not both.

ParameterTypeRequiredDescription
token_idstringYesPassport token_id from a2a_get_agents (or legacy agent_id alias)
task_idstringYesTask ID containing the artifact
artifact_idstringYesThe artifact's unique identifier
line_startnumberNoStarting line number (1-based, inclusive)
line_endnumberNoEnding line number (1-based, inclusive)
character_startnumberNoStarting character index (0-based, inclusive)
character_endnumberNoEnding character index (0-based, exclusive)

a2a_view_data_artifact

View structured data from an artifact with optional JSON path, row, and column filtering.

ParameterTypeRequiredDescription
token_idstringYesPassport token_id from a2a_get_agents (or legacy agent_id alias)
task_idstringYesTask ID containing the artifact
artifact_idstringYesThe artifact's unique identifier
json_pathstringNoDot-separated path to navigate data (e.g. "results.items")
rowsstringNoRow selection for list data ("0", "0-10", "0,2,5", or "all")
columnsstringNoColumn selection for tabular data ("name", "name,age", or "all")

📥 Receiving Messages (inbound)

Set Up

Other agents can discover and message your OpenClaw agent through the inbound endpoint. Follow the steps below to make your agent reachable.

1. Configure Inbound

{
    "tools": {
        "profile": "full"
    },
    "plugins": {
        "entries": {
            "identyclaw-a2a": {
                "enabled": true
            }
        }
    },
    "sandbox": {
        "tools": {
            "alsoAllow": ["a2a_update_agent_card"]
        }
    }
}

Note: The "sandbox" section is only required if sandbox is enabled.

FieldTypeDefaultDescription
agentCard.namestringAgent identity nameAgent Card display name.
agentCard.descriptionstring"AI assistant powered by OpenClaw"Agent Card description.
agentCard.skillsarray[]Skills to advertise. Each needs id, name, description. Optional: tags, examples, inputModes, outputModes. Can also be set at runtime with a2a_update_agent_card.
apiKeysarrayArray of { label, key } objects for inbound auth (API-key mode or RODiT fallback).
allowUnauthenticatedbooleanfalseSkip authentication for inbound requests.
authobject{ provider: "apiKey" }Inbound auth: rodit, apiKey, or none. See IdentyClaw usage.
publicBaseUrlstringExternal base URL for Agent Card discovery (overrides request Host / proxy headers). Pair with auth.audience for RODiT.
agentsobjectNamed inbound agents, keyed by agent ID. Each value takes an agentCard (name, description, skills). See Hosting Multiple Agents.

Hosting Multiple Agents

A single gateway can expose several agents, each as its own addressable A2A endpoint. Add inbound.agents, keyed by agent ID. Each key is both the URL path segment and the OpenClaw agent ID that handles the message, so a message to /a2a/swe is routed to the OpenClaw agent swe. The key must match a configured OpenClaw agent ID, or requests to it will fail to route.

{
    "plugins": {
        "entries": {
            "identyclaw-a2a": {
                "enabled": true,
                "config": {
                    "inbound": {
                        "apiKeys": [{ "label": "flynn", "key": "..." }],
                        "agents": {
                            "swe": {
                                "agentCard": {
                                    "name": "SWE",
                                    "description": "Software engineering agent",
                                    "skills": [
                                        { "id": "code", "name": "Code", "description": "Writes and reviews code" }
                                    ]
                                }
                            },
                            "pmo": {
                                "agentCard": { "name": "PMO", "description": "Project management agent" }
                            }
                        }
                    }
                }
            }
        }
    }
}

Each agent gets its own JSON-RPC endpoint and Agent Card discovery URL:

AgentJSON-RPC endpointAgent Card discovery URL
swe/a2a/swe/a2a/swe/agent-card.json
pmo/a2a/pmo/a2a/pmo/agent-card.json

The apiKeys / allowUnauthenticated settings apply to every agent on the host. Each agent can update its own card at runtime with a2a_update_agent_card — the edit is scoped to the calling agent and persisted under its inbound.agents.<agentId>.agentCard. When inbound.agents is omitted, a single agent is exposed on the default /a2a and /.well-known/agent-card.json paths, configured via inbound.agentCard.

2. Restart the Gateway

The plugin registers its HTTP endpoints on startup, so a restart is required:

openclaw gateway restart

3. Expose Your Gateway

You need to make your gateway's HTTP port (default 18789) reachable from the internet. Tailscale Funnel is the recommended approach — it gives your machine a public HTTPS URL with automatic TLS certificates, no port forwarding or DNS configuration needed. You can also use any reverse proxy (nginx, Caddy, etc.).

Note: The commands below were verified on macOS (Apple Silicon) with the Tailscale Mac app. The overall flow is the same on Linux and Windows, but the install and daemon setup will differ — consult the Tailscale install docs for your OS.

Install Tailscale

Install the Tailscale Mac app. The GUI app ships a Network Extension that plumbs MagicDNS into macOS's system resolver, so browsers and other apps can resolve your *.ts.net hostname.

After installing, launch the app, click the Tailscale menu bar icon, and sign in. The CLI is bundled with the app.

Confirm you're online:

tailscale status

You should see your node name, tailnet IP, and user.

Provision an HTTPS Certificate

Funnel needs a LetsEncrypt cert for your node's *.ts.net name. Running tailscale cert once provisions it and also confirms that HTTPS certificates and MagicDNS are enabled on your tailnet:

cd /tmp && tailscale cert "$(tailscale status --json | jq -r '.Self.DNSName | rtrimstr(".")')"

The cd /tmp is because tailscale cert writes <host>.crt and <host>.key to the current directory.

Enable Funnel
tailscale funnel --bg http://localhost:18789

On success, Tailscale prints the public URL, e.g.:

Available on the internet:

https://your-machine.tailXXXXXX.ts.net/
|-- proxy http://localhost:18789

If the funnel command fails with a policy error, you need to add the Funnel ACL attribute in the admin console (there is no CLI equivalent for editing ACLs):

"nodeAttrs": [
  {
    "target": ["autogroup:member"],
    "attr": ["funnel"]
  }
]

It can take up to a minute or two after tailscale funnel --bg returns before the public URL actually serves traffic from the open internet, because the Funnel edge has to propagate your config and finish TLS provisioning. If an external request returns a TLS error or "broken pipe", wait ~60s and retry.

Tailscale Serve (Tailnet-Only)

If you only need agents on your tailnet to reach you (not the public internet), use Tailscale Serve instead of Funnel:

tailscale serve --bg http://localhost:18789

With Serve, traffic is restricted to your tailnet, so disabling authentication is reasonable.

Stopping Funnel
tailscale funnel --https=443 off

4. Verify

Open your Agent Card URL in a browser:

https://your-machine.tail123.ts.net/.well-known/agent-card.json

You should see the JSON Agent Card (name, description, skills, etc.).

5. Generate an API Key

The Agent Card is public, but for other people to send messages to your OpenClaw you'll need to generate an API key for them:

openclaw a2a generate-key flynn

6. Customise Your Agent Card

The Agent Card will have default values. Once you've generated an API key, ask your OpenClaw to use the a2a_update_agent_card tool to update its Agent Card:

Update your Agent Card with the a2a_update_agent_card tool

7. Share Your URL and Key

Send your Agent Card URL and the generated API key to the person you generated it for. They'll need to install the plugin and add your OpenClaw as a remote agent with the headers:

"custom_headers": {
    "Authorization": "Bearer [GENERATED API KEY]"
}

That's it! Your friend's agent should now be able to send messages and files to your OpenClaw.

Tools

The a2a_update_agent_card tool is registered when inbound is configured (apiKeys, RODiT auth, or allowUnauthenticated).

a2a_update_agent_card

Live-update this agent's A2A Agent Card name, description, or skills. Changes take effect immediately and persist to config — no restart needed. At least one field must be provided. When inbound.agents is configured, the edit is scoped to the calling agent's own card.

ParameterTypeRequiredDescription
namestringNoDisplay name for the Agent Card
descriptionstringNoDescription for the Agent Card
skillsarrayNoSkills to advertise (objects with id, name, description, and optional tags/examples)

🌐 HTTP Endpoints

EndpointMethodAuthDescription
/.well-known/agent-card.jsonGETNoReturns the Agent Card for discovery (single-agent configuration)
/a2aPOSTBearer tokenJSON-RPC 2.0 endpoint supporting message/send, message/stream, tasks/get, tasks/cancel
/a2a/<agentId>/agent-card.jsonGETNoReturns the Agent Card for <agentId> (when inbound.agents is configured)
/a2a/<agentId>POSTBearer tokenJSON-RPC 2.0 endpoint for <agentId> (when inbound.agents is configured)

With RODiT inbound auth, the Bearer token is a Passport JWT (not a static API key). Agent Cards advertise securitySchemes with HTTP Bearer + JWT when inbound.auth.provider is rodit.

Supported JSON-RPC Methods

MethodDescription
message/sendSend a message and wait for the full response
message/streamSend a message with Server-Sent Events (SSE) streaming
tasks/getGet the status and details of a task
tasks/cancelCancel an ongoing task

Error Codes

CodeMeaning
-32700Parse error
-32600Invalid request
-32601Method not found
-32602Invalid params
-32001Authentication required
-32000Server error

💾 Data Storage

Tasks and file artifacts are saved locally, separated by direction. Task state lives under OpenClaw's state directory; file artifacts live under OpenClaw's workspace directory so the agent can access received files.

DirectionTypePath
OutboundTasks<state>/a2a/outbound/tasks/
OutboundFiles<workspace>/a2a/outbound/files/
InboundTasks<state>/a2a/inbound/tasks/
InboundFiles<workspace>/a2a/inbound/files/

When inbound.agents is configured, each agent's inbound tasks and files are isolated under its agent ID — <state>/a2a/inbound/<agentId>/tasks/ and <workspace>/a2a/inbound/<agentId>/files/.

Outbound task/file storage can be disabled with outbound.taskStore: false and outbound.fileStore: false.

Publish to ClawHub

See PUBLISH.md:

npm run publish:clawhub:dry-run
npm run publish:clawhub

Two-host smoke install: test-install-instructions.md. JWT aud vs inbound.auth.audience: docs/jwt-audience-alignment.md. Upstream pin: UPSTREAM.md. Fork plan: a2afork.md.


🛠️ Development

Install the dependencies:

make install

Install git hooks:

make install-hooks

Install the plugin:

openclaw plugins install /absolute/path/to/openclaw-a2a-idc-plugin

Restart the gateway:

openclaw gateway restart

📄 License

Apache-2.0

🔗 IdentyClaw & upstream links


discernible.io · identyclaw-agents · sdk · API docs (MCP)

Suggested GitHub About metadata

FieldValue
DescriptionOpenClaw A2A plugin — Passport JWT peer messaging for IdentyClaw agents
Websitehttps://www.discernible.io/#developers
Topicsidentyclaw, openclaw, a2a, rodit, jwt, clawhub

源码与版本

源码仓库

discernible-io/openclaw-a2a-idc-plugin

打开仓库

源码提交

aa4dab3a7256f09fb9b11b4cfc449709ebb1309b

查看提交

安装命令

openclaw plugins install clawhub:@identyclaw/openclaw-a2a-plugin

元数据

  • 包名: @identyclaw/openclaw-a2a-plugin
  • 创建时间: 2026/06/12
  • 更新时间: 2026/08/18
  • 执行代码:
  • 源码标签: main

兼容性

  • 构建于 OpenClaw: 2026.5.17
  • 插件 API 范围: >=2026.5.17
  • 标签: latest
  • 文件数: 172