What Is the A2A Protocol? Understanding OmniRoute's Agent-to-Agent Communication System

The A2A (Agent-to-Agent) protocol is a JSON-RPC 2.0-based communication framework in OmniRoute that lets external agents submit tasks through built-in skills like smart-routing, quota-management, and provider-discovery, with full lifecycle management from submission to completion.

The A2A protocol gives OmniRoute a standardized way to handle requests from other AI agents or external systems. Built on JSON-RPC 2.0, it transforms complex provider interactions into simple, trackable tasks that any authorized client can initiate and monitor.

How the A2A Protocol Works in OmniRoute

OmniRoute's implementation follows a strict state machine: tasks move from Submitted → Working → Completed | Failed | Cancelled. This design ensures predictable behavior whether you're calling the public HTTP endpoint or using the internal task manager directly.

Core Components of the A2A Architecture

Three files define the protocol's backbone:

  • src/lib/a2a/taskManager.ts — The A2ATaskManager class stores tasks in an in-memory map with optional SQLite persistence and TTL-based cleanup
  • src/lib/a2a/taskExecution.ts — Contains A2A_SKILL_HANDLERS, the registry mapping skill names to their implementations, plus executeA2ATaskWithState() for state-aware execution
  • src/lib/a2a/streaming.ts — Provides createA2AStream() for formatting Server-Sent Events when clients request streaming updates

Submitting Tasks to OmniRoute's A2A Endpoint

External agents interact with OmniRoute through the /a2a HTTP endpoint defined in src/app/a2a/route.ts. The endpoint normalizes incoming requests to a canonical shape and routes them to the appropriate skill handler.

HTTP API Example

POST /a2a HTTP/1.1
Content-Type: application/json
Authorization: Bearer <api-key>

{
  "skill": "smart-routing",
  "messages": [
    { "role": "user", "content": "Explain the difference between HTTP and HTTPS." }
  ],
  "metadata": { "requestId": "abc123" }
}

The response includes the task ID and initial state. You can then poll for status or request streaming updates via the SSE endpoint.

Available A2A Skills in OmniRoute

The protocol ships with six built-in skills, as cataloged in src/shared/constants/agentSkills.ts:

Skill Purpose
smart-routing Selects optimal LLM provider based on cost, latency, or quality
quota-management Tracks and enforces usage limits across providers
provider-discovery Lists available providers and their capabilities
cost-analysis Analyzes historical request costs
health-report Returns system health and connectivity status
list-capabilities Enumerates all available A2A skills

Each skill in src/lib/a2a/skills/*.ts is self-contained and can be invoked independently.

Using the Task Manager Programmatically

For internal services or other OmniRoute instances, you can bypass HTTP and interact directly with the task manager.

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

const tm = getTaskManager();

// Create a task object
const task = tm.createTask({
  skill: "cost-analysis",
  messages: [{ role: "system", content: "Analyze the cost of the last 100 requests." }],
});

// Resolve the skill handler from the registry
const handler = A2A_SKILL_HANDLERS[task.skill];

// Execute with automatic state transitions
await executeA2ATaskWithState(tm, task, handler);

The executeA2ATaskWithState() wrapper in src/lib/a2a/taskExecution.ts handles all state transitions: setting working before execution, then completed with result artifacts, or failed with error details if an exception occurs.

Streaming Task Progress with Server-Sent Events

When a client sends Accept: text/event-stream, OmniRoute switches to streaming mode. The createA2AStream() function in src/lib/a2a/streaming.ts formats incremental state updates as SSE events.

import { createA2AStream, SSE_HEADERS } from "@/lib/a2a/streaming";

export async function GET(request: Request) {
  const taskId = request.headers.get("x-task-id");
  const tm = getTaskManager();
  const task = tm.getTask(taskId!);
  
  if (!task) {
    return new Response("Task not found", { status: 404 });
  }

  const stream = createA2AStream(task);
  return new Response(stream, { headers: SSE_HEADERS });
}

This enables real-time monitoring—useful for long-running tasks like batch cost analysis or provider health checks.

Why OmniRoute Uses JSON-RPC 2.0 for A2A

The JSON-RPC 2.0 foundation provides several advantages for agent-to-agent communication:

  • Versioned contracts — Requests and responses follow a strict schema
  • Batched operations — Multiple calls in a single HTTP request
  • Idempotency controls — Through metadata like requestId for deduplication
  • Minimal overhead — Lightweight compared to REST or GraphQL for machine-to-machine traffic

Observability and Monitoring

The A2A protocol exposes task events and statistics through /api/a2a/* endpoints. External monitoring tools can track:

  • Task queue depth and processing latency
  • Skill-specific success and failure rates
  • Provider selection patterns from smart-routing decisions

This observability layer makes the A2A protocol suitable for production multi-agent deployments where audit trails and performance metrics are required.

Summary

  • The A2A protocol is OmniRoute's JSON-RPC 2.0 interface for agent-to-agent communication, enabling external systems to submit tasks to built-in skills
  • Task lifecycle is managed by A2ATaskManager in src/lib/a2a/taskManager.ts with states: Submitted → Working → Completed | Failed | Cancelled
  • Skill handlers are registered in A2A_SKILL_HANDLERS (src/lib/a2a/taskExecution.ts) and executed asynchronously with automatic state management
  • Streaming support via createA2AStream() uses Server-Sent Events for real-time progress updates
  • Six built-in skills provide smart-routing, quota-management, provider-discovery, cost-analysis, health-report, and list-capabilities

Frequently Asked Questions

What transport does OmniRoute's A2A protocol use?

OmniRoute's A2A protocol uses HTTP with JSON-RPC 2.0 for request-response interactions, with optional Server-Sent Events (SSE) for streaming task updates. The primary endpoint is /a2a defined in src/app/a2a/route.ts.

How does OmniRoute handle A2A task failures?

When a skill throws an exception, executeA2ATaskWithState() in src/lib/a2a/taskExecution.ts catches the error, transitions the task state to failed, and attaches an error artifact containing the message and stack trace. The caller receives the final state via HTTP response or SSE stream.

Can I add custom skills to OmniRoute's A2A protocol?

Yes. Create a new TypeScript file in src/lib/a2a/skills/, implement the skill handler interface, and register it in A2A_SKILL_HANDLERS in src/lib/a2a/taskExecution.ts. Update src/shared/constants/agentSkills.ts to include the skill in the UI catalog.

Is A2A task state persisted across OmniRoute restarts?

By default, tasks are stored in an in-memory Map with periodic TTL cleanup. For persistence across restarts, configure the A2ATaskManager to use its SQLite backend, which stores task state to disk with configurable retention policies.

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 →