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

> Learn how to implement the A2A protocol for agent-to-agent communication in OmniRoute. Discover its JSON-RPC 2.0 specs for autonomous service interaction via task lifecycle, skill dispatch, and SSE streaming.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: how-to-guide
- Published: 2026-07-27

---

**OmniRoute provides a built-in A2A (Agent-to-Agent) server that implements the JSON-RPC 2.0 specification, enabling autonomous services to communicate through a standardized task lifecycle, skill dispatch system, and SSE streaming interface.**

OmniRoute ships with a complete A2A protocol implementation that allows agents to invoke skills and exchange data via JSON-RPC endpoints. To implement A2A protocol for agent-to-agent communication in OmniRoute, you configure the environment, understand the five-layer architecture, and interact with the built-in skill registry through HTTP or CLI interfaces.

## Understanding the A2A Architecture

The A2A stack in OmniRoute consists of five distinct layers that handle everything from HTTP ingress to skill execution:

| Layer | Responsibility | Source File |
|-------|----------------|-------------|
| **Task lifecycle** | Creation, state transitions, and TTL cleanup | [`src/lib/a2a/taskManager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskManager.ts) |
| **Skill dispatch** | Mapping skill names to handlers and transaction management | [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts) |
| **SSE streaming** | Formatting streaming responses for real-time updates | [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts) |
| **HTTP entry point** | Next.js route for JSON-RPC ingestion and validation | [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts) |
| **Skill registration** | Exposing built-in capabilities to the A2A namespace | [`src/lib/skills/a2a.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/skills/a2a.ts) |

## Core Implementation Components

### Task Lifecycle Management

The `A2ATaskManager` class in [`src/lib/a2a/taskManager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskManager.ts) orchestrates the complete lifecycle of an agent task. When a client sends a `message/send` request, the manager creates a task with a UUID v4 identifier, stores it in-memory (with optional SQLite persistence), and enforces state transitions through the `VALID_TRANSITIONS` map.

Valid states follow a strict flow: **submitted → working → completed** (or **failed** / **cancelled**). The manager automatically expires tasks after a configurable TTL (defaulting to five minutes) and tracks active SSE streams through `beginStream` and `endStream` methods for concurrency metrics.

### Skill Dispatch and Execution

Skill routing occurs in [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts) through the `executeA2ATaskWithState` function. This wrapper uses the `A2A_SKILL_HANDLERS` object to lazily import skill implementations on first use, optimizing cold start performance.

When executing a skill, the wrapper runs the handler inside a transaction, then updates the task state to **completed** (attaching artifacts) or **failed** (attaching error artifacts). Errors are caught, logged, and re-thrown so the HTTP layer can return proper JSON-RPC error responses.

### SSE Streaming Infrastructure

For real-time communication, [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts) exports `createA2AStream`, which formats Server-Sent Events (SSE) for the `message/stream` RPC method. The stream formats task artifacts into `data:` blocks and `event:` names, maintaining a connection to the task manager's `beginStream` / `endStream` hooks to track `activeStreams` count.

### HTTP Route Handler

The HTTP entry point resides in [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts), implemented as a Next.js API route. This handler:

- Parses incoming JSON-RPC 2.0 payloads
- Validates the optional `REQUIRE_API_KEY` guard
- Routes methods (`message/send`, `message/stream`, `tasks/get`, `tasks/cancel`) to the `A2ATaskManager`
- Serializes responses while preserving the request `id` field

When processing `message/send`, the route creates a task via `createTask`, immediately transitions it to *working*, executes the corresponding skill via `executeA2ATaskWithState`, and returns the final state.

## Built-in A2A Skills

OmniRoute registers six built-in skills in [`src/lib/skills/a2a.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/skills/a2a.ts), with individual implementations located in `src/lib/a2a/skills/`. Each skill exports an async function receiving an `A2ATask` and returning:

```typescript
{
  artifacts: TaskArtifact[];
  metadata: Record<string, unknown>;
}

```

The default skill set includes:

- **smart-routing** – Selects optimal provider combinations for requests
- **quota-management** – Reports remaining quota and usage statistics per provider
- **provider-discovery** – Lists available providers and their models
- **cost-analysis** – Estimates monetary costs for specific requests
- **health-report** – Returns health metrics for the router and downstream services
- **list-capabilities** – Generates an HTML artifact enumerating all available A2A skills

