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

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 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: submittedworkingcompleted | 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/release/v3.8.50/src/lib/a2a/taskExecution.ts) maps method names (e.g., "smart-routing", "quota-management") to lazy-loaded modules:

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/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/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:

Practical Implementation Examples

Creating and Executing a Task

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

// 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/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/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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →