# How the A2A Agent Protocol Enables Communication Between OmniRoute and Other Agents

> Learn how OmniRoute uses the A2A Agent Protocol to enable seamless communication discoverability and result streaming between autonomous agents via a standardized JSON-RPC 2.0 service.

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

---

**OmniRoute implements the A2A (Agent-to-Agent) Protocol v0.3 as a lightweight JSON-RPC 2.0 service that lets autonomous agents discover, invoke, and stream results from one another through a standardized `POST /a2a` endpoint.**

The **A2A agent protocol** powers OmniRoute's interoperability with external agent systems. This open standard, implemented in the `diegosouzapw/OmniRoute` repository, transforms the routing engine into a discoverable, callable service that any compliant agent can integrate with. The protocol rests on three architectural pillars: a JSON-RPC 2.0 entry point, a stateful task manager, and a pluggable skill dispatcher.

## Core Components of OmniRoute's A2A Implementation

### JSON-RPC 2.0 Endpoint

All A2A communication flows through a single canonical entry point. In [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts), the server exposes `POST /a2a` and accepts four standard methods:

- `message/send` — synchronous request/response
- `message/stream` — Server-Sent Events for incremental updates
- `tasks/get` — query task status by UUID
- `tasks/cancel` — terminate an in-flight task

The endpoint validates `jsonrpc: "2.0"` in every payload and returns standard JSON-RPC error codes for malformed requests (`-32700`), invalid requests (`-32600`), unknown methods (`-32601`), or invalid parameters (`-32602`).

### Task Manager

