# A2A v0.3 Protocol vs ACP in OmniRoute: Key Differences Explained

> Explore the core distinctions between A2A v0.3 protocol and ACP within OmniRoute. Understand their unique approaches to agent communication and task management.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: deep-dive
- Published: 2026-08-04

---

**The A2A v0.3 protocol is an HTTP-based JSON-RPC service with full task lifecycle management, while ACP is a local process-spawning layer for CLI agents via stdin/stdout streams.**

OmniRoute provides two distinct agent-communication layers for different integration scenarios. Understanding the differences between A2A v0.3 and ACP helps you choose the right protocol for multi-agent orchestration versus local CLI agent access. This guide breaks down the architectural distinctions with direct references to the OmniRoute source code.

## Protocol Specification and Transport

### A2A v0.3: Standardized HTTP JSON-RPC

The **A2A (Agent-to-Agent) v0.3 protocol** implements the public **JSON-RPC 2.0** specification as defined in [`src/lib/a2a/README.md`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/README.md) (lines 3-5). Clients communicate over plain HTTP(S) by POSTing to the `/a2a` endpoint:

```typescript
// Example: POST /a2a with JSON-RPC body
{
  "jsonrpc": "2.0",
  "id": "req-123",
  "method": "message/send",
  "params": {
    "skill": "smart-routing",
    "messages": [{ "role": "user", "content": "Hello" }]
  }
}

```

Responses return JSON, or optionally **Server-Sent Events (SSE)** for streaming via the `message/stream` method (README lines 34-36).

### ACP: Custom JSON-RPC over Stdio

The **ACP (Agent Client Protocol)** uses a **custom JSON-RPC-style** transport that spawns CLI binaries as child processes. In [`src/lib/acp/manager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/acp/manager.ts) (lines 2-9, 55-63), the manager creates a subprocess and communicates through its stdin/stdout streams:

```typescript
// From src/lib/acp/manager.ts – spawn implementation
const proc = spawn(binaryName, args, {
  env: { ...process.env, ...envOverrides },
  stdio: ["pipe", "pipe", "pipe"],
});

```

ACP supports optional HTTP fallback, but its primary mode is local process communication.

## Service Discovery Mechanisms

### Agent Cards vs Binary Detection

| Discovery Method | A2A v0.3 | ACP |
|------------------|----------|-----|
| Mechanism | Well-known **Agent Card** at [`/.well-known/agent.json`](https://github.com/diegosouzapw/OmniRoute/blob/main//.well-known/agent.json) | Probing **installed CLI binaries** via version commands |
| Source | [`src/lib/a2a/README.md`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/README.md) (lines 42-55) | [`src/lib/acp/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/acp/registry.ts) (lines 4-9) |
| Information | Skills, capabilities, authentication requirements | Binary paths, provider aliases, custom agent definitions |

The **Agent Card** in A2A v0.3 declares high-level capabilities like `smart-routing` and `quota-management` (README lines 85-92). ACP's **registry** detects binaries such as `codex`, `claude`, and `goose` through version probes and allows runtime registration of custom agents (registry lines 53-70, 88-96).

## Task Lifecycle and State Management

### A2A v0.3: Full State Machine with Metadata

A2A v0.3 tracks tasks through a complete lifecycle: `submitted → working → completed/failed/cancelled`. The [`taskManager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/taskManager.ts) module persists state to SQLite with TTL handling and enriches results with:

- **Routing explanation** – why a specific model was chosen
- **Cost envelope** – token usage and pricing
- **Resilience trace** – fallback paths taken
- **Policy verdict** – compliance decisions

```typescript
// Task result structure from README (lines 22-30)
{
  "id": "task-abc",
  "status": "completed",
  "artifacts": [...],
  "metadata": {
    "routingExplanation": "...",
    "cost": { "inputTokens": 150, "outputTokens": 200 },
    "policyVerdict": "allowed"
  }
}

```

### ACP: Process-Bound Sessions

ACP sessions map directly to **process lifetimes**: `spawn → alive → exit/kill`. The [`manager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/manager.ts) module (lines 15-28) defines session fields tracking process ID, start time, and idle status—but contains no built-in cost tracking or policy metadata. Callers must interpret raw CLI output themselves.

## Streaming and Response Handling

**A2A v0.3** supports native streaming through SSE, preserving task metadata across chunks. The `message/stream` method (README lines 34-36) allows real-time progress updates with full observability.

