How the A2A v0.3 Protocol Works in OmniRoute: JSON-RPC Task Lifecycle Explained

TLDR: The A2A v0.3 Protocol in OmniRoute is a JSON-RPC 2.0 service that manages autonomous agent-to-agent tasks through a strict state machine (submitted → working → completed/failed/cancelled), with support for Server-Sent Events (SSE) streaming, Zod schema validation, and comprehensive routing observability.

OmniRoute implements the A2A v0.3 (Agent-to-Agent) protocol as a lightweight framework for executing and monitoring autonomous tasks between AI agents. Located in the diegosouzapw/OmniRoute repository, the protocol exposes skills as JSON-RPC endpoints, providing lifecycle management, real-time result streaming, and detailed logging of routing decisions for quota and cost analysis.

Architecture Overview

The protocol implementation resides in src/lib/a2a and consists of five interconnected layers:

  • Task Lifecycle Management (src/lib/a2a/taskManager.ts): Handles creation, state transitions, and expiration via the A2ATaskManager class.
  • Task Execution (src/lib/a2a/taskExecution.ts): Dispatches tasks to skills and orchestrates result streaming.
  • Routing & Logging (src/lib/a2a/routingLogger.ts): Captures routing decisions including quota, health, cost, and latency factors.
  • Skill Implementations (src/lib/a2a/skills/*.ts): Self-contained modules providing business logic for specific capabilities like smart routing or quota management.
  • Streaming Layer (src/lib/a2a/streaming.ts): Wraps execution in SSE-compatible ReadableStream for real-time client updates.

Task Lifecycle and State Machine

The core of the A2A v0.3 Protocol is a strict state machine defined in src/lib/a2a/taskManager.ts. Tasks progress through immutable states with validated transitions enforced by the VALID_TRANSITIONS constant.

Allowed transitions include:

  • submittedworking, failed, or cancelled
  • workingcompleted, failed, or cancelled

Any attempt to perform an illegal transition throws an immediate error (Invalid transition: …). Each state change is recorded as a TaskEvent with timestamps for full auditability.

Creating a Task

Clients initiate tasks by POSTing a JSON-RPC request to /api/a2a. The task.create method expects a skill identifier, message array, and optional metadata.

{
  "jsonrpc": "2.0",
  "id": "12345",
  "method": "task.create",
  "params": {
    "skill": "smartRouting",
    "messages": [{ "role": "user", "content": "Find the cheapest provider for GPT‑4" }],
    "metadata": { "requestId": "req-987" }
  }
}

The A2ATaskManager.createTask() method generates a UUID, sets a 5-minute expiration timestamp, and stores the task in an in-memory Map (or SQLite persistence in production). The response includes the task ID and initial submitted state:

{
  "jsonrpc": "2.0",
  "id": "12345",
  "result": {
    "id": "b8c9e6d2‑3f4a‑44e2‑a9c5‑9f9b1a2f8d6e",
    "skill": "smartRouting",
    "state": "submitted",
    "createdAt": "2026-07-31T12:34:56.789Z",
    "expiresAt": "2026-07-31T12:39:56.789Z"
  }
}

Task Execution and Skills

When a task transitions to working, the task execution engine (src/lib/a2a/taskExecution.ts) dynamically loads the requested skill module from src/lib/a2a/skills/*.ts. Each skill receives the task payload and returns TaskArtifacts with types text, json, or error.

Skills often integrate with other OmniRoute services—such as combo routing or quota management—to perform complex operations. The execution layer appends artifacts to the task record and manages the transition to completed or failed based on skill output.

Routing Observability

During execution, routingLogger.ts captures every routing decision via logRoutingDecision(). The system records factors including quota availability, provider health, cost estimates, and latency metrics to an in-memory list (or SQLite routing_decisions table in production). This data powers the omniroute_explain_route analysis feature for debugging and optimization.

Streaming Results

The A2A v0.3 Protocol supports both synchronous JSON responses and asynchronous Server-Sent Events (SSE) streaming. When a client includes Accept: text/event-stream, src/lib/a2a/streaming.ts creates a ReadableStream and calls manager.beginStream() to track active connections.

Artifacts stream to the client as they are generated by the skill. When execution completes, endStream() decrements the active stream counter and closes the connection.

Example client implementation:

const evtSource = new EventSource(
  '/api/a2a?method=task.create&skill=smartRouting'
);

evtSource.onmessage = (e) => {
  const payload = JSON.parse(e.data);
  console.log('Chunk:', payload);
};

evtSource.onerror = () => {
  evtSource.close();
};

Cleanup and Expiration

The A2ATaskManager runs a background setInterval timer that executes cleanupExpired() every minute. Tasks exceeding their expiresAt timestamp are automatically marked as failed if still in submitted or working state. This prevents stale tasks from consuming resources indefinitely.

API Reference

All methods are validated with Zod schemas for type safety. The primary JSON-RPC methods exposed by the A2A server include:

  • task.create — Initialize a new task and return the task ID
  • task.get — Retrieve full task record and artifacts
  • task.update — Internal state transition calls
  • task.cancel — Move task to cancelled state
  • task.list — Paginated query with state/skill filters
  • task.stats — Aggregate statistics (A2ATaskStats)

Practical Implementation Examples

Creating a Task with Node.js

import fetch from 'node-fetch';

async function createTask() {
  const resp = await fetch('https://your.omniroute.instance/api/a2a', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 'req-001',
      method: 'task.create',
      params: {
        skill: 'smartRouting',
        messages: [{ role: 'user', content: 'Find cheapest GPT‑4 provider' }],
        metadata: { requestId: 'abc-123' },
      },
    }),
  });

  const data = await resp.json();
  return data.result.id;
}

Polling for Task Status

async function getTask(taskId: string) {
  const resp = await fetch('https://your.omniroute.instance/api/a2a', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 'req-002',
      method: 'task.get',
      params: { taskId },
    }),
  });

  const { result } = await resp.json();
  console.log(`Task ${taskId} is ${result.state}`);
  console.table(result.artifacts);
}

Cancelling a Running Task

await fetch('https://your.omniroute.instance/api/a2a', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    jsonrpc: '2.0',
    id: 'req-003',
    method: 'task.cancel',
    params: { taskId: 'b8c9e6d2‑3f4a‑44e2‑a9c5‑9f9b1a2f8d6e' },
  }),
});

Summary

The A2A v0.3 Protocol in OmniRoute provides a robust foundation for agent-to-agent communication through:

  • Strict state machine enforcement in src/lib/a2a/taskManager.ts with validated transitions between submitted, working, completed, failed, and cancelled states
  • JSON-RPC 2.0 API surface with Zod schema validation for type-safe task operations
  • Dual-mode response handling supporting both synchronous JSON and SSE streaming via src/lib/a2a/streaming.ts
  • Comprehensive routing observability logging quota, cost, and latency factors in src/lib/a2a/routingLogger.ts
  • Modular skill architecture in src/lib/a2a/skills/*.ts for extensible business logic
  • Automatic expiration cleanup preventing resource leaks from abandoned tasks

Frequently Asked Questions

What is the A2A v0.3 Protocol in OmniRoute?

The A2A v0.3 Protocol is a JSON-RPC 2.0-based service implementation that enables autonomous agents to create, execute, and monitor tasks remotely. According to the OmniRoute source code, it treats each request as a discrete "task" that invokes specific skills on the server, with strict state management and optional Server-Sent Events streaming for real-time updates.

How does task state management work in the A2A v0.3 Protocol?

Task states follow a finite state machine defined in src/lib/a2a/taskManager.ts with the VALID_TRANSITIONS constant. Tasks start in submitted, transition to working during execution, and conclude as completed, failed, or cancelled. The A2ATaskManager validates every transition and throws an error for illegal state changes, ensuring consistency across distributed agent interactions.

What happens when an A2A task expires?

A background timer in A2ATaskManager runs cleanupExpired() every 60 seconds to check task timestamps. Any task past its expiresAt time (default 5 minutes from creation) that remains in submitted or working state is automatically marked as failed. This mechanism, implemented in src/lib/a2a/taskManager.ts, prevents resource exhaustion from orphaned tasks.

How does OmniRoute log routing decisions in A2A tasks?

During task execution, the logRoutingDecision() function in src/lib/a2a/routingLogger.ts captures routing factors including quota availability, provider health status, cost estimates, and latency metrics. In development, these records store in an in-memory list, while production deployments use a SQLite routing_decisions table. This data enables post-hoc analysis and debugging through the omniroute_explain_route utility.

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 →