The `A2ATaskManager` class in [`src/lib/a2a/taskManager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskManager.ts) tracks the full lifecycle of every request:

1. **Submitted** → task created with UUID and 5-minute TTL
2. **Working** → skill handler executing
3. **Completed | Failed | Cancelled** → terminal states

The manager enforces timeouts, persists state transitions, and exposes `getStats()` for observability. Each task carries its skill name, message array, and optional metadata.

### Skill Dispatcher

Concrete business logic lives in [`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 TypeScript modules. When a task arrives, `executeA2ATaskWithState` invokes the matching handler, which returns `{ artifacts, metadata }` for the response.

## Agent Discovery via the Agent Card

Before invoking OmniRoute, external agents **discover capabilities** by fetching [`/.well-known/agent.json`](https://github.com/diegosouzapw/OmniRoute/blob/main//.well-known/agent.json). This JSON document, generated in [`src/app/.well-known/agent.json/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/.well-known/agent.json/route.ts), contains:

- Node name and version (pulled from [`package.json`](https://github.com/diegosouzapw/OmniRoute/blob/main/package.json))
- List of available A2A skills with input schemas
- Authentication requirements

The card is cached for one hour to reduce overhead.

## Request Flow Step-by-Step

Understanding how the A2A agent protocol processes a request helps with debugging and integration:

1. **Authentication check** — If `OMNIROUTE_API_KEY` is configured, the `Authorization: Bearer <token>` header must match; otherwise the endpoint remains open
2. **Enabled toggle** — The `rejectIfA2ADisabled` helper checks `a2aEnabled` in settings, returning error `-32000` if disabled
3. **JSON-RPC parsing** — Request body validated against the 2.0 specification
4. **Task creation** — `taskManager.createTask` instantiates `A2ATask` with UUID and TTL
5. **Skill execution** — Handler from `A2A_SKILL_HANDLERS` receives the task object
6. **State updates** — Manager transitions through `working` to `completed` or `failed`
7. **Response formatting** — Single JSON object for `message/send`, SSE stream for `message/stream`

## Built-In A2A Skills

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

| Skill | Source File | Purpose |
|-------|-------------|---------|
| **Smart Routing** | [`smartRouting.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/smartRouting.ts) | Selects optimal provider/combo for prompts |
| **Quota Management** | [`quotaManagement.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/quotaManagement.ts) | Reports per-provider quota consumption |
| **Provider Discovery** | [`providerDiscovery.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/providerDiscovery.ts) | Lists installed providers and capabilities |
| **Cost Analysis** | [`costAnalysis.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/costAnalysis.ts) | Estimates request or conversation cost |
| **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 |

Adding custom skills requires creating a module under `src/lib/a2a/skills/` and registering it in `A2A_SKILL_HANDLERS`.

## Streaming Support with Server-Sent Events

The `message/stream` method enables real-time collaboration. Implemented in [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts), this returns SSE with headers defined in `SSE_HEADERS`. Calling agents receive:

- Incremental artifacts (partial text generations)
- Progress metadata
- Final state and complete artifacts

This avoids blocking waits for long-running operations like multi-provider routing chains.

## Code Examples for A2A Integration

### Synchronous Request with cURL

```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 matches the documented snippet in [`docs/frameworks/A2A-SERVER.md`](https://github.com/diegosouzapw/OmniRoute/blob/main/docs/frameworks/A2A-SERVER.md) lines 55-68.

### Streaming Response in Node.js

```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 streaming logic is defined in [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts).

### Query Task Status

```bash
curl -X POST http://localhost:20128/a2a \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_KEY" \
  -d '{"jsonrpc":"2.0","id":"2","method":"tasks/get","params":{"taskId":"<TASK_UUID>"}}'

```

The `tasks/get` handler appears in [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts) approximately lines 90-110.

### Discover Available Capabilities

```bash
curl http://localhost:20128/a2a \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_KEY" \
  -d '{"jsonrpc":"2.0","id":"cap","method":"list-capabilities","params":{}}'

```

This invokes the skill in [`src/lib/a2a/skills/listCapabilities.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/skills/listCapabilities.ts).

## Observability and Logging

Every smart-routing decision is logged via `logRoutingDecision` in [`src/lib/a2a/routingLogger.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/routingLogger.ts). The task manager's `getStats()` method exposes:

- Task counts per state (submitted, working, completed, failed, cancelled)
- Total tasks processed
- Active SSE stream count

## Key Implementation Files

| File | Role in A2A Agent Protocol |
|------|---------------------------|
| [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts) | Main JSON-RPC router, method dispatch, error handling |
| [`src/lib/a2a/taskManager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskManager.ts) | Task lifecycle, state machine, TTL enforcement |
| [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts) | Skill handler registry, execution wrapper |
| [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts) | SSE stream creation and header constants |
| `src/lib/a2a/skills/*.ts` | Concrete skill implementations |
| [`docs/frameworks/A2A-SERVER.md`](https://github.com/diegosouzapw/OmniRoute/blob/main/docs/frameworks/A2A-SERVER.md) | Authoritative protocol documentation |
| [`src/app/.well-known/agent.json/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/.well-known/agent.json/route.ts) | Agent Card generation for discovery |

## Summary

- The **A2A agent protocol** in OmniRoute v0.3 uses **JSON-RPC 2.0** over HTTP with a single `POST /a2a` endpoint
- **Agent discovery** works via [`/.well-known/agent.json`](https://github.com/diegosouzapw/OmniRoute/blob/main//.well-known/agent.json) for capability advertisement
- **Six built-in skills** handle routing, quotas, providers, cost, health, and capability listing
- **Streaming responses** use Server-Sent Events for incremental results without blocking
- **Task lifecycle management** includes UUID tracking, 5-minute TTL, and state transitions
- **Pluggable architecture** lets developers add custom skills under `src/lib/a2a/skills/`

## Frequently Asked Questions

### What authentication does the A2A endpoint require?

Authentication is optional and controlled by the `OMNIROUTE_API_KEY` environment variable. When set, all requests must include `Authorization: Bearer <token>` matching that key. If unset, the endpoint accepts unauthenticated requests. The `authenticate` function in [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts) enforces this policy.

### How do I enable or disable A2A functionality?

A2A is **disabled by default**. Set `a2aEnabled: true` in your OmniRoute configuration to activate the endpoint. When disabled, the `rejectIfA2ADisabled` helper returns JSON-RPC error `-32000` for any incoming request.

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

Yes. Create a TypeScript module under `src/lib/a2a/skills/` exporting a handler function, then register it in `A2A_SKILL_HANDLERS` within [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts). The handler receives the full `A2ATask` object and must return `{ artifacts, metadata }`. The existing [`listCapabilities.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/listCapabilities.ts) skill demonstrates the required pattern.

### What happens when a task exceeds its time limit?

The task manager enforces a **default 5-minute TTL** on all tasks. Expired tasks transition to `failed` state automatically. This prevents resource leaks from abandoned or hung requests.