## Enabling and Configuring the A2A Server

The A2A endpoint is disabled by default. To activate the server, set the environment variable in your `.env` file:

```bash
A2A_ENABLED=true

```

When enabled, the `/a2a` tab appears in the OmniRoute dashboard (declared in [`src/shared/constants/endpointCategories.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/constants/endpointCategories.ts)), and the CLI command `omniroute a2a …` becomes available for interactive invocation (implemented in [`src/lib/agentSkills/openapiParser.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/agentSkills/openapiParser.ts)).

## Client Integration Examples

### Sending a Task via HTTP

Invoke a skill from a Node.js client using standard fetch:

```typescript
import fetch from 'node-fetch';

const endpoint = 'http://localhost:3000/a2a';
const payload = {
  jsonrpc: '2.0',
  id: '12345',
  method: 'message/send',
  params: {
    skill: 'provider-discovery',
    messages: [{ role: 'system', content: 'List all providers' }],
  },
};

const resp = await fetch(endpoint, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const result = await resp.json();
console.log(result);

```

### Streaming Responses with SSE

For long-running skills, use the `message/stream` method with EventSource:

```typescript
import EventSource from 'eventsource';

const url = new URL('http://localhost:3000/a2a');
url.searchParams.set('jsonrpc', '2.0');
url.searchParams.set('id', 'stream1');
url.searchParams.set('method', 'message/stream');
url.searchParams.set(
  'params',
  JSON.stringify({
    skill: 'smart-routing',
    messages: [{ role: 'user', content: 'Explain the best model for a 10k token prompt' }],
  })
);

const es = new EventSource(url.toString());
es.onmessage = ev => console.log('chunk →', ev.data);
es.onerror = err => console.error('stream error', err);

```

### CLI Usage

List available capabilities directly from the terminal:

```bash
omniroute a2a list-capabilities

```

## Summary

- OmniRoute implements the **JSON-RPC 2.0** specification for A2A communication through a dedicated Next.js route at [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts)
- The **task lifecycle** is managed by `A2ATaskManager` in [`src/lib/a2a/taskManager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskManager.ts), enforcing states and TTL cleanup
- Skills are dispatched through `executeA2ATaskWithState` in [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts) with lazy loading and transaction safety
- **SSE streaming** for real-time updates is handled by `createA2AStream` in [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts)
- Six **built-in skills** provide immediate capabilities for routing, quota management, and health monitoring
- Enable the server by setting `A2A_ENABLED=true` and interact via HTTP, SSE, or the `omniroute a2a` CLI

## Frequently Asked Questions

### What is the A2A protocol in OmniRoute?

The A2A (Agent-to-Agent) protocol in OmniRoute is a JSON-RPC 2.0 compliant communication standard that allows autonomous agents to invoke skills, manage task lifecycles, and stream results. It enables external services or internal components to interact with OmniRoute's routing and provider management capabilities through a standardized HTTP interface.

### How do I enable SSE streaming for A2A tasks?

SSE streaming is available by calling the `message/stream` method on the `/a2a` endpoint. The server uses `createA2AStream` from [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts) to format task artifacts as server-sent events. Clients should connect using an EventSource interface and handle `onmessage` events to receive real-time updates while the task state transitions from *working* to *completed*.

### What skills are available in the default A2A implementation?

OmniRoute provides six built-in A2A skills registered in [`src/lib/skills/a2a.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/skills/a2a.ts): **smart-routing** (provider selection), **quota-management** (usage tracking), **provider-discovery** (model listing), **cost-analysis** (price estimation), **health-report** (service metrics), and **list-capabilities** (skill enumeration). Each skill resides in `src/lib/a2a/skills/` and returns standardized artifacts.

### How does OmniRoute handle A2A task failures?

When a skill handler throws an error, `executeA2ATaskWithState` catches the exception, logs it, creates an error artifact, and transitions the task state to **failed**. The error is then re-thrown to the HTTP layer in [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts), which returns a properly formatted JSON-RPC error response containing the original request ID and error details.