How the A2A Agent Protocol Integration Works in OmniRoute: A Deep Technical Dive

OmniRoute implements the A2A (Agent-to-Agent) Protocol v0.3 as a JSON-RPC 2.0 service that enables autonomous agents to discover capabilities, submit tasks, and stream results through a standardized interface.

The A2A protocol integration transforms OmniRoute into a discoverable, composable agent that can participate in multi-agent systems. According to the OmniRoute source code, this implementation centers on three architectural pillars: a canonical JSON-RPC endpoint, a stateful task manager, and a pluggable skill dispatcher that maps protocol methods to business logic handlers.

Core Architecture: Three Components Driving A2A Integration

OmniRoute's A2A implementation lives under src/app/a2a/ and src/lib/a2a/, with clear separation between transport, lifecycle management, and execution.

JSON-RPC 2.0 Endpoint (src/app/a2a/route.ts)

The POST /a2a endpoint serves as the single entry point for all A2A interactions. It exposes four primary methods:

  • message/send – synchronous request-response for immediate results
  • message/stream – initiates Server-Sent Events (SSE) for incremental delivery
  • tasks/get – queries task status and artifacts by UUID
  • tasks/cancel – aborts an in-progress task

The router enforces strict JSON-RPC 2.0 compliance. Invalid payloads trigger standard error codes: -32700 for parse errors, -32600 for invalid requests, -32601 for unknown methods, and -32602 for invalid parameters.

Task Manager (src/lib/a2a/taskManager.ts)

The A2ATask state machine tracks every request from submission through completion. Key characteristics include:

  • UUID assignment – each task receives a unique identifier
  • Default 5-minute TTL – automatic expiration prevents resource leaks
  • State transitionssubmittedworkingcompleted | failed | cancelled
  • Statistics exposuregetStats() returns counts per state, total tasks, and active streams

Skill Dispatcher (src/lib/a2a/taskExecution.ts)

The A2A_SKILL_HANDLERS registry maps skill names to concrete implementations. When a request arrives, executeA2ATaskWithState retrieves the appropriate handler, executes it with the full task context, and returns structured { artifacts, metadata } results.

Agent Discovery: The Agent Card Protocol

Before invoking capabilities, A2A agents discover what OmniRoute offers via the Agent Card – a JSON document served at /.well-known/agent.json [^2^]. This card contains:

  • Node name and version (derived from package.json)
  • Available A2A skills with descriptions
  • Authentication requirements
  • One-hour cache lifetime

The generator implementation in src/app/.well-known/agent.json/route.ts enables dynamic capability advertisement without manual configuration.

Request Flow: From HTTP Request to Skill Execution

Every A2A request traverses seven validation and execution stages:

  1. Authentication – When OMNIROUTE_API_KEY is configured, the authenticate function validates Authorization: Bearer … headers; otherwise, the endpoint remains open.

  2. Enablement check – The rejectIfA2ADisabled helper verifies a2aEnabled in settings, returning error -32000 if the service is disabled (default: off).

  3. JSON-RPC parsing – Request bodies must contain "jsonrpc": "2.0" and a recognized method.

  4. Task creationtaskManager.createTask instantiates an A2ATask with skill name, message array, and optional metadata.

  5. Skill execution – The dispatcher invokes the matched handler from A2A_SKILL_HANDLERS.

  6. State updates – The manager transitions through working to terminal states, capturing errors for failed status.

  7. Response formattingmessage/send returns complete JSON-RPC responses; message/stream opens SSE connections via createA2AStream in src/lib/a2a/streaming.ts.

Built-In A2A Skills: Six Capabilities for Agent Cooperation

OmniRoute ships with six production-ready skills in src/lib/a2a/skills/:

Skill File Function
Smart Routing smartRouting.ts Selects optimal provider/combo for user prompts
Quota Management quotaManagement.ts Reports per-provider usage and limits
Provider Discovery providerDiscovery.ts Lists installed providers with capabilities
Cost Analysis costAnalysis.ts Estimates request and conversation costs
Health Report healthReport.ts Summarizes circuit-breaker and provider health
List Capabilities listCapabilities.ts Returns full skill catalog for dynamic discovery

Each skill receives the complete A2ATask object and returns structured artifacts. New skills require only: (1) a module under src/lib/a2a/skills/, and (2) registration in A2A_SKILL_HANDLERS.

Streaming Support: Real-Time Agent Collaboration

The message/stream method enables low-latency, incremental delivery through Server-Sent Events. The implementation in src/lib/a2a/streaming.ts uses standard SSE_HEADERS and pushes chunks as they become available—critical for long-running generation tasks where agents shouldn't block waiting for completion.

Practical Implementation: Code Examples

Synchronous Task Invocation

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

This pattern from docs/frameworks/A2A-SERVER.md (lines 55-68) demonstrates the complete request structure: JSON-RPC envelope, skill selection, message array, and execution metadata.

Streaming Response Consumption

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

The createA2AStream helper manages SSE formatting and connection lifecycle per src/lib/a2a/streaming.ts.

Observability and Debugging

Two mechanisms support production monitoring:

  • Routing decisionslogRoutingDecision in src/lib/a2a/routingLogger.ts records every smart-routing choice
  • Task statisticsgetStats() exposes state distributions and stream counts for health dashboards

These complement OmniRoute's existing telemetry without requiring separate A2A-specific instrumentation.

Summary

  • A2A protocol integration in OmniRoute follows the v0.3 specification through a clean JSON-RPC 2.0 interface at POST /a2a
  • Three core components handle transport (route.ts), lifecycle (taskManager.ts), and execution (taskExecution.ts with A2A_SKILL_HANDLERS)
  • Agent discovery works via /.well-known/agent.json with one-hour caching
  • Six built-in skills cover routing, quotas, discovery, cost, health, and capability enumeration
  • Streaming via SSE enables real-time collaboration without blocking waits
  • Pluggable architecture allows new skills through module creation and registry insertion

Frequently Asked Questions

What authentication does OmniRoute's A2A endpoint require?

Authentication is optional and environment-controlled. When OMNIROUTE_API_KEY is set, all requests must include Authorization: Bearer YOUR_KEY; otherwise, the endpoint accepts unauthenticated traffic. The authenticate function in src/app/a2a/route.ts implements this logic, returning appropriate JSON-RPC errors for missing or invalid credentials.

How do I enable or disable the A2A service?

The service is disabled by default. Set a2aEnabled: true in OmniRoute settings to activate it. The rejectIfA2ADisabled helper checks this flag and returns a -32000 error code for any requests when disabled, preventing accidental exposure of agent capabilities.

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

Yes. Create a TypeScript module under src/lib/a2a/skills/ that exports a handler function receiving an A2ATask and returning { artifacts, metadata }. Then register the skill name and handler in the A2A_SKILL_HANDLERS map within src/lib/a2a/taskExecution.ts. No changes to the JSON-RPC router are required.

What's the difference between message/send and message/stream?

message/send returns complete results in a single JSON-RPC response, suitable for fast operations. message/stream opens an SSE connection that pushes incremental artifacts as they're generated—essential for long-running tasks like large language model generation where partial results improve perceived responsiveness.

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 →