How the A2A Agent Protocol Enables Communication Between OmniRoute and Other Agents

OmniRoute implements the A2A (Agent-to-Agent) Protocol v0.3 as a lightweight JSON-RPC 2.0 service that lets autonomous agents discover, invoke, and stream results from one another through a standardized POST /a2a endpoint.

The A2A agent protocol powers OmniRoute's interoperability with external agent systems. This open standard, implemented in the diegosouzapw/OmniRoute repository, transforms the routing engine into a discoverable, callable service that any compliant agent can integrate with. The protocol rests on three architectural pillars: a JSON-RPC 2.0 entry point, a stateful task manager, and a pluggable skill dispatcher.

Core Components of OmniRoute's A2A Implementation

JSON-RPC 2.0 Endpoint

All A2A communication flows through a single canonical entry point. In src/app/a2a/route.ts, the server exposes POST /a2a and accepts four standard methods:

  • message/send — synchronous request/response
  • message/stream — Server-Sent Events for incremental updates
  • tasks/get — query task status by UUID
  • tasks/cancel — terminate an in-flight task

The endpoint validates jsonrpc: "2.0" in every payload and returns standard JSON-RPC error codes for malformed requests (-32700), invalid requests (-32600), unknown methods (-32601), or invalid parameters (-32602).

Task Manager

The A2ATaskManager class in src/lib/a2a/taskManager.ts tracks the full lifecycle of every request:

  1. Submitted → task created with UUID and 5-minute TTL
  2. Working → skill handler executing
  3. Completed | Failed | Cancelled → terminal states

The manager enforces timeouts, persists state transitions, and exposes getStats() for observability. Each task carries its skill name, message array, and optional metadata.

Skill Dispatcher

Concrete business logic lives in src/lib/a2a/taskExecution.ts. The A2A_SKILL_HANDLERS registry maps skill names to TypeScript modules. When a task arrives, executeA2ATaskWithState invokes the matching handler, which returns { artifacts, metadata } for the response.

Agent Discovery via the Agent Card

Before invoking OmniRoute, external agents discover capabilities by fetching /.well-known/agent.json. This JSON document, generated in src/app/.well-known/agent.json/route.ts, contains:

  • Node name and version (pulled from package.json)
  • List of available A2A skills with input schemas
  • Authentication requirements

The card is cached for one hour to reduce overhead.

Request Flow Step-by-Step

Understanding how the A2A agent protocol processes a request helps with debugging and integration:

  1. Authentication check — If OMNIROUTE_API_KEY is configured, the Authorization: Bearer <token> header must match; otherwise the endpoint remains open
  2. Enabled toggle — The rejectIfA2ADisabled helper checks a2aEnabled in settings, returning error -32000 if disabled
  3. JSON-RPC parsing — Request body validated against the 2.0 specification
  4. Task creationtaskManager.createTask instantiates A2ATask with UUID and TTL
  5. Skill execution — Handler from A2A_SKILL_HANDLERS receives the task object
  6. State updates — Manager transitions through working to completed or failed
  7. Response formatting — Single JSON object for message/send, SSE stream for message/stream

Built-In A2A Skills

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

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

Adding custom skills requires creating a module under src/lib/a2a/skills/ and registering it in A2A_SKILL_HANDLERS.

Streaming Support with Server-Sent Events

The message/stream method enables real-time collaboration. Implemented in src/lib/a2a/streaming.ts, this returns SSE with headers defined in SSE_HEADERS. Calling agents receive:

  • Incremental artifacts (partial text generations)
  • Progress metadata
  • Final state and complete artifacts

This avoids blocking waits for long-running operations like multi-provider routing chains.

Code Examples for A2A Integration

Synchronous Request with cURL

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 matches the documented snippet in docs/frameworks/A2A-SERVER.md lines 55-68.

Streaming Response in Node.js

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 streaming logic is defined in src/lib/a2a/streaming.ts.

Query Task Status

curl -X POST http://localhost:20128/a2a \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_KEY" \
  -d '{"jsonrpc":"2.0","id":"2","method":"tasks/get","params":{"taskId":"<TASK_UUID>"}}'

The tasks/get handler appears in src/app/a2a/route.ts approximately lines 90-110.

Discover Available Capabilities

curl http://localhost:20128/a2a \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_KEY" \
  -d '{"jsonrpc":"2.0","id":"cap","method":"list-capabilities","params":{}}'

This invokes the skill in src/lib/a2a/skills/listCapabilities.ts.

Observability and Logging

Every smart-routing decision is logged via logRoutingDecision in src/lib/a2a/routingLogger.ts. The task manager's getStats() method exposes:

  • Task counts per state (submitted, working, completed, failed, cancelled)
  • Total tasks processed
  • Active SSE stream count

Key Implementation Files

File Role in A2A Agent Protocol
src/app/a2a/route.ts Main JSON-RPC router, method dispatch, error handling
src/lib/a2a/taskManager.ts Task lifecycle, state machine, TTL enforcement
src/lib/a2a/taskExecution.ts Skill handler registry, execution wrapper
src/lib/a2a/streaming.ts SSE stream creation and header constants
src/lib/a2a/skills/*.ts Concrete skill implementations
docs/frameworks/A2A-SERVER.md Authoritative protocol documentation
src/app/.well-known/agent.json/route.ts Agent Card generation for discovery

Summary

  • The A2A agent protocol in OmniRoute v0.3 uses JSON-RPC 2.0 over HTTP with a single POST /a2a endpoint
  • Agent discovery works via /.well-known/agent.json for capability advertisement
  • Six built-in skills handle routing, quotas, providers, cost, health, and capability listing
  • Streaming responses use Server-Sent Events for incremental results without blocking
  • Task lifecycle management includes UUID tracking, 5-minute TTL, and state transitions
  • Pluggable architecture lets developers add custom skills under src/lib/a2a/skills/

Frequently Asked Questions

What authentication does the A2A endpoint require?

Authentication is optional and controlled by the OMNIROUTE_API_KEY environment variable. When set, all requests must include Authorization: Bearer <token> matching that key. If unset, the endpoint accepts unauthenticated requests. The authenticate function in src/app/a2a/route.ts enforces this policy.

How do I enable or disable A2A functionality?

A2A is disabled by default. Set a2aEnabled: true in your OmniRoute configuration to activate the endpoint. When disabled, the rejectIfA2ADisabled helper returns JSON-RPC error -32000 for any incoming request.

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

Yes. Create a TypeScript module under src/lib/a2a/skills/ exporting a handler function, then register it in A2A_SKILL_HANDLERS within src/lib/a2a/taskExecution.ts. The handler receives the full A2ATask object and must return { artifacts, metadata }. The existing listCapabilities.ts skill demonstrates the required pattern.

What happens when a task exceeds its time limit?

The task manager enforces a default 5-minute TTL on all tasks. Expired tasks transition to failed state automatically. This prevents resource leaks from abandoned or hung requests.

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 →