# How the A2A Agent Protocol Integration Works in OmniRoute: A Deep Technical Dive

> Explore the technical details of A2A agent protocol integration in OmniRoute. Learn how agents discover capabilities, submit tasks, and stream results via JSON-RPC 2.0.

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

---

**OmniRoute implements the A2A (Agent-to-Agent) Protocol v0.3 as a JSON-RPC 2.0 service that enables autonomous agents to discover capabilities, submit tasks, and stream results through a standardized interface.**

The A2A protocol integration transforms OmniRoute into a discoverable, composable agent that can participate in multi-agent systems. According to the OmniRoute source code, this implementation centers on three architectural pillars: a canonical JSON-RPC endpoint, a stateful task manager, and a pluggable skill dispatcher that maps protocol methods to business logic handlers.

## Core Architecture: Three Components Driving A2A Integration

OmniRoute's A2A implementation lives under `src/app/a2a/` and `src/lib/a2a/`, with clear separation between transport, lifecycle management, and execution.

### JSON-RPC 2.0 Endpoint ([`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts))

The `POST /a2a` endpoint serves as the single entry point for all A2A interactions. It exposes four primary methods:

- `message/send` – synchronous request-response for immediate results
- `message/stream` – initiates Server-Sent Events (SSE) for incremental delivery
- `tasks/get` – queries task status and artifacts by UUID
- `tasks/cancel` – aborts an in-progress task

The router enforces strict JSON-RPC 2.0 compliance. Invalid payloads trigger standard error codes: `-32700` for parse errors, `-32600` for invalid requests, `-32601` for unknown methods, and `-32602` for invalid parameters.

### Task Manager ([`src/lib/a2a/taskManager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskManager.ts))

The `A2ATask` state machine tracks every request from submission through completion. Key characteristics include:

- **UUID assignment** – each task receives a unique identifier
- **Default 5-minute TTL** – automatic expiration prevents resource leaks
- **State transitions** – `submitted` → `working` → `completed` | `failed` | `cancelled`
- **Statistics exposure** – `getStats()` returns counts per state, total tasks, and active streams

### Skill Dispatcher ([`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts))

The `A2A_SKILL_HANDLERS` registry maps skill names to concrete implementations. When a request arrives, `executeA2ATaskWithState` retrieves the appropriate handler, executes it with the full task context, and returns structured `{ artifacts, metadata }` results.

## Agent Discovery: The Agent Card Protocol

