Files
OmniRoute/docs/frameworks/A2A-SERVER.md
Diego Rodrigues de Sa e Souza 5ac4637816 docs: reorganize docs/ + accuracy audit + drop Node 20 (rebased onto current release tip) (#5262)
* docs: move superpowers/research artifacts to isolated _tasks repo + docs tree cleanup

- Move docs/superpowers/{plans,specs} and docs/research/* into the gitignored,
  separately-versioned _tasks/ repo; untrack the two tracked research design docs.
- Add CLAUDE.md "Planning & Research Artifacts" section overriding the superpowers
  default save paths (docs/... -> _tasks/...); align REPOSITORY_MAP and
  DOCUMENTATION_OVERHAUL_PLAN with the new convention.
- Drop 4 now-obsolete /api/discovery/* entries from check-docs-symbols allowlist
  (stale-enforcement) and refresh code/spec path comments to _tasks/...
- Sweeps in concurrent docs-tree restructuring (root-level provider/guide docs,
  compression spec cleanup, .mcp.json.example removal).

* docs: reorganize docs/ tree + fix stale facts across ~26 docs

Phase A — reorganization:
- Move 7 orphan root docs into subfolders (providers/ created; TIERS+USAGE_QUOTA→guides/;
  plugins+PLUGIN_SDK→frameworks/); delete 8 obsolete/redundant docs (SUBMIT_PR superseded
  by CONTRIBUTING; DOCUMENTATION_OVERHAUL_PLAN; INCIDENT_RESPONSE/PERF_BUDGETS/THREAT_MODEL;
  3 ops snapshots). Rebuild README index (was missing ~40 files) + per-folder meta.json nav.
- Clean 14 dangling doc-path references in bin/ ops scripts, scripts/, workflow, tests;
  fix the dockerignore-docs-coverage required-docs path (PROVIDERS→providers/CLAUDE_WEB).

Phase B — content accuracy (verified against code, not the audit summary):
- Functional: ENVIRONMENT flag defaults (INPUT_SANITIZER/MCP_ENFORCE_SCOPES=true,
  COMPRESS_DESCRIPTIONS=false, dynamic heap); MCP-SERVER notion tool names (omniroute_*→
  notion_*) + counts 87→94; coverage gate 75/70→60/60/60/60 (RELEASE_CHECKLIST, COVERAGE_PLAN,
  ERROR_SANITIZATION, CONTRIBUTING); pre-push hook description; regenerate PROVIDER_REFERENCE (237).
- Count drift: providers 237, executors 70, migrations 106, db modules 94, oauth 19,
  strategies 17, MCP 94, flags 38, TS 6.0, open-sse ~900/services 294 across architecture/
  frameworks/ops docs; AUTO-COMBO 9→12 factors w/ correct DEFAULT_WEIGHTS; REASONING +2
  patterns; STEALTH UA defaults; AGENT_PROTOCOLS +cursor-cloud/list-capabilities;
  LANGUAGE_PACKS +id pack.
- Kept Node 20 (runtime guard accepts 20.20.2+; only engines is stricter) and MCP scopes=13
  (mcpScopes.ts) — both were correct in the docs; corrected only the attribution.

* docs: finish content refresh — compression engines, CLI_TOKEN merge, metadata sweep

- Compression: document the additional built-in engines (CCR, headroom, ionizer,
  session-dedup) in COMPRESSION_ENGINES; clarify LLMLingua-2 is the ultra-mode SLM
  backend + cross-ref the extra engines in EXTENDING_COMPRESSION; add the id
  (Indonesian) language pack to LANGUAGE_PACKS.
- AUTO-COMBO: replace the orphan 'How tiers fit' weight table (stale weights) with a
  pointer to the canonical 12-factor DEFAULT_WEIGHTS table.
- Security: merge CLI_TOKEN_AUTH.md (legacy 32-char SHA-256 format) into CLI_TOKEN.md
  as a 'Legacy format — still accepted' section (server accepts both HMAC + legacy),
  delete CLI_TOKEN_AUTH.md, drop it from the index + security nav.
- Metadata: bump stale frontmatter (version/lastUpdated) to 3.8.40/2026-06-28 across the
  doc set audited this pass, and normalize the in-body 'Last updated' header lines to match.

* fix(runtime): drop Node 20 from supported range + align all docs/diagrams/counts

- Node minimum is now 22 (aligned with package.json engines). SUPPORTED_NODE_RANGE in
  src/shared/utils/nodeRuntimeSupport.ts (and the bin/ mirror) drops the 20.x line →
  '>=22.22.2 <23 || >=24.0.0 <27'; getNodeRuntimeSupport now rejects Node 20 as
  unsupported-major. Test updated (TDD): node-runtime-support.test.ts asserts Node 20
  rejected. Docs aligned (TROUBLESHOOTING ×2, TERMUX, RELEASE_CHECKLIST, CODEBASE,
  CLI-TOOLS, README, llm.txt + 42 i18n llm.txt mirrors, skills/cli-serve).
- Diagrams regenerated: mcp-tools-87 -> mcp-tools-94 (34 base + pool 6 = 94) and
  auto-combo-9factor -> auto-combo-12factor (correct DEFAULT_WEIGHTS); SVGs re-rendered
  via mermaid-cli; doc refs + diagrams/README updated; fixed a pre-existing broken
  resilience-3layers image path.
- CLAUDE.md + AGENTS.md aligned to real counts (237 providers, 94 MCP tools / 34 base,
  106 migrations, 94 db modules, 12-factor auto-combo, 17 strategies); README provider
  count 231 -> 237; executor count corrected to 68 (provider executors, excl base/index)
  and OAuth to 18 across architecture docs. check:docs-all now passes (0 strict drift,
  0 broken links); removed dead .mcp.json.example doc link.

* fix(services): update installer Node hint to >=22.22.2 (aligned with dropped Node 20)

* docs: realign counts to current release tip after rebase

The release tip advanced while this work was in flight (Gemini CLI provider/executor
removed by #5246, plus other PRs). Re-counted against the current code and updated:
providers 237->236, executors 68->67, OAuth modules 18->17, open-sse services 294->298;
regenerated PROVIDER_REFERENCE.md (236). check:docs-all passes (0 strict drift).

* docs(changelog) + i18n: record Node 20 drop + fix nodeIncompatibleHint

- CHANGELOG: add [3.8.40] entries for the Node 20.x removal (runtime) and the docs
  reorganization/accuracy audit.
- i18n: nodeIncompatibleHint across all 42 locales no longer lists Node 20.x as
  supported (ASCII + CJK full-width variants), aligned with the dropped Node 20.

* fix(docs): repair CI breakages from the doc moves

- test: cli-plugin-system asserted docs/dev/plugins.md exists; the file moved to
  docs/frameworks/PLUGINS.md — point the test at the new path (Unit fast-path 2/2 fix).
- frontmatter: PLUGINS.md and PLUGIN_SDK.md moved into the fumadocs-indexed
  docs/frameworks/ which requires a 'title' frontmatter; the missing frontmatter
  failed the Next.js MDX build (dast-smoke 'invalid frontmatter'). Added frontmatter
  to both, plus the providers/ docs (consistency; that folder is not indexed).
2026-06-28 22:07:18 -03:00

11 KiB

title, version, lastUpdated
title version lastUpdated
OmniRoute A2A Server Documentation 3.8.40 2026-06-28

OmniRoute A2A Server Documentation

Agent-to-Agent Protocol v0.3 — OmniRoute as an intelligent routing agent

The A2A surface has two faces:

  • JSON-RPC 2.0 at POST /a2a (canonical entry point, defined in src/app/a2a/route.ts).
  • REST under /api/a2a/* for dashboards and tooling (status, task list, cancel).

Tasks are tracked by A2ATaskManager (src/lib/a2a/taskManager.ts, default 5-minute TTL). Skills are dispatched via A2A_SKILL_HANDLERS in src/lib/a2a/taskExecution.ts.

Agent Discovery

curl http://localhost:20128/.well-known/agent.json

Returns the Agent Card describing OmniRoute's capabilities, skills, and authentication requirements.

The Agent Card's version field is sourced from process.env.npm_package_version (see src/app/.well-known/agent.json/route.ts:13), so it stays auto-synced with package.json on every release.


Authentication

All /a2a requests require an API key via the Authorization header:

Authorization: Bearer YOUR_OMNIROUTE_API_KEY

If no API key is configured on the server, authentication is bypassed.

Enablement

A2A is controlled by the Endpoints → A2A toggle and is disabled by default. When disabled, GET /api/a2a/status reports status: "disabled" and online: false; JSON-RPC calls to POST /a2a return HTTP 503 with JSON-RPC error code -32000.


JSON-RPC 2.0 Methods

message/send — Synchronous Execution

Sends a message to a skill and waits for the complete response.

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 in Python"}],
      "metadata": {"model": "auto", "combo": "fast-coding"}
    }
  }'

Response:

{
  "jsonrpc": "2.0",
  "id": "1",
  "result": {
    "task": { "id": "uuid", "state": "completed" },
    "artifacts": [{ "type": "text", "content": "..." }],
    "metadata": {
      "routing_explanation": "Selected claude-sonnet via provider \"anthropic\" (latency: 1200ms, cost: $0.003)",
      "cost_envelope": { "estimated": 0.005, "actual": 0.003, "currency": "USD" },
      "resilience_trace": [
        { "event": "primary_selected", "provider": "anthropic", "timestamp": "..." }
      ],
      "policy_verdict": { "allowed": true, "reason": "within budget and quota limits" }
    }
  }
}

message/stream — SSE Streaming

Same as message/send but returns Server-Sent Events for real-time streaming.

curl -N -X POST http://localhost:20128/a2a \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_KEY" \
  -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "message/stream",
    "params": {
      "skill": "smart-routing",
      "messages": [{"role": "user", "content": "Explain quantum computing"}]
    }
  }'

SSE Events:

data: {"jsonrpc":"2.0","method":"message/stream","params":{"task":{"id":"...","state":"working"},"chunk":{"type":"text","content":"..."}}}

: heartbeat 2026-03-03T17:00:00Z

data: {"jsonrpc":"2.0","method":"message/stream","params":{"task":{"id":"...","state":"completed"},"metadata":{...}}}

tasks/get — Query Task Status

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

tasks/cancel — Cancel a Task

curl -X POST http://localhost:20128/a2a \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_KEY" \
  -d '{"jsonrpc":"2.0","id":"3","method":"tasks/cancel","params":{"taskId":"TASK_UUID"}}'

Available Skills

OmniRoute exposes 6 A2A skills wired in src/lib/a2a/taskExecution.ts::A2A_SKILL_HANDLERS. Each skill module lives in src/lib/a2a/skills/.

Skill ID Description Tags Examples
Smart Routing smart-routing Routes a prompt through the optimal provider/combo using OmniRoute's combo engine + scoring routing, providers "Route this prompt via the best model"
Quota Management quota-management Reports per-provider quota state, helps callers decide when to throttle/switch quota, providers "Check quota for anthropic"
Provider Discovery provider-discovery Lists installed providers with capabilities, free-tier flags, OAuth status providers, discovery "What providers are available?"
Cost Analysis cost-analysis Estimates cost of a request/conversation given the catalog + recent usage cost, usage "Estimate cost for this conversation"
Health Report health-report Aggregates circuit breaker, cooldown, lockout state per provider health, resilience "Show health status of all providers"
List Capabilities list-capabilities Returns the full 42-entry Agent Skills catalog as a markdown table with raw SKILL.md URLs for context injection catalog, discovery, skills "List all OmniRoute capabilities"

Note: the Agent Card description currently advertises "36+ providers" (src/app/.well-known/agent.json/route.ts:26 and :55). The actual catalog has grown to 180+ providers — the string should be updated in a follow-up change (tracked as a separate doc/code TODO; not modified here).

list-capabilities Skill Detail

The list-capabilities skill is particularly useful for external agents that need to discover what OmniRoute exposes before sending API calls. It returns a structured markdown table artifact:

| ID | Name | Category | Area | Endpoints/Commands | Raw URL |
| --- | --- | --- | --- | --- | --- |
| omni-auth | Auth & Sessions | api | auth | POST /api/auth/login, ... | https://raw.githubusercontent.com/... |
...

Each row includes the rawUrl column so agents can immediately fetch the full SKILL.md. The metadata.totalSkills field is always 42. Implementation: src/lib/a2a/skills/listCapabilities.ts. See also AGENT-SKILLS.md.


REST API (auxiliary)

The JSON-RPC endpoint /a2a is the canonical A2A entry point. The REST endpoints below provide auxiliary access for dashboards and external tooling:

Endpoint Method Description Auth
/api/a2a/status GET Server status, registered skills (public)
/api/a2a/tasks GET List tasks with filters management
/api/a2a/tasks/[id] GET Get task by ID management
/api/a2a/tasks/[id]/cancel POST Cancel running task management
/.well-known/agent.json GET Agent Card (A2A discovery) (public, cached 3600s)

Adding a New Skill

  1. Create skill file: src/lib/a2a/skills/<your-skill>.ts

    Export an async function (task: A2ATask) => Promise<{ artifacts, metadata }>. Follow the shape of existing skills such as smartRouting.ts.

  2. Register handler: in src/lib/a2a/taskExecution.ts, add an entry to A2A_SKILL_HANDLERS:

    export const A2A_SKILL_HANDLERS = {
      // ...existing skills
      "your-skill": async (task) => {
        const skillModule = await import("./skills/yourSkill");
        return skillModule.executeYourSkill(task);
      },
    };
    
  3. Expose in Agent Card: in src/app/.well-known/agent.json/route.ts, append to the skills array:

    {
      "id": "your-skill",
      "name": "Your Skill",
      "description": "Brief, intent-focused description",
      "tags": ["routing", "quota"],
      "examples": ["Sample natural-language invocation"]
    }
    
  4. Write tests: tests/unit/a2a-<your-skill>.test.ts. Cover happy path + error path.

  5. Document the new skill in this file's Available Skills table.


Task TTL

Tasks expire after ttlMinutes (default 5 min) — configured in the A2ATaskManager constructor at src/lib/a2a/taskManager.ts:82. To customize, fork the A2ATaskManager instantiation and pass a different value (e.g., new A2ATaskManager(15) for 15-minute TTL). A background interval sweeps expired tasks every 60 seconds.


Task Lifecycle

submitted → working → completed
                    → failed
                    → cancelled
  • Tasks expire after 5 minutes by default (see Task TTL)
  • Terminal states: completed, failed, cancelled
  • Event log tracks every state transition

Error Codes

Code Meaning
-32700 Parse error (invalid JSON)
-32600 Invalid request / Unauthorized
-32601 Method or skill not found
-32602 Invalid params
-32603 Internal error
-32000 A2A endpoint is disabled

Integration Examples

Python (requests)

import requests

resp = requests.post("http://localhost:20128/a2a", json={
    "jsonrpc": "2.0", "id": "1",
    "method": "message/send",
    "params": {
        "skill": "smart-routing",
        "messages": [{"role": "user", "content": "Hello"}]
    }
}, headers={"Authorization": "Bearer YOUR_KEY"})

result = resp.json()["result"]
print(result["artifacts"][0]["content"])
print(result["metadata"]["routing_explanation"])

TypeScript (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: "1",
    method: "message/send",
    params: {
      skill: "smart-routing",
      messages: [{ role: "user", content: "Hello" }],
    },
  }),
});
const { result } = await resp.json();
console.log(result.metadata.routing_explanation);