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

> Explore the A2A v0.3 Protocol in OmniRoute. Understand its JSON-RPC task lifecycle from submitted to completed using a state machine, SSE streaming, Zod validation, and observability.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: internals
- Published: 2026-07-31

---

**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`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskManager.ts)): Handles creation, state transitions, and expiration via the `A2ATaskManager` class.
- **Task Execution** ([`src/lib/a2a/taskExecution.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskExecution.ts)): Dispatches tasks to skills and orchestrates result streaming.
- **Routing & Logging** ([`src/lib/a2a/routingLogger.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/taskManager.ts). Tasks progress through immutable states with validated transitions enforced by the `VALID_TRANSITIONS` constant.

Allowed transitions include:
- `submitted` → `working`, `failed`, or `cancelled`
- `working` → `completed`, `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.

```json
{
  "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:

```json
{
  "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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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:

```javascript
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

```typescript
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

```typescript
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

```typescript
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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/streaming.ts)
- Comprehensive routing observability logging quota, cost, and latency factors in [`src/lib/a2a/routingLogger.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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.