# How to Implement the A2A Protocol for Agent-to-Agent Communication in Agent-Native

> Learn to implement the A2A protocol for agent-to-agent communication in Agent-Native. Use mountA2A and callAgent() to enable seamless inter-app actions.

- Repository: [Builder.io/agent-native](https://github.com/BuilderIO/agent-native)
- Tags: how-to-guide
- Published: 2026-06-27

---

**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](https://github.com/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`](https://github.com/BuilderIO/agent-native/blob/main/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.

```typescript
// 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`](https://github.com/BuilderIO/agent-native/blob/main/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`](https://github.com/BuilderIO/agent-native/blob/main/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`](https://github.com/BuilderIO/agent-native/blob/main//.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`](https://github.com/BuilderIO/agent-native/blob/main/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.

```typescript
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`](https://github.com/BuilderIO/agent-native/blob/main/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.

```typescript
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 a `working` task immediately.
- **`getTask(taskId)`** – Fetch current status via the `tasks/get` endpoint.
- **`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:

1. The caller sends `message/send` with `async:true`.
2. The server immediately returns a task in `working` state.
3. The handler **self-fires** a POST to `/_agent-native/a2a/_process-task`, running the agent loop in a fresh function execution with extended timeout.
4. The caller polls `tasks/get` or 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`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/integrations/a2a-continuation-processor.ts).

## Summary

- **Mount the endpoint** using `mountA2A` in a Nitro server plugin to expose [`/.well-known/agent-card.json`](https://github.com/BuilderIO/agent-native/blob/main//.well-known/agent-card.json) and the JSON-RPC handler.
- **Secure communication** by setting `A2A_SECRET` for JWT signing between Agent-Native apps, or use `apiKeyEnv` for 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 or `A2AClient` for 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`](https://github.com/BuilderIO/agent-native/blob/main/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`).