# How the A2A v0.3 Protocol Integrates with OmniRoute Skills: Architecture and Implementation

> Learn how the A2A v0.3 protocol integrates with OmniRoute skills. Explore the JSON-RPC 2.0 transport and asynchronous skill handlers enabling agent-to-agent communication.

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

---

**The A2A v0.3 protocol integrates with OmniRoute skills through a JSON-RPC 2.0 transport layer that maps incoming method calls to asynchronous skill handlers in [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts), enabling agent-to-agent communication via a unified task lifecycle managed by `A2ATaskManager`.**

The **A2A (Agent-to-Agent) v0.3 protocol** provides a standardized interface for external agents to invoke capabilities within the OmniRoute ecosystem. As implemented in the `diegosouzapw/OmniRoute` repository (release v3.8.49), this protocol exposes six built-in skills—from smart routing to health reporting—through a type-safe JSON-RPC endpoint. Understanding this integration reveals how OmniRoute transforms internal business logic into remotely accessible agent capabilities.

## Architecture Overview

### JSON-RPC 2.0 Transport Layer

The protocol implements a **JSON-RPC 2.0** transport endpoint at [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts). This Next.js API route authenticates incoming requests, parses the JSON-RPC payload, and forwards validated calls to the task manager. The implementation maintains strict compliance with the A2A v0.3 specification while bridging to OmniRoute's internal TypeScript modules.

### Request Flow

Incoming requests follow a predictable pipeline: the route handler creates an `A2ATask` instance via `A2ATaskManager`, dispatches the task to a registered skill handler, and returns the results as JSON-RPC responses. For streaming operations, [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts) wraps output in **Server-Sent Events (SSE)** while preserving the JSON-RPC envelope structure.

## Task Lifecycle and Skill Dispatch

### A2ATaskManager Instance

The global `A2ATaskManager` singleton defined in [`src/lib/a2a/taskManager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskManager.ts) governs the complete task lifecycle: **created → running → completed → failed/cancelled**. When a request creates a task, the manager assigns a unique task ID, tracks execution state, and maintains the `artifacts` array that stores skill outputs such as Markdown tables or cost estimates.

### Skill Dispatch Table

The core integration point resides in [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts) as the `A2A_SKILL_HANDLERS` record. This dispatch table maps string skill names to asynchronous handler functions using the signature `(task: A2ATask) => Promise<...>`. Each entry points to a concrete implementation in the `src/lib/a2a/skills/` directory.

## Built-in A2A Skills

OmniRoute ships with six self-contained A2A skills, each exposing a specific domain capability:

- **smart-routing**: Executes provider selection logic via `executeSmartRouting` in [`src/lib/a2a/skills/smartRouting.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/skills/smartRouting.ts)
- **quota-management**: Reports remaining quota by querying [`src/lib/db/quotaSnapshots.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/quotaSnapshots.ts) through `executeQuotaManagement`
- **provider-discovery**: Lists known providers and models via `executeProviderDiscovery`
- **cost-analysis**: Estimates request costs using `executeCostAnalysis`
- **health-report**: Returns router health metrics via `executeHealthReport`
- **list-capabilities**: Generates the Agent Card "Capabilities" artifact through `executeListCapabilities`

### Skill Signature and Artifacts

Every skill adheres to a uniform interface receiving the `A2ATask` object. Skills populate the `task.artifacts` array with structured outputs—typically Markdown content—before returning a status object. For example, `executeListCapabilities` retrieves skill rows from the database, renders them as a Markdown table, and pushes the result into `task.artifacts` before marking the task completed.

## Integration Points

### Database Access

A2A skills leverage existing OmniRoute data layers rather than duplicating logic. The **quota-management** skill directly queries [`src/lib/db/quotaSnapshots.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/quotaSnapshots.ts) to surface live quota data, while **smart-routing** utilizes internal combo-routing algorithms. This tight coupling ensures A2A responses reflect real-time system state.

### Streaming Support

