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

> Explore the technical details of the A2A agent protocol with OmniRoute. Learn how it uses JSON-RPC 2.0, Bearer tokens, and state machines for smart routing and quota management.

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

---

**OmniRoute exposes an Agent-to-Agent (A2A) JSON-RPC 2.0 endpoint at `POST /a2a` that authenticates requests via Bearer tokens, manages task lifecycles through an in-memory state machine with configurable TTL, and dispatches to pluggable skill handlers for smart routing and quota management.**

The OmniRoute repository (`diegosouzapw/OmniRoute`) implements the A2A protocol to allow external AI agents to interact with its intelligent routing layer. This protocol enables synchronous and streaming communication patterns while enforcing strict security boundaries and resource quotas through a structured JSON-RPC interface.

## Architecture of the A2A Protocol Implementation

### Entry Point and Authentication

The canonical entry point resides in [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts), which exposes a single `POST /a2a` endpoint. This handler validates the JSON-RPC envelope and authenticates the caller using an `Authorization: Bearer <API-KEY>` header. According to the source code, if no API key is configured in the environment, the authentication check is bypassed strictly for local development scenarios.

### Task Manager and State Machine

The core lifecycle management lives in [`src/lib/a2a/taskManager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskManager.ts). The `getTaskManager()` singleton creates `A2ATask` instances identified by **UUID v4**, tracking states through a deterministic state machine: `submitted → working → completed|failed|cancelled`. Tasks reside in an in-memory Map with a default **5-minute TTL** and optional SQLite persistence. The manager enforces owner scoping—referencing security advisory **GHSA‑jcm5‑6wpp‑wjj8**—to ensure tasks created under a hashed API key remain visible only to that principal.

### Skill Dispatch Registry

Concrete execution logic resides in [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts), which exports `A2A_SKILL_HANDLERS`—a registry that lazily imports skill modules such as `smart-routing` and `quota-management`. The `executeA2ATaskWithState` helper orchestrates execution: it runs the selected skill, captures artifacts, and transitions the task state to `completed` or `failed` accordingly.

### Streaming Layer

For real-time responses, [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts) implements Server-Sent Events (SSE). The `createA2AStream` function returns a `ReadableStream` that formats SSE chunks via `createChunkEvent`, emits `createHeartbeat` events every **15 seconds**, and terminates with either `createCompletionEvent` or `createFailureEvent` depending on the skill outcome. The stream also honors an optional **abort signal** for cancellation.

## Protocol Execution Flow

The A2A protocol follows a strict 9-step lifecycle when processing agent requests:

1. **JSON-RPC Request**: The client posts to `POST /a2a` with methods such as `message/send` or `message/stream`.

2. **Authentication**: The route handler validates the `Authorization` Bearer token against the configured API key.

3. **Task Creation**: `getTaskManager().createTask()` allocates a new `A2ATask` in the `submitted` state with a unique UUID.

4. **Skill Selection**: The `skill` field in the JSON-RPC params selects a handler from the `A2A_SKILL_HANDLERS` registry.

5. **Skill Execution**: The handler runs—for example, [`smartRouting.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/smartRouting.ts) calls OmniRoute’s internal `/v1/chat/completions` endpoint to fulfill the request.

6. **State Update**: On success, the manager calls `updateTask(..., "completed")`; on error, it transitions the state to `"failed"`.

7. **SSE Streaming**: For `message/stream`, `createA2AStream` drives the response with partial content chunks and heartbeats until resolution.

8. **Final Response**: The JSON-RPC response includes the task ID, final state, artifacts, and rich metadata including `routing_explanation`, `cost_envelope`, `resilience_trace`, and `policy_verdict`.

9. **Lifecycle Management**: A background process runs every minute to expire non-terminal tasks after TTL and purge terminal tasks after **2× TTL** (10 minutes).

## Practical Code Examples

### Synchronous JSON-RPC Call

To invoke the smart-routing skill synchronously, send a standard JSON-RPC request:

```json
POST http://localhost:20128/a2a
Content-Type: application/json
Authorization: Bearer YOUR_OMNIROUTE_API_KEY

{
  "jsonrpc": "2.0",
  "id": "1",
  "method": "message/send",
  "params": {
    "skill": "smart-routing",
    "messages": [{ "role": "user", "content": "Explain quantum computing" }],
    "metadata": { "model": "auto", "budget": 0.01 }
  }
}

```

The response includes detailed execution metadata:

