# What Is the A2A Protocol? Understanding OmniRoute's Agent-to-Agent Communication System

> Discover the A2A protocol, OmniRoute's agent-to-agent communication system. Learn how it enables external agents to submit tasks with full lifecycle management through smart-routing, quota-management, and provider-discovery.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: deep-dive
- Published: 2026-08-13

---

**The A2A (Agent-to-Agent) protocol is a JSON-RPC 2.0-based communication framework in OmniRoute that lets external agents submit tasks through built-in skills like smart-routing, quota-management, and provider-discovery, with full lifecycle management from submission to completion.**

The **A2A protocol** gives OmniRoute a standardized way to handle requests from other AI agents or external systems. Built on `JSON-RPC 2.0`, it transforms complex provider interactions into simple, trackable tasks that any authorized client can initiate and monitor.

## How the A2A Protocol Works in OmniRoute

OmniRoute's implementation follows a strict **state machine**: tasks move from **Submitted → Working → Completed | Failed | Cancelled**. This design ensures predictable behavior whether you're calling the public HTTP endpoint or using the internal task manager directly.

### Core Components of the A2A Architecture

Three files define the protocol's backbone:

- **[`src/lib/a2a/taskManager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskManager.ts)** — The `A2ATaskManager` class stores tasks in an in-memory map with optional SQLite persistence and TTL-based cleanup
- **[`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts)** — Contains `A2A_SKILL_HANDLERS`, the registry mapping skill names to their implementations, plus `executeA2ATaskWithState()` for state-aware execution
- **[`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts)** — Provides `createA2AStream()` for formatting Server-Sent Events when clients request streaming updates

## Submitting Tasks to OmniRoute's A2A Endpoint

External agents interact with OmniRoute through the **`/a2a`** HTTP endpoint defined in [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts). The endpoint normalizes incoming requests to a canonical shape and routes them to the appropriate skill handler.

### HTTP API Example

```http
POST /a2a HTTP/1.1
Content-Type: application/json
Authorization: Bearer <api-key>

{
  "skill": "smart-routing",
  "messages": [
    { "role": "user", "content": "Explain the difference between HTTP and HTTPS." }
  ],
  "metadata": { "requestId": "abc123" }
}

```

The response includes the task ID and initial state. You can then poll for status or request streaming updates via the SSE endpoint.

### Available A2A Skills in OmniRoute

The protocol ships with six built-in skills, as cataloged in [`src/shared/constants/agentSkills.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/constants/agentSkills.ts):

| Skill | Purpose |
|-------|---------|
| **smart-routing** | Selects optimal LLM provider based on cost, latency, or quality |
| **quota-management** | Tracks and enforces usage limits across providers |
| **provider-discovery** | Lists available providers and their capabilities |
| **cost-analysis** | Analyzes historical request costs |
| **health-report** | Returns system health and connectivity status |
| **list-capabilities** | Enumerates all available A2A skills |

Each skill in `src/lib/a2a/skills/*.ts` is self-contained and can be invoked independently.

## Using the Task Manager Programmatically

For internal services or other OmniRoute instances, you can bypass HTTP and interact directly with the task manager.

```ts
import { getTaskManager } from "@/lib/a2a/taskManager";
import { A2A_SKILL_HANDLERS, executeA2ATaskWithState } from "@/lib/a2a/taskExecution";

const tm = getTaskManager();

// Create a task object
const task = tm.createTask({
  skill: "cost-analysis",
  messages: [{ role: "system", content: "Analyze the cost of the last 100 requests." }],
});

// Resolve the skill handler from the registry
const handler = A2A_SKILL_HANDLERS[task.skill];

// Execute with automatic state transitions
await executeA2ATaskWithState(tm, task, handler);

```

The `executeA2ATaskWithState()` wrapper in [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts) handles all state transitions: setting **working** before execution, then **completed** with result artifacts, or **failed** with error details if an exception occurs.

## Streaming Task Progress with Server-Sent Events

When a client sends `Accept: text/event-stream`, OmniRoute switches to streaming mode. The `createA2AStream()` function in [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts) formats incremental state updates as SSE events.

```ts
import { createA2AStream, SSE_HEADERS } from "@/lib/a2a/streaming";

export async function GET(request: Request) {
  const taskId = request.headers.get("x-task-id");
  const tm = getTaskManager();
  const task = tm.getTask(taskId!);
  
  if (!task) {
    return new Response("Task not found", { status: 404 });
  }

  const stream = createA2AStream(task);
  return new Response(stream, { headers: SSE_HEADERS });
}

```

This enables real-time monitoring—useful for long-running tasks like batch cost analysis or provider health checks.

## Why OmniRoute Uses JSON-RPC 2.0 for A2A

The **JSON-RPC 2.0** foundation provides several advantages for agent-to-agent communication:

- **Versioned contracts** — Requests and responses follow a strict schema
- **Batched operations** — Multiple calls in a single HTTP request
- **Idempotency controls** — Through metadata like `requestId` for deduplication
- **Minimal overhead** — Lightweight compared to REST or GraphQL for machine-to-machine traffic

## Observability and Monitoring

The A2A protocol exposes task events and statistics through `/api/a2a/*` endpoints. External monitoring tools can track:

- Task queue depth and processing latency
- Skill-specific success and failure rates
- Provider selection patterns from smart-routing decisions

This observability layer makes the A2A protocol suitable for production multi-agent deployments where audit trails and performance metrics are required.

## Summary

- The **A2A protocol** is OmniRoute's JSON-RPC 2.0 interface for agent-to-agent communication, enabling external systems to submit tasks to built-in skills
- **Task lifecycle** is managed by `A2ATaskManager` in [`src/lib/a2a/taskManager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskManager.ts) with states: Submitted → Working → Completed | Failed | Cancelled
- **Skill handlers** are registered in `A2A_SKILL_HANDLERS` ([`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts)) and executed asynchronously with automatic state management
- **Streaming support** via `createA2AStream()` uses Server-Sent Events for real-time progress updates
- Six built-in skills provide **smart-routing**, **quota-management**, **provider-discovery**, **cost-analysis**, **health-report**, and **list-capabilities**

## Frequently Asked Questions

### What transport does OmniRoute's A2A protocol use?

OmniRoute's A2A protocol uses **HTTP with JSON-RPC 2.0** for request-response interactions, with optional **Server-Sent Events (SSE)** for streaming task updates. The primary endpoint is `/a2a` defined in [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts).

### How does OmniRoute handle A2A task failures?

When a skill throws an exception, `executeA2ATaskWithState()` in [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts) catches the error, transitions the task state to **failed**, and attaches an error artifact containing the message and stack trace. The caller receives the final state via HTTP response or SSE stream.

### Can I add custom skills to OmniRoute's A2A protocol?

Yes. Create a new TypeScript file in `src/lib/a2a/skills/`, implement the skill handler interface, and register it in `A2A_SKILL_HANDLERS` in [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts). Update [`src/shared/constants/agentSkills.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/constants/agentSkills.ts) to include the skill in the UI catalog.

### Is A2A task state persisted across OmniRoute restarts?

By default, tasks are stored in an **in-memory Map** with periodic TTL cleanup. For persistence across restarts, configure the `A2ATaskManager` to use its **SQLite backend**, which stores task state to disk with configurable retention policies.