A2A Protocol for AI Agent Hosting Platforms
April 18, 2026·16 min read
Your planner runs on OpenClaw. Your executor runs on Hermes. Your critic lives in a LangGraph service someone shipped two quarters ago. Without a shared protocol, every edge between them becomes bespoke glue code. A2A — agent-to-agent — is the piece that turns a pile of agents into a fleet, and the hosting platform is what makes it deployable.
TL;DR
A2A standardizes the message envelope between independent AI agents (sender, recipient, intent, payload, trace context). It’s the agent-to-agent surface; MCP is the agent-to-tool surface — you need both. An ai agent hosting platform supplies the registry, identity, routing, and observability that turn A2A from a spec into production traffic. This guide includes working code for OpenClaw + Hermes Agent and a deployable topology you can steal.

Want A2A routing built in?
Try Rapid ClawWhy A2A Exists
A single agent is a tractable system. A fleet of agents is a distributed one — and distributed systems without a protocol are a spaghetti plate of HTTP endpoints, ad-hoc JSON shapes, and prompts that paste URLs into each other. Every team that tried to scale past one agent has rebuilt the same thing: a message envelope, a registry, a retry policy, a trace context. A2A is the consolidation of those rebuilds.
Both OpenClaw and Hermes Agent expose the pieces A2A needs — structured tool calls, pluggable transports, async primitives. What they don’t agree on is the shape of the envelope on the wire. A2A fills exactly that gap, which is why the most useful implementations live at the hosting layer, not inside any single framework.
A2A vs MCP in one sentence
MCP is how an agent talks to tools and data; A2A is how an agent talks to other agents. Production deployments need both — and they compose cleanly, because an A2A peer is just a tool with an agent on the other end.
Anatomy of an A2A Message
The envelope is deliberately boring. Every A2A message carries the same required fields — the interesting bits go in payload:
{
"a2a_version": "1.0",
"message_id": "msg_01HZKXR8...",
"correlation_id": "conv_01HZKXR7...",
"trace": {
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7",
"parent_span_id": "00f067aa0ba902b6"
},
"sender": {
"agent_id": "planner-openclaw-prod-01",
"framework": "openclaw",
"signature": "eyJhbGciOiJFZERTQSJ9..."
},
"recipient": {
"agent_id": "executor-hermes-prod-03",
"framework": "hermes"
},
"intent": "task.delegate",
"payload": {
"task": "summarize_and_file",
"inputs": { "url": "https://example.com/report.pdf" },
"constraints": { "max_tokens": 4000, "deadline_ms": 30000 }
},
"reply_to": "https://agents.rapidclaw.dev/a2a/planner-openclaw-prod-01/inbox",
"expires_at": "2026-04-18T12:34:56Z"
}Three fields do most of the work:
- correlation_id — ties every message in a multi-agent conversation into a single thread. Without it, your traces look like unrelated point-to-point RPCs.
- trace — OpenTelemetry-compatible span context. Pass it through, and your existing APM stitches the whole fan-out automatically.
- intent — a verb like
task.delegate,result.return, orquery.ask. The intent, not a URL path, is what recipients dispatch on.
Publishing an OpenClaw Agent over A2A
An OpenClaw agent becomes an A2A peer by exposing an inbox endpoint and registering itself with the platform registry. The agent doesn’t need to know who will call it — only how to respond when someone does.
from fastapi import FastAPI, HTTPException, Header
from openclaw import Agent, Task
from a2a import Envelope, verify_signature, sign
import os, uuid, datetime as dt
app = FastAPI()
planner = Agent.from_config("planner.yaml") # your OpenClaw agent
AGENT_ID = "planner-openclaw-prod-01"
PRIVATE_KEY = os.environ["A2A_PRIVATE_KEY"]
TRUSTED_SIGNERS = os.environ["A2A_TRUSTED_SIGNERS"].split(",")
@app.post("/a2a/inbox")
async def inbox(envelope: Envelope, authorization: str | None = Header(None)):
# 1. Verify the sender's signature against the registry's trusted set
if not verify_signature(envelope, allowed=TRUSTED_SIGNERS):
raise HTTPException(401, "a2a signature verification failed")
# 2. Dispatch on intent — not on URL path
if envelope.intent == "task.delegate":
task = Task(
name=envelope.payload["task"],
inputs=envelope.payload["inputs"],
trace=envelope.trace, # propagate the OTEL span
)
result = await planner.run(task)
# 3. Build the reply envelope
reply = Envelope(
a2a_version="1.0",
message_id=f"msg_{uuid.uuid4().hex}",
correlation_id=envelope.correlation_id,
trace=envelope.trace,
sender={"agent_id": AGENT_ID, "framework": "openclaw"},
recipient=envelope.sender,
intent="result.return",
payload={"status": "ok", "result": result.to_dict()},
expires_at=(dt.datetime.utcnow() + dt.timedelta(minutes=5)).isoformat() + "Z",
)
return sign(reply, PRIVATE_KEY).dict()
raise HTTPException(400, f"unsupported intent: {envelope.intent}")The hosting platform is what turns this from a single process into a discoverable agent. Registration is one call at startup:
import httpx, os
REGISTRY = os.environ["A2A_REGISTRY_URL"] # e.g. https://registry.rapidclaw.dev
async def register_on_startup():
async with httpx.AsyncClient() as client:
r = await client.post(
f"{REGISTRY}/v1/agents",
headers={"Authorization": f"Bearer {os.environ['RAPIDCLAW_TOKEN']}"},
json={
"agent_id": "planner-openclaw-prod-01",
"framework": "openclaw",
"endpoint": "https://planner.internal.mycorp.com/a2a/inbox",
"intents": ["task.delegate"],
"description": "plans multi-step research tasks",
"labels": {"env": "prod", "team": "research"},
"public_key": os.environ["A2A_PUBLIC_KEY"],
},
)
r.raise_for_status()Calling a Hermes Agent from OpenClaw
The planner discovers the executor by label, not by URL. This is the part A2A gets right — the caller never hardcodes a hostname.
from a2a import Envelope, sign, lookup
import httpx, os, uuid, datetime as dt
async def delegate_to_hermes(task_name: str, inputs: dict, trace: dict) -> dict:
# 1. Resolve the executor via the registry — by intent + labels, not URL
executor = await lookup(
intent="task.execute",
labels={"framework": "hermes", "env": "prod"},
)
# 2. Build an A2A envelope
env = Envelope(
a2a_version="1.0",
message_id=f"msg_{uuid.uuid4().hex}",
correlation_id=trace["correlation_id"],
trace=trace,
sender={"agent_id": "planner-openclaw-prod-01", "framework": "openclaw"},
recipient={"agent_id": executor.agent_id, "framework": "hermes"},
intent="task.execute",
payload={"task": task_name, "inputs": inputs},
reply_to=os.environ["A2A_INBOX_URL"],
expires_at=(dt.datetime.utcnow() + dt.timedelta(seconds=30)).isoformat() + "Z",
)
signed = sign(env, os.environ["A2A_PRIVATE_KEY"])
# 3. POST with mTLS — the platform provisions client certs per agent
async with httpx.AsyncClient(
verify=os.environ["A2A_CA_BUNDLE"],
cert=(os.environ["A2A_CLIENT_CERT"], os.environ["A2A_CLIENT_KEY"]),
timeout=30.0,
) as client:
r = await client.post(executor.endpoint, json=signed.dict())
r.raise_for_status()
return r.json()["payload"]On the Hermes side, the handler looks similar. Hermes’ agent loop already speaks JSON tool calls; A2A is just a structured HTTP wrapper around that surface.
from hermes_agent import Agent, ToolContext
from a2a import Envelope, verify_signature, sign
from fastapi import FastAPI, HTTPException
import os, uuid, datetime as dt
app = FastAPI()
executor = Agent.from_yaml("executor.yaml") # Hermes agent config
AGENT_ID = "executor-hermes-prod-03"
@app.post("/a2a/inbox")
async def handle(env: Envelope):
if not verify_signature(env, allowed=os.environ["A2A_TRUSTED_SIGNERS"].split(",")):
raise HTTPException(401, "signature failed")
if env.intent != "task.execute":
raise HTTPException(400, f"unsupported intent: {env.intent}")
# Hermes's agent loop — returns a typed result
ctx = ToolContext(trace=env.trace, correlation_id=env.correlation_id)
result = await executor.execute(
task=env.payload["task"],
inputs=env.payload["inputs"],
context=ctx,
)
reply = Envelope(
a2a_version="1.0",
message_id=f"msg_{uuid.uuid4().hex}",
correlation_id=env.correlation_id,
trace=env.trace,
sender={"agent_id": AGENT_ID, "framework": "hermes"},
recipient=env.sender,
intent="result.return",
payload={"status": "ok", "result": result.dict()},
expires_at=(dt.datetime.utcnow() + dt.timedelta(minutes=5)).isoformat() + "Z",
)
return sign(reply, os.environ["A2A_PRIVATE_KEY"]).dict()What the Hosting Platform Actually Does
The protocol is the easy part. The operational surface around it is what teams underestimate. A production A2A deployment needs five things, all of which belong at the platform layer — not inside each agent:
Registry & discovery
Agents register their agent_id, framework, supported intents, labels, public key, and endpoint. Callers resolve peers by intent + labels — not by URL. This is the single feature that makes the fleet relocatable.
Identity & mTLS
Each agent gets a signed identity and a client certificate. The platform rotates them. Every A2A hop is mutually authenticated — no bearer tokens floating through prompts.
Routing & network policy
Default-deny egress at the agent level, allow-list only the A2A peers each agent is permitted to talk to. The registry becomes the source of truth for the policy.
Observability
Because every envelope carries OpenTelemetry trace context, the platform stitches multi-agent conversations into a single trace view. You can see "planner → executor → critic → back to planner" as one flame graph.
Rate limits & quotas
Per-agent, per-intent, per-caller rate limits enforced at the edge. Stops a runaway planner from fan-outing 10,000 sub-tasks to an executor fleet in a retry loop.
The managed shortcut
You can build all five pillars yourself — a Postgres registry, Vault for keys, Envoy for mTLS, OpenTelemetry collector, Redis for rate limits. It’s maybe four weeks of solid work plus ongoing maintenance. Rapid Claw ships all of them preconfigured for OpenClaw and Hermes agents out of the box, so you deploy an agent and get its A2A endpoint, signed identity, and trace view on the same day.
A Deployable A2A Topology
A minimal production-grade topology for a mixed OpenClaw + Hermes fleet. This is the skeleton — scale each tier horizontally as your traffic grows.
version: "3.8"
services:
# --- Platform layer ---
a2a-registry:
image: rapidclaw/a2a-registry:1.0
environment:
- POSTGRES_URL=postgres://a2a:a2a@pg:5432/a2a
- ISSUER=https://registry.internal.mycorp.com
ports:
- "8443:8443"
networks: [a2a-plane]
otel-collector:
image: otel/opentelemetry-collector:latest
volumes:
- ./otel-config.yaml:/etc/otel/config.yaml:ro
networks: [a2a-plane]
# --- Agent layer ---
planner-openclaw:
image: openclaw/agent:latest
environment:
- A2A_REGISTRY_URL=https://a2a-registry:8443
- A2A_INBOX_URL=https://planner-openclaw:9000/a2a/inbox
- A2A_CA_BUNDLE=/certs/ca.crt
- A2A_CLIENT_CERT=/certs/planner.crt
- A2A_CLIENT_KEY=/certs/planner.key
volumes:
- ./certs/planner:/certs:ro
networks: [a2a-plane]
executor-hermes:
image: nousresearch/hermes-agent:latest
environment:
- A2A_REGISTRY_URL=https://a2a-registry:8443
- A2A_INBOX_URL=https://executor-hermes:9000/a2a/inbox
- A2A_CLIENT_CERT=/certs/executor.crt
- A2A_CLIENT_KEY=/certs/executor.key
volumes:
- ./certs/executor:/certs:ro
networks: [a2a-plane]
critic-openclaw:
image: openclaw/agent:latest
environment:
- A2A_REGISTRY_URL=https://a2a-registry:8443
- A2A_INBOX_URL=https://critic-openclaw:9000/a2a/inbox
volumes:
- ./certs/critic:/certs:ro
networks: [a2a-plane]
networks:
a2a-plane:
driver: bridge
# no egress to the public internet — A2A stays on the planeThe a2a-plane network is the key detail. Every agent sits on it, every A2A hop stays on it, and nothing on it can reach the public internet by default. External model APIs (Anthropic, OpenAI) reach agents through a separate egress proxy, not through A2A.
Three A2A Patterns Worth Knowing
1. Request/reply
The simplest case: planner calls executor, waits for the reply envelope, acts on it. Use for sub-tasks with a clear deadline. Covered by the code above.
2. Fan-out / fan-in
Planner dispatches the same intent to a pool of executors in parallel, correlates replies by correlation_id, and either takes the first good answer or aggregates. This is how you run research-agent ensembles — one planner, five executors, quorum on the output.
3. Async task with callback
For long-running work, the caller fires a task.delegate with a reply_to inbox URL and returns immediately. The callee does its work and POSTs a result.return envelope back when it’s done. You get durability without holding an HTTP connection open for 20 minutes.
Where A2A Meets the Rest of Your Stack
A2A rarely lives alone. Three adjacent concerns you’ll want to plan for at the same time:
- Orchestration shape. A2A is the transport — the question of who plans, who executes, and who critiques is a design choice. See multi-agent orchestration patterns for the common topologies.
- Security posture. Every A2A edge is a trust boundary — signature verification, rate limits, and scoped keys belong on every hop. Our AI agent security best practices guide covers the controls you’ll want around this.
- Enterprise rollout. Once A2A is in place, the conversation shifts to SSO, audit logs, data residency, and change management — see enterprise AI agents deployment for how those pieces fit together.
Frequently Asked Questions
Skip the platform build
Rapid Claw deploys OpenClaw and Hermes agents with A2A routing, identity, mTLS, and unified tracing preconfigured. Register an agent — get an addressable A2A endpoint on the same call.
Deploy with A2A includedRelated reading
Planner/executor, supervisor, blackboard, and when to use each
AI Agent Security Best PracticesThreat model and controls for production agent fleets
Enterprise AI Agents DeploymentSSO, audit, residency, and scaling agents in the enterprise
AI Agent Firewall SetupRate limiting, scoped keys, network isolation, and permissions