```json
{
  "jsonrpc": "2.0",
  "id": "1",
  "result": {
    "task": { "id": "e3b0c442-…", "state": "completed" },
    "artifacts": [{ "type": "text", "content": "Quantum computing..." }],
    "metadata": {
      "routing_explanation": "Selected gpt‑4 via provider \"openai\" (latency: 842ms, cost: $0.004)",
      "cost_envelope": { "estimated": 0.005, "actual": 0.004, "currency": "USD" },
      "resilience_trace": [{ "event": "primary_selected", "provider": "openai", "timestamp": "2026‑08‑28T12:34:56Z" }],
      "policy_verdict": { "allowed": true, "reason": "within budget and quota limits" }
    }
  }
}

```

### Streaming with Server-Sent Events

For real-time streaming responses, invoke the `message/stream` method:

```bash
curl -N -X POST http://localhost:20128/a2a \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_OMNIROUTE_API_KEY" \
  -d '{
        "jsonrpc":"2.0","id":"2","method":"message/stream",
        "params":{"skill":"smart-routing","messages":[{"role":"user","content":"Write a hello world in Rust"}]}
      }'

```

The SSE output includes chunked data events and periodic heartbeats:

```

data: {"jsonrpc":"2.0","method":"message/stream","params":{"task":{"id":"…","state":"working"},"chunk":{"type":"text","content":"fn main() {"}}}

: heartbeat 2026-08-28T12:35:15.123Z

data: {"jsonrpc":"2.0","method":"message/stream","params":{"task":{"id":"…","state":"completed"},"metadata":{"routing_explanation":"...","cost_envelope":{"estimated":0.004,"actual":0.003,"currency":"USD"}}}}

```

### Internal Task Manager Usage

Developers can interact with the task manager directly for custom skill implementations:

```typescript
import { getTaskManager } from "@/src/lib/a2a/taskManager";

const manager = getTaskManager();
const task = manager.createTask({
  skill: "quota-management",
  messages: [],
  metadata: {}
});

// Later, from within a skill implementation:
manager.updateTask(task.id, "completed", [{ type: "text", content: "All quotas OK" }]);

```

## Summary

- OmniRoute exposes a JSON-RPC 2.0 endpoint at `POST /a2a` defined in [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts) for Agent-to-Agent communication.
- The **Task Manager** in [`src/lib/a2a/taskManager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskManager.ts) maintains UUID-identified tasks with a `submitted → working → completed|failed|cancelled` state machine, 5-minute TTL, and API-key-based owner isolation.
- **Skill execution** occurs through the `A2A_SKILL_HANDLERS` registry in [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts), enabling lazy-loaded modules like `smart-routing` to invoke internal OmniRoute APIs.
- **Streaming support** via [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts) provides SSE with 15-second heartbeats through `createA2AStream`, supporting abort signals for cancellation.
- The protocol enforces authentication via Bearer tokens and returns rich metadata including cost envelopes, routing explanations, and policy verdicts.

## Frequently Asked Questions

### What authentication method does OmniRoute require for A2A protocol access?

OmniRoute requires an `Authorization: Bearer <API-KEY>` header for all requests to the `POST /a2a` endpoint. As implemented in [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts), the handler validates this token against configured environment variables; if no key is set, authentication is bypassed for local development only.

### How long do A2A tasks persist in the OmniRoute system?

Tasks live in an in-memory Map with a default **5-minute TTL**. A background cleanup process runs every minute to mark expired non-terminal tasks as `failed`. Terminal tasks (completed, failed, or cancelled) are purged after **2× TTL** (10 minutes by default), though optional SQLite persistence can retain them longer per the configuration in [`src/lib/a2a/taskManager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskManager.ts).

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

Yes. The `A2A_SKILL_HANDLERS` registry in [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts) lazily imports skill modules based on the `skill` parameter in the JSON-RPC request. Developers can extend the registry with new handlers following the pattern established in [`src/lib/a2a/skills/smartRouting.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/skills/smartRouting.ts), which demonstrates how to call internal OmniRoute APIs and return structured artifacts.

### Does OmniRoute support real-time streaming via the A2A protocol?

Yes. The `message/stream` method triggers Server-Sent Events (SSE) through `createA2AStream` in [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts). This streams partial content as `createChunkEvent` objects, emits `createHeartbeat` events every 15 seconds, and terminates with either `createCompletionEvent` or `createFailureEvent` based on the skill execution result, while respecting client-side abort signals.