How to Implement the A2A Protocol for Agent-to-Agent Communication in Agent-Native
The A2A protocol is a JSON-RPC-based bridge built into Agent-Native that enables one app to call actions in another via the mountA2A server plugin and the callAgent() helper or A2AClient class.
The A2A (agent-to-agent) protocol is natively integrated into the BuilderIO/agent-native framework, allowing seamless communication between distributed Agent-Native applications. This implementation handles service discovery, authentication, async execution, and streaming updates without requiring external infrastructure. You can expose your app's capabilities to other agents or consume remote skills using high-level helpers or low-level client classes.
Mounting the A2A Endpoint Server-Side
To expose your app to other agents, you must register the A2A routes using a Nitro server plugin.
Creating the Nitro Plugin
Create a plugin file (e.g., plugins/a2a.ts) and import mountA2A from the core package. This function registers the discovery endpoint, the primary JSON-RPC handler, and the internal async worker route.
// plugins/a2a.ts
import { mountA2A } from "@agent-native/core/a2a";
export default defineNitroPlugin((nitro) => {
mountA2A(nitro, {
name: "Analytics Agent",
description: "Runs analytics queries",
skills: [
{
id: "run-query",
name: "Run Query",
description: "Execute a SQL query",
tags: ["analytics", "sql"],
examples: ["Show me sign-ups by source this month"],
},
],
apiKeyEnv: "A2A_API_KEY", // optional legacy support
streaming: true, // enable SSE streaming
});
});
Auto-Generated Routes
The mountA2A function in packages/core/src/a2a/server.ts automatically registers three critical routes:
GET /.well-known/agent-card.json– Serves public discovery metadata describing your agent's capabilities.POST /_agent-native/a2a– The primary JSON-RPC endpoint for incoming agent requests.POST /_agent-native/a2a/_process-task– Internal async worker endpoint used for self-firing continuation tasks.
In multi-app workspaces (apps/<id>/), the framework mounts these endpoints for each sibling app automatically, sharing a global A2A_SECRET across the deployment.
Configuring Authentication
The A2A protocol enforces authentication before any agent code executes, preventing prompt-injection attacks.
Production JWT Signing
Set the A2A_SECRET environment variable in every participating app. The framework automatically signs outgoing calls with a JWT whose sub claim identifies the caller's email. The auth policy implemented in packages/core/src/a2a/auth-policy.ts validates these tokens on incoming requests.
Legacy API Key Support
For external peers that do not support JWT signing, provide a static bearer token via the apiKeyEnv option (e.g., A2A_API_KEY). This fallback is validated against the Authorization: Bearer <token> header.
Discovering Remote Agents
Every Agent-Native app exposes a public agent card at /.well-known/agent-card.json. This JSON document contains the app's base URL, available skills, and endpoint paths. The A2AClient class automatically fetches this file during initialization to determine whether to use /_agent-native/a2a or the standard /a2a path.
Calling Remote Agents
Agent-Native provides two idiomatic patterns for invoking remote agents, both implemented in packages/core/src/a2a/client.ts.
Using the callAgent Helper
For simple text-in/text-out scenarios, use the callAgent() function. This helper handles JWT signing, async mode initiation, and polling automatically.
import { callAgent } from "@agent-native/core/a2a";
const reply = await callAgent(
"https://analytics.example.com", // remote app URL
"How many sign-ups last week?", // message text
{
apiKey: process.env.ANALYTICS_API_KEY, // optional static key
userEmail: "steve@example.com", // signed JWT automatically
async: true, // async + poll (default)
timeoutMs: 5 * 60_000, // 5 min timeout
},
);
console.log("Analytics reply →", reply);
The callAgent implementation (lines 66-88 in client.ts) automatically polls tasks/get until the task reaches a terminal state (completed, failed, or canceled).
Using the A2AClient Class
For streaming, custom metadata, or explicit control over the polling interval, instantiate the A2AClient class directly.
import { A2AClient } from "@agent-native/core/a2a";
const client = new A2AClient("https://analytics.example.com");
// Resolve the correct endpoint (auto-detects /_agent-native/a2a vs /a2a)
await client.resolveEndpoint();
// Send a message and wait for completion
const task = await client.sendAndWait(
{
role: "user",
parts: [{ type: "text", text: "Show sign-ups by source this month" }],
},
{
async: true,
timeoutMs: 5 * 60_000,
pollIntervalMs: 2_000,
metadata: { userEmail: "steve@example.com" },
},
);
console.log("Task finished with state:", task.status.state);
Key methods available in A2AClient:
send(message, { async: true })– Fire-and-forget that returns aworkingtask immediately.getTask(taskId)– Fetch current status via thetasks/getendpoint.sendAndWait(...)– Convenience wrapper that polls until terminal state.stream(message)– Returns an SSE stream for real-time partial updates.
Handling Async Tasks on Serverless Platforms
On serverless hosts (Netlify, Vercel, Cloudflare), the primary HTTP request must return quickly to avoid timeout errors. The A2A protocol implements a continuation pattern:
- The caller sends
message/sendwithasync:true. - The server immediately returns a task in
workingstate. - The handler self-fires a POST to
/_agent-native/a2a/_process-task, running the agent loop in a fresh function execution with extended timeout. - The caller polls
tasks/getor receives SSE updates until completion.
If the self-fired function crashes, a periodic sweeper re-claims the task to ensure work is never lost. This continuation logic is handled by packages/core/src/integrations/a2a-continuation-processor.ts.
Summary
- Mount the endpoint using
mountA2Ain a Nitro server plugin to expose/.well-known/agent-card.jsonand the JSON-RPC handler. - Secure communication by setting
A2A_SECRETfor JWT signing between Agent-Native apps, or useapiKeyEnvfor legacy static tokens. - Discover capabilities automatically via the agent card served at the well-known endpoint.
- Choose your client pattern: Use
callAgent()for simple requests orA2AClientfor streaming and fine-grained control. - Handle serverless constraints by leveraging the built-in async continuation pattern that self-fires processing tasks and polls for completion.
Frequently Asked Questions
What is the A2A protocol in Agent-Native?
The A2A protocol is a JSON-RPC-based communication standard built into the Agent-Native framework that allows one application to discover and invoke actions in another. It handles authentication, skill discovery via agent cards, and supports both synchronous and asynchronous execution patterns.
How do I secure A2A communication between apps?
Set the A2A_SECRET environment variable in all participating applications. The framework automatically signs outgoing requests with JWTs containing the caller's identity, and the auth policy in packages/core/src/a2a/auth-policy.ts validates these tokens before any agent logic executes. For external systems, provide a static API key via the apiKeyEnv configuration option.
What is the difference between callAgent and A2AClient?
callAgent() is a high-level helper function that wraps the entire request lifecycle—signing, sending, and polling—into a single promise, ideal for simple text-based interactions. A2AClient provides low-level control over the connection, supporting custom polling intervals, metadata injection, and Server-Sent Events (SSE) streaming for real-time updates.
How does A2A handle long-running tasks on serverless platforms?
When async: true is specified, the server immediately returns a task ID and self-fires a POST to /_agent-native/a2a/_process-task to execute the work in a separate function invocation. This bypasses serverless timeout limits. The client polls tasks/get or listens to SSE streams until the task reports a terminal state (completed, failed, or canceled).
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →