How the A2A v0.3 Protocol Facilitates Agent-to-Agent Communication in OmniRoute

The A2A v0.3 protocol in OmniRoute enables autonomous agents to discover, invoke, and stream results from one another through a lightweight JSON-RPC 2.0 service built on a task manager, skill dispatcher, and streaming interface.

OmniRoute implements the A2A (Agent-to-Agent) Protocol version 0.3 to provide standardized agent-to-agent communication across distributed systems. This protocol allows external agents to leverage OmniRoute’s smart routing, quota management, and provider discovery capabilities through a clean, standards-based interface defined in docs/frameworks/A2A-SERVER.md.

Core Architecture of the A2A v0.3 Protocol

The protocol architecture rests on three primary components that handle request ingestion, lifecycle management, and business logic execution.

JSON-RPC 2.0 Endpoint

All A2A interactions flow through the canonical entry point defined in src/app/a2a/route.ts. This Next.js route handler exposes the POST /a2a endpoint and implements the JSON-RPC 2.0 specification. The supported methods include:

  • message/send – Synchronous request/response pattern
  • message/stream – Server-Sent Events (SSE) for real-time updates
  • tasks/get – Retrieve task status and artifacts
  • tasks/cancel – Abort an in-flight task

The router validates the "jsonrpc": "2.0" header and returns standard JSON-RPC error codes (-32700, -32600, -32601, -32602) for malformed requests.

Task Manager

Located in src/lib/a2a/taskManager.ts, the task manager maintains the full lifecycle state machine for every request. It assigns a UUID to each task, enforces a default 5-minute TTL, and tracks transitions from submittedworkingcompleted or failed. The createTask function initializes the record, while getStats exposes operational metrics including active stream counts and state distributions.

Skill Dispatcher

The dispatcher in src/lib/a2a/taskExecution.ts maps skill names to concrete handlers via the A2A_SKILL_HANDLERS registry. When a request specifies a skill (e.g., "smart-routing"), the dispatcher invokes executeA2ATaskWithState, which runs the handler and manages state transitions through the task manager.

Agent Discovery via the Agent Card

Before invoking capabilities, A2A clients discover what an OmniRoute node offers by requesting /.well-known/agent.json. This Agent Card—generated in src/app/.well-known/agent.json/route.ts—contains the node’s name, version, available skills, and authentication requirements. The response is cached for one hour and provides the canonical manifest for automated agent negotiation.

Request Lifecycle and Method Handlers

Every A2A request passes through a rigorous validation and execution pipeline:

  1. Authentication – If OMNIROUTE_API_KEY is configured, the authenticate function requires a matching Authorization: Bearer <token> header.
  2. Feature Toggle – The rejectIfA2ADisabled helper checks the a2aEnabled setting, returning error code -32000 if the endpoint is disabled.
  3. Task Creation – Valid requests trigger taskManager.createTask, persisting the skill name, message array, and metadata.
  4. Skill Execution – The appropriate handler from A2A_SKILL_HANDLERS receives the A2ATask object and returns { artifacts, metadata }.
  5. State Updates – The manager updates the task to working, then finalizes to completed or failed depending on handler success.
  6. Response Formation – Synchronous calls return a JSON-RPC response object, while streaming calls initiate SSE via createA2AStream from src/lib/a2a/streaming.ts.

Built-in Skills for Agent Capabilities

OmniRoute ships with six native skills located in src/lib/a2a/skills/, each exportable for the A2A_SKILL_HANDLERS registry:

  • Smart Routing (smartRouting.ts) – Selects optimal provider combinations for prompt execution.
  • Quota Management (quotaManagement.ts) – Reports per-provider usage and limits.
  • Provider Discovery (providerDiscovery.ts) – Lists installed providers and their capabilities.
  • Cost Analysis (costAnalysis.ts) – Estimates token and monetary costs for requests.
  • Health Report (healthReport.ts) – Summarizes circuit-breaker status and provider health.
  • List Capabilities (listCapabilities.ts) – Returns the full skill catalog for dynamic discovery.

New capabilities are added by creating a TypeScript module in the skills directory and registering it in src/lib/a2a/taskExecution.ts.

Real-time Communication with SSE Streaming

The message/stream method enables progressive result delivery through Server-Sent Events. When invoked, the server establishes an SSE connection using headers defined in SSE_HEADERS (located in src/lib/a2a/streaming.ts) and pushes incremental chunks as the skill handler produces artifacts. This allows calling agents to process partial results—such as streaming text generation—without waiting for the full operation to complete.

Practical Implementation Examples

Synchronous Smart Routing Request

Invoke the smart-routing skill with a standard JSON-RPC payload:

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

Streaming Response Handling

Use message/stream for real-time results:

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

Task Status Query

Retrieve the current state of a specific task:

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

Capability Discovery

Query available skills dynamically:

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

Summary

Frequently Asked Questions

What authentication mechanism does the A2A v0.3 protocol use in OmniRoute?

OmniRoute validates requests using a Bearer token scheme. When the OMNIROUTE_API_KEY environment variable is set, the authenticate function in src/app/a2a/route.ts requires an Authorization: Bearer <token> header matching that key. If the variable is unset, the endpoint operates without authentication.

How does OmniRoute handle long-running agent tasks?

Long-running tasks utilize the message/stream method, which opens an SSE connection via createA2AStream in src/lib/a2a/streaming.ts. This allows the server to push incremental artifacts while the task status remains working, avoiding timeouts that would occur with synchronous HTTP requests.

Can I add custom skills to the OmniRoute A2A implementation?

Yes. Create a new TypeScript file in src/lib/a2a/skills/ that exports a handler function receiving an A2ATask object and returning { artifacts, metadata }. Register this handler in the A2A_SKILL_HANDLERS object within src/lib/a2a/taskExecution.ts to make it available for invocation via the JSON-RPC interface.

What happens if the A2A endpoint is disabled in OmniRoute?

The rejectIfA2ADisabled helper checks the a2aEnabled configuration flag before processing any request. If disabled, the server returns a JSON-RPC error with code -32000 and a message indicating that the A2A service is not active, preventing accidental exposure of agent capabilities.

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 →