# How the A2A v0.3 Protocol Facilitates Agent-to-Agent Communication in OmniRoute

> Discover how A2A v0.3 protocol in OmniRoute powers agent-to-agent communication. Learn about its JSON-RPC 2.0 service for discovery, invocation, and result streaming.

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

---

**The A2A v0.3 protocol in OmniRoute enables autonomous agents to discover, invoke, and stream results from one another through a lightweight JSON-RPC 2.0 service built on a task manager, skill dispatcher, and streaming interface.**

OmniRoute implements the **A2A (Agent-to-Agent) Protocol version 0.3** to provide standardized agent-to-agent communication across distributed systems. This protocol allows external agents to leverage OmniRoute’s smart routing, quota management, and provider discovery capabilities through a clean, standards-based interface defined in [`docs/frameworks/A2A-SERVER.md`](https://github.com/diegosouzapw/OmniRoute/blob/main/docs/frameworks/A2A-SERVER.md).

## Core Architecture of the A2A v0.3 Protocol

The protocol architecture rests on three primary components that handle request ingestion, lifecycle management, and business logic execution.

### JSON-RPC 2.0 Endpoint

All A2A interactions flow through the canonical entry point defined in [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts). This Next.js route handler exposes the `POST /a2a` endpoint and implements the JSON-RPC 2.0 specification. The supported methods include:

- `message/send` – Synchronous request/response pattern
- `message/stream` – Server-Sent Events (SSE) for real-time updates
- `tasks/get` – Retrieve task status and artifacts
- `tasks/cancel` – Abort an in-flight task

The router validates the `"jsonrpc": "2.0"` header and returns standard JSON-RPC error codes (`-32700`, `-32600`, `-32601`, `-32602`) for malformed requests.

### Task Manager

Located in [`src/lib/a2a/taskManager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskManager.ts), the task manager maintains the full lifecycle state machine for every request. It assigns a UUID to each task, enforces a default **5-minute TTL**, and tracks transitions from `submitted` → `working` → `completed` or `failed`. The `createTask` function initializes the record, while `getStats` exposes operational metrics including active stream counts and state distributions.

### Skill Dispatcher

The dispatcher in [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts) maps skill names to concrete handlers via the `A2A_SKILL_HANDLERS` registry. When a request specifies a skill (e.g., `"smart-routing"`), the dispatcher invokes `executeA2ATaskWithState`, which runs the handler and manages state transitions through the task manager.

## Agent Discovery via the Agent Card

Before invoking capabilities, A2A clients discover what an OmniRoute node offers by requesting [`/.well-known/agent.json`](https://github.com/diegosouzapw/OmniRoute/blob/main//.well-known/agent.json). This **Agent Card**—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 the node’s name, version, available skills, and authentication requirements. The response is cached for one hour and provides the canonical manifest for automated agent negotiation.

## Request Lifecycle and Method Handlers

Every A2A request passes through a rigorous validation and execution pipeline:

1. **Authentication** – If `OMNIROUTE_API_KEY` is configured, the `authenticate` function requires a matching `Authorization: Bearer <token>` header.
2. **Feature Toggle** – The `rejectIfA2ADisabled` helper checks the `a2aEnabled` setting, returning error code `-32000` if the endpoint is disabled.
3. **Task Creation** – Valid requests trigger `taskManager.createTask`, persisting the skill name, message array, and metadata.
4. **Skill Execution** – The appropriate handler from `A2A_SKILL_HANDLERS` receives the `A2ATask` object and returns `{ artifacts, metadata }`.
5. **State Updates** – The manager updates the task to `working`, then finalizes to `completed` or `failed` depending on handler success.
6. **Response Formation** – Synchronous calls return a JSON-RPC response object, while streaming calls initiate SSE via `createA2AStream` from [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts).

## Built-in Skills for Agent Capabilities

OmniRoute ships with six native skills located in `src/lib/a2a/skills/`, each exportable for the `A2A_SKILL_HANDLERS` registry:

- **Smart Routing** ([`smartRouting.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/smartRouting.ts)) – Selects optimal provider combinations for prompt execution.
- **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 and their capabilities.
- **Cost Analysis** ([`costAnalysis.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/costAnalysis.ts)) – Estimates token and monetary costs for requests.
- **Health Report** ([`healthReport.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/healthReport.ts)) – Summarizes circuit-breaker status and provider health.
- **List Capabilities** ([`listCapabilities.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/listCapabilities.ts)) – Returns the full skill catalog for dynamic discovery.

New capabilities are added by creating a TypeScript module in the skills directory and registering it in [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts).

## Real-time Communication with SSE Streaming

The `message/stream` method enables progressive result delivery through Server-Sent Events. When invoked, the server establishes an SSE connection using headers defined in `SSE_HEADERS` (located in [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts)) and pushes incremental chunks as the skill handler produces artifacts. This allows calling agents to process partial results—such as streaming text generation—without waiting for the full operation to complete.

## Practical Implementation Examples

### Synchronous Smart Routing Request

Invoke the `smart-routing` skill with a standard JSON-RPC payload:

```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"}
    }
  }'

```

### Streaming Response Handling

Use `message/stream` for real-time results:

```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());
}

```

### Task Status Query

Retrieve the current state of a specific task:

```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>"}}'

```

### Capability Discovery

Query available skills dynamically:

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

```

## Summary

- The **A2A v0.3 protocol** utilizes JSON-RPC 2.0 over HTTP with a dedicated `POST /a2a` endpoint defined in [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts).
- **Task lifecycle management** is handled by the task manager in [`src/lib/a2a/taskManager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskManager.ts), which enforces a 5-minute TTL and tracks state transitions.
- **Skill execution** is modularized through the `A2A_SKILL_HANDLERS` registry in [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts), supporting six built-in capabilities.
- **Real-time streaming** is implemented via Server-Sent Events using utilities in [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts).
- **Discovery** follows the A2A specification through the [`/.well-known/agent.json`](https://github.com/diegosouzapw/OmniRoute/blob/main//.well-known/agent.json) endpoint 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).

## Frequently Asked Questions

### What authentication mechanism does the A2A v0.3 protocol use in OmniRoute?

OmniRoute validates requests using a Bearer token scheme. When the `OMNIROUTE_API_KEY` environment variable is set, the `authenticate` function in [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts) requires an `Authorization: Bearer <token>` header matching that key. If the variable is unset, the endpoint operates without authentication.

### How does OmniRoute handle long-running agent tasks?

Long-running tasks utilize the `message/stream` method, which opens an SSE connection via `createA2AStream` in [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts). This allows the server to push incremental artifacts while the task status remains `working`, avoiding timeouts that would occur with synchronous HTTP requests.

### Can I add custom skills to the OmniRoute A2A implementation?

Yes. Create a new TypeScript file in `src/lib/a2a/skills/` that exports a handler function receiving an `A2ATask` object and returning `{ artifacts, metadata }`. Register this handler in the `A2A_SKILL_HANDLERS` object within [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts) to make it available for invocation via the JSON-RPC interface.

### What happens if the A2A endpoint is disabled in OmniRoute?

The `rejectIfA2ADisabled` helper checks the `a2aEnabled` configuration flag before processing any request. If disabled, the server returns a JSON-RPC error with code `-32000` and a message indicating that the A2A service is not active, preventing accidental exposure of agent capabilities.