@aramisfa/openclaw-a2a-inbound
v1.0.2
Published
Inbound A2A channel plugin for OpenClaw agents
Maintainers
Readme
@aramisfa/openclaw-a2a-inbound
Native OpenClaw inbound A2A channel plugin.
This package serves an A2A agent card plus a JSON-RPC endpoint and routes inbound A2A requests into OpenClaw through one committed task runtime. The runtime is the single source of truth for message/send, message/stream, tasks/get, tasks/cancel, and tasks/resubscribe.
Phase 2 keeps the public A2A contract unchanged while persisting an internal durable committed journal beside the latest committed task snapshot.
Installation
openclaw plugins install @aramisfa/openclaw-a2a-inboundPin the exact published version if you want reproducible installs:
openclaw plugins install @aramisfa/openclaw-a2a-inbound --pinNetworking Prerequisites
This plugin is an inbound channel: it waits for external A2A agents to call it over HTTP. Two independent requirements must both be met before any traffic can arrive.
1. Set publicBaseUrl in the account config
publicBaseUrl is the plugin's only config-level readiness gate. Without it the account will not start because the agent card URL cannot be constructed. Set it to the externally reachable base URL of your gateway (see below).
2. Make the OpenClaw gateway reachable from the internet
OpenClaw binds to 127.0.0.1 by default (gateway.bind: "loopback"). An external agent can never connect to a loopback address. You must configure the gateway to accept external connections before any A2A traffic can arrive:
| Goal | Gateway config |
| --- | --- |
| LAN / reverse proxy (nginx, Caddy, Cloudflare Tunnel, …) | gateway.bind: "lan" (binds 0.0.0.0) |
| Tailscale Serve — tailnet-only HTTPS | gateway.bind: "tailnet", gateway.tailscale.mode: "serve" |
| Tailscale Funnel — public internet HTTPS | gateway.bind: "tailnet", gateway.tailscale.mode: "funnel" |
| Explicit bind host | gateway.bind: "custom", gateway.customBindHost: "<host>" |
Set publicBaseUrl to the URL that resolves through whichever of the above options you choose — the agent card and the gateway endpoint must agree.
Current Contract
- Plugin/package id:
openclaw-a2a-inbound - Registers one OpenClaw channel:
a2a - Serves:
- agent card
- JSON-RPC
- Advertises:
streaming = truepushNotifications = false
- Supports:
message/sendmessage/streamtasks/gettasks/canceltasks/resubscribe
- Rejects with
methodNotFound:tasks/pushNotificationConfig/settasks/pushNotificationConfig/gettasks/pushNotificationConfig/listtasks/pushNotificationConfig/delete
- Does not expose:
- REST transport
/a2a/files- outbound A2A file transport
- push notifications
Default content modes:
- input:
["text/plain", "application/json"] - output:
["text/plain", "application/json"]
Inbound request parts:
- supported:
text,data - rejected: any A2A
filepart withinvalidParams
Serialized A2A payloads do not emit metadata.openclaw.* or vendor openclaw.reply payloads.
Task Storage
Each account can select one task store:
memoryjson-file
If taskStore is omitted, it defaults to:
{ "kind": "memory" }json-file.path must be a non-empty absolute path.
Phase 2 persistence stores one durable schema v2 record per task containing:
- the latest committed task snapshot
- the stored OpenClaw binding
currentSequence- the ordered committed journal of
status-updateandartifact-updateevents
The initial Task snapshot is not journaled. The durable journal is internal-only in this phase and is used only to preserve committed history for future replay work.
Existing schema v1 snapshot-only json-file records load through a lazy one-way upgrade in memory and persist as schema v2 on the next write.
Phase 2 still does not expose:
- public committed journal replay
- public backlog replay
- replay cursors or replay markers
- lease heartbeats
- orphan recovery
- hidden replay toggles
Direct streaming runs that never promote return one canonical final Message and do not materialize a task. Promoted runs persist the committed task snapshot and committed updates.
Each account also exposes agentStyle:
hybrid(default): stay protocol-faithful and allow new blocking or streaming sends to complete as a directMessagewhen task promotion never becomes necessarytask-generating: eagerly materialize every new execution as a task, so simple blocking replies return aTaskand simple streaming replies emit task-bearing events
Example OpenClaw Config
Channel config lives under channels.a2a, not under plugins.entries.
{
channels: {
a2a: {
accounts: {
default: {
enabled: true,
label: "Primary A2A Endpoint",
publicBaseUrl: "https://agents.example.com",
defaultAgentId: "main",
agentCardPath: "/.well-known/agent-card.json",
jsonRpcPath: "/a2a/jsonrpc",
maxBodyBytes: 1048576,
defaultInputModes: ["text/plain", "application/json"],
defaultOutputModes: ["text/plain", "application/json"],
agentStyle: "hybrid",
taskStore: {
kind: "json-file",
path: "/var/lib/openclaw/a2a-tasks"
},
skills: [
{
id: "chat",
name: "Chat"
}
]
}
}
}
}
}publicBaseUrl is the only plugin-level readiness prerequisite — the account will not start without it. Gateway networking must also be configured independently; see Networking Prerequisites.
Streaming And Resubscribe Semantics
message/sendblockingwithagentStyle="hybrid": may return a direct canonicalMessageor a committedTaskblockingwithagentStyle="task-generating": always returns a committedTasknon_blocking: always starts on the task path
message/streamagentStyle="hybrid"direct runs yield exactly one canonical finalMessageagentStyle="task-generating"new runs emit the committed initialTask, then committedstatus-updateandartifact-updateevents, then the committed final status update- promoted runs yield the committed initial
Task, then committedstatus-updateandartifact-updateevents, then the committed final status update
tasks/resubscribe- emits the latest committed task snapshot first
- attaches a live tail only when the task is still active and the execution is live in the current process
- does not replay stored journal backlog in this phase
- terminal, quiescent, and restart-orphaned active tasks emit the snapshot and close
tasks/cancel keeps the same committed semantics:
- terminal tasks pass through unchanged
- quiescent tasks are canceled immediately
- active tasks can be canceled only while live in the current process
Package Layout
src/index.ts: plugin entrypoint and registrationsrc/channel.ts: OpenClaw channel definitionsrc/http-routes.ts: plugin HTTP route registrationsrc/plugin-host.ts: active account registrysrc/a2a-server.ts: A2A SDK server wiringsrc/request-handler.ts: committed request lifecycle handlingsrc/openclaw-executor.ts: bridge from A2AAgentExecutorinto OpenClawsrc/task-store.ts: committed runtime store plus memory/json-file backendssrc/session-routing.ts: inbound message and session mapping helperssrc/config.ts: channel config schema and parser
Requirements
- Node.js
>=22.12.0 - OpenClaw
2026.3.2
Development
corepack pnpm --filter @aramisfa/openclaw-a2a-inbound run build
corepack pnpm --filter @aramisfa/openclaw-a2a-inbound run test