**ACP** has no native streaming implementation. The `sendPrompt` method in [`manager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/manager.ts) (lines 28-40) uses an **idle timer** to detect when the CLI process has finished writing to stdout, collecting output as a single batch.

## Authentication and Security

| Aspect | A2A v0.3 | ACP |
|--------|----------|-----|
| Method | Bearer token in HTTP header | Environment variables to spawned process |
| Enforcement | Required on every request (`Authorization: Bearer <key>`) | Handled by spawning process, not ACP manager |
| Source | README lines 99-104 | [`manager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/manager.ts) lines 58-60 |

A2A v0.3 explicitly validates tokens at the HTTP boundary. ACP delegates authentication to the underlying CLI tool—API keys pass through `envOverrides` to the subprocess, but the manager itself performs no validation.

## Observability and Extensibility

### Logging and Monitoring

- **A2A v0.3**: Every call logs routing decisions, costs, and policy outcomes through [`routingLogger.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/routingLogger.ts)
- **ACP**: Emits Node.js events (`stdout`, `stderr`, `exit`, `error`) that subscribers can handle for custom logging ([`manager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/manager.ts) lines 38-46)

### Adding New Capabilities

- **A2A v0.3**: Create a module in `src/lib/a2a/skills/*` and register with the skill engine
- **ACP**: Define a `CustomAgentDef` in the settings database; the registry auto-detects the binary at runtime (registry lines 88-96)

## Code Examples

### Calling A2A v0.3 from TypeScript

```typescript
// a2aCall.ts
import fetch from "node-fetch";

const BASE_URL = "http://localhost:20128";
const API_KEY = process.env.OMNIROUTE_API_KEY;

async function a2aCall<T>(method: string, params: Record<string, any>) {
  const resp = await fetch(`${BASE_URL}/a2a`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${API_KEY}`,
    },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: `req-${Date.now()}`,
      method,
      params,
    }),
  });
  const json = await resp.json();
  if (json.error) throw new Error(`${json.error.code}: ${json.error.message}`);
  return json.result as T;
}

// Invoke smart-routing skill
const result = await a2aCall("message/send", {
  skill: "smart-routing",
  messages: [{ role: "user", content: "Refactor this function" }],
  metadata: { model: "auto", combo: "fast-coding" },
});
console.log(result.artifacts[0].content);

```

*Source references*: [`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts), [`src/lib/a2a/skills/smartRouting.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/skills/smartRouting.ts)

### Controlling CLI Agents with ACP

```typescript
// acpDemo.ts
import { acpManager } from "./src/lib/acp";

async function demo() {
  // Spawn Codex CLI (must be installed and in ALLOWED_AGENTS)
  const session = acpManager.spawn(
    "codex",                    // agentId from registry
    "codex",                    // binary name
    ["--quiet", "--model", "gpt-4o"],
    { OPENAI_API_KEY: process.env.OPENAI_API_KEY }
  );

  // Send prompt and await response
  const reply = await acpManager.sendPrompt(
    session.id,
    "Generate a TypeScript interface for a User"
  );
  
  console.log("Response:", reply);
  acpManager.kill(session.id);  // Clean up process
}

demo().catch(console.error);

```

*Source references*: [`src/lib/acp/manager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/acp/manager.ts), [`src/lib/acp/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/acp/registry.ts)

## Summary

- **A2A v0.3** targets **multi-agent orchestration** with HTTP/JSON-RPC, standardized discovery via Agent Cards, rich task metadata, SSE streaming, and built-in cost/policy tracking
- **ACP** targets **local CLI integration** through process spawning, binary-based discovery, minimal overhead, and direct stdin/stdout access without intermediary processing
- Choose A2A v0.3 when you need observability, policy enforcement, and interoperability with LangChain/CrewAI-style orchestrators
- Choose ACP when integrating existing CLI tools like Codex or Claude Code that lack HTTP interfaces

## Frequently Asked Questions

### Can ACP agents be accessed over HTTP?

ACP's primary transport is stdin/stdout via spawned processes. The source code in [`src/lib/acp/manager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/acp/manager.ts) indicates optional HTTP support, but the implementation focuses on local binary execution. For HTTP-based access, use A2A v0.3 instead.

### Does A2A v0.3 require running a persistent server?

Yes. The A2A v0.3 protocol runs as a Next.js API route ([`src/app/a2a/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/a2a/route.ts)) that must be deployed and reachable at a stable URL. Clients authenticate with Bearer tokens for each request.

### How do I add a custom CLI agent to ACP?

Define a `CustomAgentDef` in OmniRoute's settings database. The registry in [`src/lib/acp/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/acp/registry.ts) (lines 88-96) auto-detects the binary at runtime by executing the configured version command. No server restart is required.

### Which protocol provides better cost tracking?

A2A v0.3 includes comprehensive cost tracking through [`routingLogger.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/routingLogger.ts) and task result metadata. ACP provides no built-in cost information—you must parse and calculate costs from the CLI agent's raw output yourself.