A2A v0.3 Protocol vs ACP in OmniRoute: Key Differences Explained
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 (lines 3-5). Clients communicate over plain HTTP(S) by POSTing to the /a2a endpoint:
// 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 (lines 2-9, 55-63), the manager creates a subprocess and communicates through its stdin/stdout streams:
// 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 |
Probing installed CLI binaries via version commands |
| Source | src/lib/a2a/README.md (lines 42-55) |
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 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
// 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 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 (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 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 - ACP: Emits Node.js events (
stdout,stderr,exit,error) that subscribers can handle for custom logging (manager.tslines 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
CustomAgentDefin the settings database; the registry auto-detects the binary at runtime (registry lines 88-96)
Code Examples
Calling A2A v0.3 from 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, src/lib/a2a/skills/smartRouting.ts
Controlling CLI Agents with ACP
// 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, 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 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) 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 (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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →