# OmniRoute A2A Server Architecture: How JSON-RPC 2.0 Skills and Tasks Work

> Explore the OmniRoute A2A server architecture. Learn how JSON-RPC 2.0 skills and tasks leverage a state machine for efficient method routing and real-time data streaming via SSE.

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

---

**The OmniRoute A2A server implements a JSON-RPC 2.0 protocol layer via a task-based state machine that routes method calls to skill handlers, supporting both synchronous responses and Server-Sent Event (SSE) streaming.**

The `diegosouzapw/OmniRoute` repository contains a lightweight **A2A (agent-to-agent) server** that enables external agents to invoke routing capabilities via standard JSON-RPC 2.0 requests. This architecture decouples request lifecycle management from business logic through a dedicated task system and skill registry located in `src/lib/a2a/`.

## Architecture Overview

The A2A server follows a modular design with four primary layers that handle everything from request ingestion to response streaming.

### Core Components

- **Task Lifecycle Management** – The `TaskManager` class in [[`src/lib/a2a/taskManager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskManager.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/lib/a2a/taskManager.ts) creates, persists, and expires task objects that represent individual JSON-RPC calls.
- **Skill Registry** – [[`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/lib/a2a/taskExecution.ts) maintains the `A2A_SKILL_HANDLERS` map that associates JSON-RPC method names with async handler functions.
- **Streaming Layer** – [[`src/lib/a2a/streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/lib/a2a/streaming.ts) encodes responses as SSE events following the JSON-RPC 2.0 "message/stream" convention.
- **Skill Implementations** – Concrete capabilities (routing, quotas, discovery) live under `src/lib/a2a/skills/` and expose a uniform `executeX(task)` signature.

## Task Lifecycle and State Management

Every JSON-RPC request materializes as an `A2ATask` object that tracks state transitions, artifacts, and metadata throughout its lifetime.

### The TaskManager Singleton

The `TaskManager` operates as a process-wide singleton via `getTaskManager()`. It maintains an in-memory store of tasks and enforces a strict state machine:

- **States**: `submitted` → `working` → `completed` | `failed` | `cancelled`
- **TTL Cleanup**: Tasks automatically expire after a configurable time-to-live (default five minutes) and are purged by a periodic `cleanupExpired` timer.
- **Atomic Updates**: State transitions are validated against `VALID_TRANSITIONS` to prevent illegal moves (e.g., from `completed` back to `working`).

When a request arrives, the server calls `taskManager.createTask()` to generate a UUIDv4 identifier and persist the initial task state.

## JSON-RPC 2.0 Skill Execution

The server routes incoming JSON-RPC methods to TypeScript functions through a centralized registry pattern.

### Skill Registry and Handlers

The `A2A_SKILL_HANDLERS` object in [[`taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/taskExecution.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/lib/a2a/taskExecution.ts) maps method names (e.g., `"smart-routing"`, `"quota-management"`) to lazy-loaded modules:

```typescript
const A2A_SKILL_HANDLERS: Record<string, SkillHandler> = {
  "smart-routing": async (task) => 
    (await import("./skills/smartRouting")).executeSmartRouting(task),
  "quota-management": async (task) => 
    (await import("./skills/quotaManagement")).executeQuotaManagement(task),
};

```

Each handler receives the full `A2ATask` object and returns a promise resolving to `{ artifacts: TaskArtifact[], metadata: Record<string, unknown> }`.

### State-Aware Execution Wrapper

The `executeA2ATaskWithState()` utility wraps skill invocation with automatic state management:

1. Updates the task to `working` before execution.
2. Catches errors and transitions to `failed` with error details.
3. On success, appends returned artifacts and sets state to `completed`.

This ensures the task state always reflects the execution reality without manual intervention from skill authors.

## Streaming Responses with SSE

OmniRoute supports streaming JSON-RPC responses via **Server-Sent Events (SSE)**, enabling real-time delivery of partial results.

### JSON-RPC 2.0 Stream Events

The [[`streaming.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/streaming.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/lib/a2a/streaming.ts) module formats events according to the JSON-RPC 2.0 specification:

- **`message/stream`** – Carries intermediate content chunks.
- **`message/heartbeat`** – Sent every 15 seconds to keep connections alive.
- **`message/completion`** – Signals successful termination with final metadata.
- **`message/failure`** – Indicates error conditions.

The `createA2AStream()` function returns a `ReadableStream` that orchestrates the flow: it starts the heartbeat interval, invokes the skill, yields each artifact as a chunk event, emits the completion event, and finally cleans up resources.

## Built-in Skills Implementation

Skills are concrete implementations of A2A capabilities that expose routing, cost analysis, and provider management functionality.

### Smart Routing and Metadata Enrichment

The [[`smartRouting.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/smartRouting.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/lib/a2a/skills/smartRouting.ts) skill forwards user prompts to OmniRoute's internal chat endpoint and returns a rich metadata envelope containing:

- Routing explanations and provider selection logic.
- Cost estimates and latency predictions.
- Resilience traces and policy verdicts.

It returns these as structured artifacts alongside a compact metadata object.

### Provider Discovery and Quota Management

Additional skills in the same directory provide operational visibility:

- **[[`providerDiscovery.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/providerDiscovery.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/lib/a2a/skills/providerDiscovery.ts)** – Returns available LLM providers and their current status.
- **[[`quotaManagement.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/quotaManagement.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/lib/a2a/skills/quotaManagement.ts)** – Checks rate limits and budget constraints for the requesting agent.
- **[[`listCapabilities.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/listCapabilities.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/lib/a2a/skills/listCapabilities.ts)** – Dynamically returns the list of registered skill names.

## Practical Implementation Examples

### Creating and Executing a Task

```typescript
import { getTaskManager } from "@/src/lib/a2a/taskManager";
import { executeA2ATaskWithState, A2A_SKILL_HANDLERS } from "@/src/lib/a2a/taskExecution";

// Initialize the task
const tm = getTaskManager();
const task = tm.createTask({
  skill: "smart-routing",
  messages: [{ role: "user", content: "Route this prompt efficiently" }],
  metadata: { model: "gpt-4", budget: 0.01 },
});

// Execute with automatic state management
await executeA2ATaskWithState(
  tm,
  task,
  A2A_SKILL_HANDLERS["smart-routing"]
);

console.log(task.state); // "completed"
console.log(task.artifacts); // Array of results

```

### Implementing a Custom Skill

```typescript
// src/lib/a2a/skills/customAnalytics.ts
import { A2ATask, TaskArtifact } from "@/src/lib/a2a/types";

export async function executeCustomAnalytics(task: A2ATask): Promise<{
  artifacts: TaskArtifact[];
  metadata: Record<string, unknown>;
}> {
  const analysis = await performAnalysis(task.messages);
  
  return {
    artifacts: [{
      type: "text",
      content: analysis.summary,
      metadata: { confidence: analysis.score }
    }],
    metadata: { processedAt: Date.now() }
  };
}

// Register in src/lib/a2a/taskExecution.ts
A2A_SKILL_HANDLERS["custom-analytics"] = async (task) =>
  (await import("./skills/customAnalytics")).executeCustomAnalytics(task);

```

## Summary

- **TaskManager** provides durable, stateful context for every JSON-RPC request with automatic expiration and cleanup.
- **Skill handlers** are dynamically imported functions mapped to JSON-RPC method names via `A2A_SKILL_HANDLERS`.
- **Streaming** uses SSE with JSON-RPC 2.0 envelopes to deliver real-time chunks and heartbeats.
- **State transitions** are atomic and validated, preventing inconsistent task statuses.
- **Extensibility** follows a simple pattern: implement `executeX(task)`, return artifacts and metadata, and register the handler.

## Frequently Asked Questions

### What is the role of the TaskManager in OmniRoute's A2A server?

The **TaskManager** acts as the central authority for task lifecycle management. It creates unique task instances, enforces valid state transitions (submitted → working → completed/failed), and automatically removes expired tasks after a configurable TTL. This ensures that every JSON-RPC request maintains a consistent, queryable state throughout its execution.

### How does the A2A server handle JSON-RPC 2.0 method routing?

Incoming JSON-RPC requests are parsed to extract the `method` field. The server lookups this key in the `A2A_SKILL_HANDLERS` registry located in [[`taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/taskExecution.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/lib/a2a/taskExecution.ts). If found, the corresponding skill handler is invoked with the current `A2ATask` object; if not found, the server returns a standard JSON-RPC error response indicating the method is not found.

### What streaming protocol does OmniRoute use for A2A responses?

OmniRoute uses **Server-Sent Events (SSE)** to stream responses. Each SSE data line contains a JSON-RPC 2.0 object with the method field set to `message/stream` for content chunks, `message/heartbeat` for keep-alive signals, and `message/completion` or `message/failure` for terminal states. This approach maintains HTTP compatibility while enabling real-time, server-to-client streaming.

### How can developers add custom skills to the OmniRoute A2A server?

Developers create a new file under `src/lib/a2a/skills/` exporting an `executeX(task)` function that returns `{ artifacts, metadata }`. They then register this function in [[`taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/taskExecution.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/lib/a2a/taskExecution.ts) by adding an entry to `A2A_SKILL_HANDLERS` with a unique JSON-RPC method name. The registration uses dynamic imports to enable code-splitting and lazy loading.