Before invoking capabilities, A2A agents discover what OmniRoute offers via the **Agent Card** – a JSON document served at [`/.well-known/agent.json`](https://github.com/diegosouzapw/OmniRoute/blob/main//.well-known/agent.json) [^2^]. This card contains:

- Node name and version (derived from [`package.json`](https://github.com/diegosouzapw/OmniRoute/blob/main/package.json))
- Available A2A skills with descriptions
- Authentication requirements
- One-hour cache lifetime

The generator implementation in [`src/app/.well-known/agent.json/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/.well-known/agent.json/route.ts) enables dynamic capability advertisement without manual configuration.

## Request Flow: From HTTP Request to Skill Execution

Every A2A request traverses seven validation and execution stages:

1. **Authentication** – When `OMNIROUTE_API_KEY` is configured, the `authenticate` function validates `Authorization: Bearer …` headers; otherwise, the endpoint remains open.

2. **Enablement check** – The `rejectIfA2ADisabled` helper verifies `a2aEnabled` in settings, returning error `-32000` if the service is disabled (default: off).

3. **JSON-RPC parsing** – Request bodies must contain `"jsonrpc": "2.0"` and a recognized `method`.

4. **Task creation** – `taskManager.createTask` instantiates an `A2ATask` with skill name, message array, and optional metadata.

5. **Skill execution** – The dispatcher invokes the matched handler from `A2A_SKILL_HANDLERS`.

6. **State updates** – The manager transitions through `working` to terminal states, capturing errors for `failed` status.

7. **Response formatting** – `message/send` returns complete JSON-RPC responses; `message/stream` opens SSE connections via `createA2AStream` in [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts).

## Built-In A2A Skills: Six Capabilities for Agent Cooperation

OmniRoute ships with six production-ready skills in `src/lib/a2a/skills/`:

| Skill | File | Function |
|-------|------|----------|
| **Smart Routing** | [`smartRouting.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/smartRouting.ts) | Selects optimal provider/combo for user prompts |
| **Quota Management** | [`quotaManagement.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/quotaManagement.ts) | Reports per-provider usage and limits |
| **Provider Discovery** | [`providerDiscovery.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/providerDiscovery.ts) | Lists installed providers with capabilities |
| **Cost Analysis** | [`costAnalysis.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/costAnalysis.ts) | Estimates request and conversation costs |
| **Health Report** | [`healthReport.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/healthReport.ts) | Summarizes circuit-breaker and provider health |
| **List Capabilities** | [`listCapabilities.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/listCapabilities.ts) | Returns full skill catalog for dynamic discovery |

Each skill receives the complete `A2ATask` object and returns structured artifacts. New skills require only: (1) a module under `src/lib/a2a/skills/`, and (2) registration in `A2A_SKILL_HANDLERS`.

## Streaming Support: Real-Time Agent Collaboration

The `message/stream` method enables low-latency, incremental delivery through Server-Sent Events. The implementation in [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts) uses standard `SSE_HEADERS` and pushes chunks as they become available—critical for long-running generation tasks where agents shouldn't block waiting for completion.

## Practical Implementation: Code Examples

### Synchronous Task Invocation

```bash
curl -X POST http://localhost:20128/a2a \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_KEY" \
  -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "message/send",
    "params": {
      "skill": "smart-routing",
      "messages": [{"role": "user", "content": "Write a hello world program in Python"}],
      "metadata": {"model": "auto", "combo": "fast-coding"}
    }
  }'

```

This pattern from [`docs/frameworks/A2A-SERVER.md`](https://github.com/diegosouzapw/OmniRoute/blob/main/docs/frameworks/A2A-SERVER.md) (lines 55-68) demonstrates the complete request structure: JSON-RPC envelope, skill selection, message array, and execution metadata.

### Streaming Response Consumption

```javascript
import fetch from "node-fetch";

const resp = await fetch("http://localhost:20128/a2a", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer YOUR_KEY",
  },
  body: JSON.stringify({
    jsonrpc: "2.0",
    id: "stream-1",
    method: "message/stream",
    params: {
      skill: "smart-routing",
      messages: [{ role: "user", content: "Explain quantum computing in simple terms" }],
    },
  }),
});

for await (const line of resp.body) {
  console.log(line.toString());
}

```

The `createA2AStream` helper manages SSE formatting and connection lifecycle per [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts).

## Observability and Debugging

Two mechanisms support production monitoring:

- **Routing decisions** – `logRoutingDecision` in [`src/lib/a2a/routingLogger.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/routingLogger.ts) records every smart-routing choice
- **Task statistics** – `getStats()` exposes state distributions and stream counts for health dashboards

These complement OmniRoute's existing telemetry without requiring separate A2A-specific instrumentation.

## Summary

- **A2A protocol integration** in OmniRoute follows the v0.3 specification through a clean JSON-RPC 2.0 interface at `POST /a2a`
- **Three core components** handle transport ([`route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/route.ts)), lifecycle ([`taskManager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/taskManager.ts)), and execution ([`taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/taskExecution.ts) with `A2A_SKILL_HANDLERS`)
- **Agent discovery** works via [`/.well-known/agent.json`](https://github.com/diegosouzapw/OmniRoute/blob/main//.well-known/agent.json) with one-hour caching
- **Six built-in skills** cover routing, quotas, discovery, cost, health, and capability enumeration
- **Streaming via SSE** enables real-time collaboration without blocking waits
- **Pluggable architecture** allows new skills through module creation and registry insertion

## Frequently Asked Questions

### What authentication does OmniRoute's A2A endpoint require?

Authentication is optional and environment-controlled. When `OMNIROUTE_API_KEY` is set, all requests must include `Authorization: Bearer YOUR_KEY`; otherwise, the endpoint accepts unauthenticated traffic. The `authenticate` function in [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts) implements this logic, returning appropriate JSON-RPC errors for missing or invalid credentials.

### How do I enable or disable the A2A service?

The service is **disabled by default**. Set `a2aEnabled: true` in OmniRoute settings to activate it. The `rejectIfA2ADisabled` helper checks this flag and returns a `-32000` error code for any requests when disabled, preventing accidental exposure of agent capabilities.

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

Yes. Create a TypeScript module under `src/lib/a2a/skills/` that exports a handler function receiving an `A2ATask` and returning `{ artifacts, metadata }`. Then register the skill name and handler in the `A2A_SKILL_HANDLERS` map within [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts). No changes to the JSON-RPC router are required.

### What's the difference between `message/send` and `message/stream`?

`message/send` returns complete results in a single JSON-RPC response, suitable for fast operations. `message/stream` opens an SSE connection that pushes incremental artifacts as they're generated—essential for long-running tasks like large language model generation where partial results improve perceived responsiveness.