A2A & agent card
Chorus exposes a version-negotiated A2A JSON-RPC adapter over its native task, run, and delivery model. A2A tasks are authoritative Chorus execution tasks—not legacy task-shaped signals.
Supported profile
Section titled “Supported profile”| Capability | Support |
|---|---|
| Protocol versions | 0.3, 1.0 |
Empty or absent A2A-Version |
0.3 compatibility default |
| Transport | JSON-RPC over POST /a2a (and /a2a/) |
| Streaming / task subscription | Not advertised; -32004 |
| Push notifications | Not advertised; -32003 |
| Extended agent card | Not advertised; -32004 |
The A2A-Version header alone selects the contract. Chorus never guesses a
version from a method name or malformed payload. Send exactly 1.0 for v1 or
0.3 for the legacy adapter; any other non-empty value returns the standard
VersionNotSupportedError (-32009). Responses include
Vary: A2A-Version.
The reviewed contracts are pinned in test/fixtures/a2a-contract.json:
| Contract | Pin |
|---|---|
| JavaScript SDK | @a2a-js/sdk@1.0.0-beta.0 plus lockfile integrity |
| A2A TCK | 5996b79f9cefa6fc390980e383e358a66fb9e49e |
| A2A specification snapshot | 173695755607e884aa9acf8ce4feed90e32727a1 |
Discovery
Section titled “Discovery”GET /.well-known/agent-card.json is public. It advertises v1 first and v0.3
second as JSON-RPC interfaces, the Bearer authentication scheme, and one safe
generic native-task skill. It does not expose the instance’s identities or
internal roles.
The discovery endpoint follows the same A2A-Version rules. Its headerless
response is the pure legacy v0.3 projection. Clients that explicitly request
v1 receive the v1 representation and negotiate through supportedInterfaces.
Version-specific ETags, Cache-Control, and 304 revalidation prevent one
version’s card from being reused as another.
Operations
Section titled “Operations”| v1.0 method | v0.3 method | Native behavior |
|---|---|---|
SendMessage |
message/send |
Create or continue an execution task |
GetTask |
tasks/get |
Project task, latest run, history, and deliveries |
ListTasks |
— | List only visible, owned tasks with cursor pagination |
CancelTask |
tasks/cancel |
Cancel through the canonical fenced task command |
v1 uses PascalCase operations and ProtoJSON field/enum shapes. v0.3 uses slash methods and its legacy message/part/task shapes. The compatibility adapter is isolated from the canonical v1 service.
Send a v1 task
Section titled “Send a v1 task”The caller must be a ring member. Supply the ring in message metadata or a data part; when omitted, Chorus uses the caller’s first visible ring.
curl -X POST "$CHORUS_URL/a2a" \ -H "Authorization: Bearer $CHORUS_API_KEY" \ -H "A2A-Version: 1.0" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "SendMessage", "params": { "message": { "messageId": "release-audit-01", "role": "ROLE_USER", "parts": [{"text": "Audit the release boundary"}], "metadata": {"ring": "ring:dev"} }, "configuration": {"historyLength": 10, "returnImmediately": true} } }'For a follow-up, resend with a new messageId plus the returned taskId and
contextId. Replaying the same message ID and content is idempotent. Reusing a
message ID with different content or a different Chorus ring, crossing contexts
or rings, or reviving a terminal task is rejected as invalid parameters. Stable
task, context, message, and transition IDs make retries converge on one
execution graph.
Follow-up history and its idempotency fingerprints are appended in one storage transaction, so concurrent requests through different Chorus replicas cannot overwrite one another. A task retains at most 100 accepted A2A messages and at most 8 MiB of encoded history. At either limit, an exact replay still returns the existing task, while a new unique message or a same-ID/different-content collision returns invalid parameters. Entries are not evicted, so an old retained message ID never becomes silently acceptable again.
ListTasks pushes owner, ring, context, status, inclusive timestamp, and
exclusive cursor predicates into storage. One transaction obtains the exact
pre-pagination total and at most one bounded keyset page; delivery artifacts
are loaded in one batch only when requested. Results use
updated_at DESC, id DESC; opaque page tokens bind the caller and filters to
the last returned (updated_at, id) pair. Tokens remain usable if that cursor
task is subsequently updated or removed from the filter because pagination
does not look the old row up again.
Response projection is bounded as well as input. A single encoded task is
limited to 2 MiB (plus a small JSON-RPC envelope allowance). A ListTasks
page is limited to 4 MiB of encoded tasks, 100 projected delivery artifacts,
and 1,000 artifact parts. Each task projects at most 20 native deliveries and
each delivery artifact at most 20 parts. When the next task would cross a page
budget, Chorus returns the bounded prefix and anchors nextPageToken to the
last task actually emitted; the unreturned task remains first on the next
keyset page. Positive historyLength remains an upper bound as required by
A2A, so Chorus may return fewer recent messages to keep one task finite.
For ListTasks, an unset historyLength defaults to zero. Storage uses a
lightweight task projection that omits unrelated flexible metadata; an
explicit positive history request reads only one bounded recent entry per task
in a smaller internal keyset batch. This prevents retained-history bytes from
being multiplied by a 100-task inventory page before wire projection begins.
Native deliveries are a bounded Chorus-to-A2A projection rather than a separately pageable A2A collection. Clients that need every delivery or artifact reference should use the native Chorus execution APIs. Legacy task metadata is validated before SDK conversion: corrupt, deeply nested, or oversized retained messages are skipped, and invalid/oversized stored context IDs fall back to the deterministic native-task context.
Authorization and cancellation
Section titled “Authorization and cancellation”Bearer authentication runs before parsing can mutate task state. Non-admin callers can only get, list, continue, and cancel tasks they created in rings they can currently see. Foreign-owner and cross-ring IDs are deliberately indistinguishable from missing tasks.
Idle cancellation is idempotent. Active work may be canceled only by the exact current lease holder with matching lease and dispatcher generations. This uses the same canonical command and fencing rules as Chorus HTTP, SDK, and MCP task paths.
Errors and validation
Section titled “Errors and validation”Chorus streams and caps request bodies at 1,500,000 bytes, rejects ambiguous
media/encoding headers and malformed UTF-8, and then validates the JSON-RPC
envelope, version-specific parameters, parts, IDs, nested JSON bounds, strict
RFC3339 timestamps, pagination, and terminal-state rules before the SDK
converters run. A2A errors use standard numeric codes and include
google.rpc.ErrorInfo in error.data. JSON-RPC and Agent Card bodies are not
decorated with Chorus’s normal response meta object.
Conformance gate and exclusions
Section titled “Conformance gate and exclusions”CI runs both official SDK clients and a real loopback Chorus server against the pinned upstream TCK. The selected upstream profile covers:
- Agent Card structure, interface declarations, cache control, and ETags;
- non-streaming JSON-RPC envelope, content type, method, and service headers;
- standard A2A error codes and
google.rpc.ErrorInfo.
The gate excludes cases whose preconditions Chorus does not advertise: gRPC,
HTTP+JSON, streaming, push notifications, and extended cards. It also excludes
the TCK’s special tck-* executor scenarios, which require a test agent that
manufactures prescribed terminal/input-required/artifact sequences; Chorus’s
adapter submits real native work and does not embed test-only execution
semantics. Native lifecycle, authorization, idempotency, race, pagination, and
generation-fenced cancellation are covered by Chorus’s own contract suites.
Run the local gates with a pinned TCK checkout:
bun run a2a:contractuv_path="$(type -P uv)"test -n "$uv_path"A2A_TCK_DIR=/path/to/a2a-tck S22_UV_BIN="$uv_path" bun run a2a:tckGET /health exposes the same supported-version and capability matrix for
operator diagnostics.