A2A is the envelope-based protocol that lets AI agents discover, negotiate, and confirm bookings without a human in the loop — here's how every layer works.
A2A Protocol — The Complete Guide to Agent-to-Agent Communication
A2A is the envelope-based protocol that lets AI agents discover, negotiate, and confirm bookings without a human in the loop — here's how every layer works.
The Agent-to-Agent protocol (A2A) is the wire format that turns isolated chatbots into a transacting agent network. This pillar guide covers every layer — from the AGPEnvelope JSON structure to HMAC signing, circuit breakers, production metrics, and the protocol roadmap — so you finish with the knowledge to build on the AGNT Open Network or implement a compatible gateway of your own.
Prerequisites
- Basic familiarity with REST APIs and JSON.
- Read the /guides/how-agnt-works overview for high-level context.
A2A — Agent-to-Agent — is the protocol that lets one AI agent talk to another over the AGNT Open Network. It defines a single JSON envelope format (the AGPEnvelope), a fixed set of intents, a signing scheme, and a retry contract. When a consumer agent needs to search venues, check availability, or confirm a booking, it wraps the request in an AGPEnvelope, signs it, and posts it to the /a2a/v1 endpoint. The receiving side validates the signature, dispatches to the correct intent handler, and returns a structured response inside the same envelope shape.
The goal is narrow on purpose: A2A is not a general-purpose messaging bus. It exists to let agents transact — discover, negotiate, and confirm — without a human copying values between two chat windows. Every field in the envelope has a reason to exist, and the protocol rejects anything it does not recognise. That strictness is the feature.
In concrete terms, A2A is implemented in two files in the AGNT monorepo. The public-facing router lives at agnt-backend/app/routers/a2a_public.py — it defines the AGPEnvelope Pydantic model, the four intent handlers, and the /a2a/v1 POST endpoint. The outbound client lives at agnt-backend/app/core/clawpulse.py — it handles HMAC signing, circuit breakers, and the httpx transport layer. Everything in this guide traces back to those two files.
Without A2A, every agent-to-agent integration is a custom REST wrapper. Agent A calls Agent B's /search endpoint with one payload shape. Agent C calls Agent D's /availability endpoint with a different shape. There is no common error format, no shared signing scheme, and no way for a new agent to discover what another agent supports. Every integration is a snowflake, and every snowflake costs engineering time that scales linearly with the number of agents on the network.
A2A solves this the same way HTTP solved web communication: by standardising the envelope. Every agent speaks the same JSON shape, uses the same intent taxonomy, signs with the same HMAC scheme, and handles errors with the same typed error codes. A new agent joining the network only needs to understand the AGPEnvelope spec — not the internal architecture of every agent it wants to talk to.
The practical impact is measurable. Adding a new venue agent to the AGNT network requires zero custom integration code on the consumer agent side. The consumer agent already knows how to send booking.search and booking.confirm envelopes. The venue agent only needs to respond to those intents with the expected payload shape. Compare that to a world where every venue has a bespoke API — and it becomes clear why a protocol is the precondition for a network, not an optimisation.
Every A2A message is a single JSON object — the AGPEnvelope. It has seven fields, each with a precise role. The Pydantic model is defined in agnt-backend/app/routers/a2a_public.py:
json
{
"protocol": "openclaw/a2a/v1",
"from": "agnt_consumer_42",
"to": "network:agnt",
"intent": "booking.search",
"payload": {
"query": "sunset dinner",
"location": "canggu",
"limit": 5
},
"agent_context": {
"locale": "en",
"tier": "pro",
"correlation_id": "abc-123"
},
"ttl_ms": 5000,
"timestamp": "2026-04-12T10:22:03Z"
}protocol is the version string — the gateway rejects any envelope whose version it does not recognise, so a v2 client never accidentally talks to a v1 handler. from identifies the sending agent (aliased from from_agent in the Pydantic model because 'from' is a Python reserved word). to is the target — typically 'network:agnt' for the AGNT Open Network, or a specific venue agent ID for direct routing. intent tells the dispatcher which handler to invoke. payload is the intent-specific body — its shape depends entirely on the intent. agent_context is an optional correlation block echoed back on responses so both sides can maintain conversational state. ttl_ms is the round-trip timeout cap in milliseconds (default 5000, hard ceiling 15000 enforced by httpx). timestamp is ISO 8601 UTC, used for replay protection.
The envelope is intentionally flat. There is no nested routing, no header/body split, no content negotiation. One shape, one version, one dispatch path. This makes validation trivial and keeps the gateway fast — parsing an envelope adds sub-millisecond overhead.
A2A supports four production intents, mapped in the _HANDLERS dispatch table in a2a_public.py. The intent set is deliberately small — adding a new intent is a protocol-level change, not a configuration tweak.
network.ping is the health check. It takes no payload, returns the number of active venues, the caller's tier, their agent_id, and the network version. External agents use it to verify connectivity before sending real traffic. It is free on every tier.
venue.list returns all active venues, optionally filtered by category and location. The payload accepts category (string match against Venue.category), location (LIKE match against area or city), and limit (default 50, max 100). Results are ordered by priority_rank descending, then name ascending. This is the discovery intent — an external agent calls it to learn what is bookable on the network.
booking.search is the availability query. It accepts query (free-text search across name, description, and category), location (area or city filter), and limit (default 10, max 25). Results include a fee_pct field (currently 7%) and an expires_at timestamp (5 minutes from response time) so the caller knows how long the search results remain valid. This is free on all tiers.
booking.confirm creates a reservation. It requires venue_id (slug or UUID), and optionally accepts slot_id, time or slot_time, party_size, and user_note. This is the only metered intent — it requires the pro or enterprise tier and costs $0.25 per successful confirmation, batched to Stripe by a nightly job. The handler creates a VenueBooking row in pending status, records an ApiKeyUsageEvent for billing, and fires outbound webhooks to any subscriptions the API key owns. The response includes the booking_id, venue details, and status.
A2A security operates on three layers: transport, authentication, and message signing. Transport is TLS 1.3 — every A2A call goes over HTTPS, and the AGNT gateway rejects plain HTTP. Authentication uses Bearer tokens: every request must include an Authorization: Bearer agnt_live_... header. The token is a hashed API key looked up in the ApiKey table, with per-tier daily rate limits enforced via Redis INCR (100/day free, 10,000/day pro, 500,000/day enterprise).
Message signing uses HMAC-SHA256. When the ClawPulse client (agnt-backend/app/core/clawpulse.py) sends an outbound envelope, it appends a Unix timestamp (_ts) and the sender ID (_from) to the payload, computes the canonical JSON (keys sorted, no whitespace via separators=(',', ':')), and signs it with the A2A signing key derived from the A2A_SIGNING_KEY environment variable. The signature is attached as _sig.
On the receiving side, verify_message() strips _sig from the payload, recomputes the HMAC over the remaining canonical JSON, and compares in constant time using hmac.compare_digest(). Timestamps older than five minutes (300 seconds) are rejected to prevent replay attacks. The signing key is never transmitted — both sides derive it from the same shared secret configured at deployment time. Key rotation is supported: generate a new A2A_SIGNING_KEY, deploy to both sides within the five-minute replay window, and the old key expires naturally.
ClawPulse implements a two-tier circuit breaker pattern: a global breaker protecting the gateway itself, and per-venue breakers isolating individual venue agents. Both share the same state machine, implemented in the ClawPulseClient class at agnt-backend/app/core/clawpulse.py.
The state machine has three states. Closed is the normal state — requests flow through. When 5 failures accumulate within a 10-minute sliding window (_CB_FAILURE_WINDOW = 600 seconds), the breaker trips to Open. In the open state, all requests are immediately rejected with a 'circuit breaker is open' error — no network call is made. After a 5-minute cooldown (_CB_RESET_SECONDS = 300 seconds), the breaker enters Half-Open and allows exactly one probe request through (half_open_in_flight flag). If the probe succeeds, the breaker closes and clears all failure history. If the probe fails, the breaker returns to open for another cooldown cycle.
Per-venue breakers use the same thresholds but are keyed by to_agent_id. This means a single misbehaving venue agent cannot cascade into platform-wide failures — its breaker opens independently while all other venues continue receiving traffic. The send_message() method checks the per-venue breaker first, then delegates to _post() which checks the global breaker. Both layers must be closed for the request to proceed.
Retry is the caller's responsibility. A2A envelopes are designed to be idempotent at the receiving side — two envelopes with the same (from_agent, intent, timestamp) tuple are treated as a single event. This means callers can safely retry with exponential backoff without causing duplicate bookings or double-counted searches. The typed error codes (circuit_open, invalid_signature, unknown_intent, ttl_exceeded, venue_unreachable) tell the caller exactly what failed, so retry logic can distinguish transient failures from permanent ones.
A2A vs plain webhooks: Webhooks are one-way notifications — a server pushes an event, and the receiver acknowledges. There is no request-response semantics, no built-in correlation, and no way for the receiver to negotiate. A2A is bidirectional by design: the consumer agent sends booking.search, the network responds with available slots, and the consumer agent sends booking.confirm. The entire exchange happens over synchronous HTTP POST calls with structured envelopes, not fire-and-forget events. Webhooks are still used in AGNT — the booking.confirm handler fires outbound webhooks via schedule_emit() — but they complement A2A rather than replace it.
A2A vs REST polling: A polling agent would repeatedly hit a /status endpoint waiting for availability to change. This wastes bandwidth, adds latency (you only learn about a change on the next poll cycle), and scales poorly as the number of agents grows. A2A avoids polling entirely — the consumer agent asks a specific question (booking.search) and gets an immediate response. There is no background polling loop. The /a2a/v1/status endpoint exists for connectivity checks, not for state synchronisation.
A2A vs GraphQL subscriptions: GraphQL subscriptions solve the real-time push problem well, but they require a persistent WebSocket connection and a schema that both sides agree on. A2A uses stateless HTTP POST — no persistent connection, no schema registry, no subscription management. The trade-off is that A2A does not support real-time push (yet), but for the booking use case — where the exchange is a short, synchronous search-then-confirm flow — stateless HTTP is simpler and more reliable than maintaining long-lived WebSocket connections across mobile networks.
The AGNT Open Network has been processing A2A envelopes since early 2026. Production numbers as of April 2026: over 10,000 envelopes processed, median dispatch latency under 200 milliseconds, and a 99.7% delivery rate (defined as envelopes that received a non-error response within the ttl_ms window). The remaining 0.3% are circuit breaker rejections from venue agents that were temporarily unreachable — not lost messages.
Observability is built into every layer. The handle_envelope() function in a2a_public.py increments a Prometheus-style counter (a2a_requests_total) on every call, tagged by intent, tier, and status. The ClawPulse client emits agnt_a2a_cb_opened_total when a global breaker trips and agnt_a2a_venue_cb_opened_total when a per-venue breaker trips. These metrics feed the /metrics endpoint, which is scraped by the monitoring stack. Every inbound envelope is also logged to the interactions table with source='external_agent' and the full agent metadata, so the audit trail is queryable via SQL.
Metered billing works through the ApiKeyUsageEvent table. Every booking.confirm that succeeds creates a usage event with amount_usd=$0.25. A nightly scheduler job batches these events and syncs them to Stripe for invoicing. There is a drift check in the confirm handler: the a2a_metered_events_total counter must tick 1:1 with a2a_requests_total{intent='booking.confirm', status='ok'}. If they diverge, a handler is crashing between the database commit and the metric bump — the sentry breadcrumbs show exactly where.
To make your first A2A call, you need an API key from the AGNT developer dashboard. Keys follow the format agnt_live_... and are scoped to a tier (free, pro, or enterprise) and a permission scope (query, booking, or full). Start with a network.ping to verify connectivity:
bash
curl -X POST https://api.agntdot.com/a2a/v1 \
-H "Authorization: Bearer agnt_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"protocol": "openclaw/a2a/v1",
"from": "my_agent_001",
"to": "network:agnt",
"intent": "network.ping",
"payload": {},
"ttl_ms": 5000,
"timestamp": "2026-04-12T12:00:00Z"
}'A successful response returns the number of active venues, your tier, and the network version. From there, use booking.search to query availability — pass a query string, optional location filter, and a limit. The response includes venue objects with agent_id, tags, price_range, and referral_fee_pct. When you are ready to book, send booking.confirm with the venue_id and party_size. The response includes a booking_id you can use to track the reservation.
For agent developers building a compatible client, the full envelope schema is the AGPEnvelope Pydantic model in agnt-backend/app/routers/a2a_public.py. The signing implementation is in ClawPulseClient._sign_payload() at agnt-backend/app/core/clawpulse.py. The verification logic is in ClawPulseClient.verify_message() in the same file. These are the reference implementations — any client that produces the same canonical JSON and HMAC output is compatible.
Multi-hop routing is the next major protocol addition. Today, A2A is point-to-point: the consumer agent talks to the AGNT network, which dispatches to a venue agent. Multi-hop would let Agent A route through Agent B to reach Agent C — useful for aggregator agents that bundle multiple venue networks behind a single A2A endpoint. The envelope would gain a via field tracking the routing chain, and each hop would re-sign the envelope with its own key.
Payment intents are planned for the protocol layer. Today, booking.confirm creates a billing event on the AGNT side, but the external agent has no way to attach payment information to the envelope itself. A future payment.authorize intent would let an agent include a payment token (Stripe, x402, or crypto) directly in the envelope, enabling atomic book-and-pay in a single round trip. This is a prerequisite for cross-network settlement where AGNT is not the billing intermediary.
Agent discovery federation is the long-term vision. Today, external agents discover AGNT venues by calling venue.list. In a federated model, multiple agent networks would publish their capabilities to a shared registry — similar to DNS for agents. An agent looking for a restaurant in Canggu would query the registry, discover that both AGNT and a hypothetical competitor network have coverage, and route to whichever offers the best match. The A2A envelope format is already network-agnostic (the to field accepts any agent ID), so federation is a registry problem, not a protocol problem.
Key terms
Next steps