For long-running or incremental responses, the protocol supports streaming via [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts). The implementation wraps task outputs in SSE streams, allowing clients to receive partial results while maintaining the JSON-RPC response format. This proves essential for capabilities that require progressive disclosure of routing decisions.

### Discovery and UI Integration

The Agent Card catalog at [`src/lib/agentSkills/catalog.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/agentSkills/catalog.ts) automatically registers all six A2A skills, making them discoverable to external agents. Within the OmniRoute dashboard, `src/app/(dashboard)/dashboard/a2a/A2ADashboardPage.tsx` renders an "A2A" tab for manual skill invocation. Developers can also trigger skills via the CLI command wrapper at `bin/cli/commands/a2a.mjs`.

## Code Examples

The following example demonstrates invoking the **list-capabilities** skill via HTTP:

```typescript
// Client-side invocation of the A2A protocol
const response = await fetch("/api/a2a/tasks", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    jsonrpc: "2.0",
    method: "list-capabilities",
    params: {},
    id: "req-123"
  })
});

const result = await response.json();
// Returns: { taskId: "a2a-01", status: "completed", artifacts: [...] }

```

The dispatch table in [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts) routes this request:

```typescript
// Skill dispatch table implementation
export const A2A_SKILL_HANDLERS: Record<string, A2ASkillHandler> = {
  "smart-routing": executeSmartRouting,
  "quota-management": executeQuotaManagement,
  "provider-discovery": executeProviderDiscovery,
  "cost-analysis": executeCostAnalysis,
  "health-report": executeHealthReport,
  "list-capabilities": executeListCapabilities,
};

```

A skill implementation follows this pattern:

```typescript
// src/lib/a2a/skills/listCapabilities.ts
export async function executeListCapabilities(task: A2ATask) {
  const rows = await getAllSkillRows(); // Database query
  const markdown = renderMarkdownTable(rows);
  
  task.artifacts.push({ 
    type: "markdown", 
    content: markdown 
  });
  
  return { 
    status: "completed", 
    artifacts: task.artifacts 
  };
}

```

## Summary

- The **A2A v0.3 protocol** exposes OmniRoute capabilities via JSON-RPC 2.0 through the endpoint [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts)
- **A2ATaskManager** in [`src/lib/a2a/taskManager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskManager.ts) handles the task lifecycle (created → running → completed)
- The `A2A_SKILL_HANDLERS` dispatch table in [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts) routes calls to six built-in skills
- Skills reside in `src/lib/a2a/skills/` and follow the signature `(task: A2ATask) => Promise<...>`
- **Streaming support** via [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts) enables Server-Sent Events for long-running operations
- Full integration with existing data layers allows skills to query live system state without logic duplication

## Frequently Asked Questions

### How does OmniRoute authenticate A2A v0.3 protocol requests?

Authentication occurs at the entry point in [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts) before the JSON-RPC payload reaches the task manager. The route validates credentials according to configured OmniRoute security policies, ensuring only authorized agents can invoke skills.

### Can custom skills be added to the A2A v0.3 protocol implementation?

Yes. Developers can extend the `A2A_SKILL_HANDLERS` record in [`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts) with new key-value pairs, implementing handlers that follow the `(task: A2ATask) => Promise<...>` signature. New skills must be registered in [`src/lib/agentSkills/catalog.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/agentSkills/catalog.ts) to appear in the Agent Card discovery mechanism.

### What is the difference between A2A skills and regular OmniRoute functions?

**A2A skills** are specifically wrapped to comply with the A2A v0.3 specification, including JSON-RPC envelope handling, artifact generation, and task lifecycle management through `A2ATaskManager`. Regular OmniRoute functions lack this standardized interface and task state tracking, though skills often delegate to internal functions for business logic.

### How does streaming work for A2A skills that take time to execute?

Long-running skills utilize the streaming utilities in [`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts), which wraps task outputs in **Server-Sent Events (SSE)**. This allows the client to receive incremental updates while the task remains in "running" status, with final completion signaled when the handler returns and the task status updates to "completed".