Skip to content

A2A conformance

SuperOptiX implements the A2A 1.0 wire protocol directly over FastAPI. This page covers how that implementation is verified, how version negotiation works, and how to test an agent you have adapted.

Current results

Measured with the official A2A TCK against the conformance harness:

Level Passed / exercised Not exercised TCK headline
MUST 73 / 73 21 77.7%
SHOULD 7 / 7 4 63.6%
MAY 4 / 4 0 100%

Zero failures. Every requirement the TCK is able to exercise against this endpoint passes.

Why the headline percentage is lower

The TCK counts a requirement it cannot exercise as non-compliant. Until 2026-08-31 it counted those as compliant, which is why earlier versions of this page reported 100% across the board. The change is upstream commit cf4985b, "don't count NOT TESTED requirements as compatible".

25 requirements fall into that bucket here:

Area Count Why it cannot be exercised
Authentication and TLS 13 The harness serves plain HTTP with no auth scheme
Agent Card JWS signatures 4 Card signing is not implemented
Cross-binding equivalence 4 Requires the gRPC binding for comparison
Version negotiation probes 3 Require a second declared server version
gRPC binding 1 Not implemented

None are failures. Reaching a 100% headline means implementing authentication, card signing and a gRPC binding, which are not scheduled.

What the CI gate checks

.github/workflows/a2a-conformance.yml runs on demand and fails on any conformance failure. It does not gate on the headline percentage, because that number moves when the TCK changes what it can exercise, independent of this implementation.

The published SuperOptiX endpoint and agents produced by super a2a adapt score lower on the headline figure. The difference is not a defect: the remaining requirements are TCK scenario hooks that a production agent should not implement. See The conformance harness.

The live endpoint

A SuperOptiX agent runs at a2a.superoptix.ai, hosted on Cloud Run.

Agent Card, published superoptix.ai/.well-known/agent-card.json
Agent Card, served by the agent a2a.superoptix.ai/.well-known/agent-card.json
Endpoint https://a2a.superoptix.ai

Two copies of the card is the intended arrangement rather than duplication. The published copy is a static file on the website, so discovery answers instantly whether or not the service is warm. The served copy confirms the running agent agrees with what was published. The two are the same JSON document (pretty-printed on the website, compact on Cloud Run).

The agent exposes two skills, both deterministic. Neither calls a model, reads user code, nor holds a credential, which is what makes the endpoint safe to expose and inexpensive to run.

curl -X POST https://a2a.superoptix.ai/message:send \
  -H 'content-type: application/json' \
  -d '{"message":{"role":"ROLE_USER","parts":[{"text":"Does CrewAI support A2A?"}]}}'

curl -X POST https://a2a.superoptix.ai/a2a/jsonrpc \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":"1","method":"message/send","params":{"message":{"role":"user","parts":[{"kind":"text","text":"Does CrewAI support A2A?"}]}}}'

Opening https://a2a.superoptix.ai in a browser returns a page describing the endpoint rather than an error. The address appears in the Agent Card and in registry listings, so people follow it, and a bare 404 reads as a broken service.

Hosting

The endpoint moved from Render to Cloud Run in August 2026. The reason was cold start rather than cost:

Cold start Warm
Render free tier 42.5 s 0.41 s
Cloud Run, scale to zero 1.7 s 0.06 s

A2A clients apply timeouts. A registry fetching a card with a 30 second timeout records a 42.5 second endpoint as unreachable rather than slow, so the free tier on Render was not viable for an agent meant to be discovered. Cloud Run keeps the image staged and starts a container on demand, which brings the same idle-to-first-request path inside those timeouts.

deploy/a2a/README.md covers the deployment and the migration.

Running the TCK

The TCK is a pytest suite that exercises a running agent across the JSON-RPC, HTTP+JSON and gRPC bindings, filtered by RFC 2119 level.

git clone https://github.com/a2aproject/a2a-tck.git
cd a2a-tck
uv venv && uv pip install --python .venv/bin/python -e .

Start the conformance harness and point the suite at it:

uvicorn superoptix.protocols.a2a.tck_sut:app --port 8000

.venv/bin/python run_tck.py --sut-host http://127.0.0.1:8000 --level must

Reports land in reports/: compatibility.json for machine reading, compatibility.html for review.

To test an agent you have adapted, point the same command at its server instead.

Official A2A CLI

The A2A project publishes an official command-line client, a2a (a2aproject/a2a-cli). Prefer it when you want a standardized third-party or CI client against the public SuperOptiX agent or any adapted agent. Product surfaces remain super a2a adapt, serving the FastAPI bridge, and the TCK harness below. SuperOptiX does not vendor the Go CLI.

Install from the a2a-cli releases. Then:

# Discover the public Agent Card
a2a card get -a https://a2a.superoptix.ai

# Call a catalogue skill (framework readiness)
a2a send -a https://a2a.superoptix.ai "Does CrewAI support A2A?"

Add -o json for scripts. Machine-readable card fetch and a skill send against https://a2a.superoptix.ai were verified with a2a v0.2.0. Upstream CLI command details may change while the CLI specification is in Review.

The conformance harness

The TCK drives an agent into specific protocol states using reserved messageId prefixes. tck-input-required must leave a task non-terminal, tck-complete-task must complete it, tck-artifact-text must return an artifact. Reference implementations in the A2A project do the same.

Those hooks live in a separate application, superoptix/protocols/a2a/tck_sut.py, rather than in the published endpoint. A production agent that changes behaviour based on a client-supplied identifier is honouring untrusted input. Both applications share the same server implementation, so conformance measured against the harness holds for the protocol layer that adapted agents use.

Continuous integration

.github/workflows/a2a-conformance.yml is a manual (workflow_dispatch) job. It starts superoptix.protocols.a2a.tck_sut:app, runs the official TCK against that harness, publishes the compatibility report as an artifact, and fails on any FAIL status. It does not run on every push, and it does not gate on the TCK headline percentage.

The published endpoint at a2a.superoptix.ai is a separate Cloud Run service, redeployed from a git tag. Protocol regressions can therefore land on main before they are on the live host.

Version negotiation

One endpoint serves both spec lines. Clients select with the A2A-Version request header, and 1.0 is assumed when the header is absent.

curl -H 'A2A-Version: 1.0' localhost:8000/.well-known/agent-card.json
curl -H 'A2A-Version: 0.3' localhost:8000/.well-known/agent-card.json

What changes between the two:

1.0 0.3
Task state TASK_STATE_COMPLETED completed
Message role ROLE_AGENT agent
Part shape Unified, fields set directly Wrapped, tagged with kind
File part raw / url / filename / mediaType file.bytes / file.uri / file.name / file.mimeType
Card supportedInterfaces Top-level url and preferredTransport
JSON-RPC method names SendMessage message/send

Both sets of method names reach the same handlers, so a 0.3 client does not have to know it is talking to a 1.0 implementation:

0.3 1.0
message/send SendMessage
message/stream SendStreamingMessage
tasks/get GetTask
tasks/list ListTasks
tasks/cancel CancelTask
tasks/resubscribe SubscribeToTask
agent/authenticatedExtendedCard GetExtendedAgentCard
tasks/pushNotificationConfig/* *TaskPushNotificationConfig

A method outside both sets returns -32601.

An unrecognised version returns VersionNotSupportedError: -32009 over JSON-RPC, HTTP 400 over REST.

Both lines matter because the installed base is on 0.3. Of the eight frameworks SuperOptiX adapts, five declare no A2A dependency, and the three that do (CrewAI, Google ADK and Pydantic AI) are pinned below 1.0. An endpoint that speaks only 1.0 is unreachable by most agents currently deployed.

Translation is available directly:

from superoptix.protocols.a2a import bridge

legacy = bridge.task_to_v03(task)
current = bridge.task_to_v1(legacy)
card = bridge.card_to_v03(agent_card)

Agent Card caching

The Agent Card is fixed for the life of the process, so it is served with validators that let a caller skip the transfer on a repeat read.

Cache-Control: public, max-age=3600
ETag: "5b8e694cb6e718eb2633ad7de9a2909b"
Last-Modified: Mon, 31 Aug 2026 19:40:41 GMT
Vary: A2A-Version

A conditional request that matches returns 304 with no body:

curl -sI localhost:8000/.well-known/agent-card.json | grep -i etag
curl -si -H 'If-None-Match: "<etag>"' localhost:8000/.well-known/agent-card.json | head -1

The 1.0 and 0.3 renderings of the card are different documents and carry different entity tags, which is what the Vary header exists to signal. A cache holding one will not hand it to a client that asked for the other.

If-None-Match follows RFC 9110: a comma separated list is accepted, * matches anything, and a weak validator compares equal to its strong form.

Error handling

A2A binds each error to a JSON-RPC code, an HTTP status and an ErrorInfo reason. superoptix/protocols/a2a/errors.py holds the table.

JSON-RPC errors are returned with HTTP 200. The transport succeeded; the failure is inside the envelope. Returning 4xx alongside a JSON-RPC error causes conformant clients to treat the response as a transport failure and never read the code.

A2A-specific errors carry a google.rpc.ErrorInfo entry in error.data, per specification section 9.5:

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32001,
    "message": "Task not found",
    "data": [{
      "@type": "type.googleapis.com/google.rpc.ErrorInfo",
      "domain": "a2a-protocol.org",
      "reason": "TASK_NOT_FOUND"
    }]
  }
}

HTTP+JSON errors use AIP-193 bodies with the same ErrorInfo in error.details.

Implemented surface

Method Status
SendMessage Implemented
SendStreamingMessage Implemented, SSE
GetTask Implemented, honours historyLength
ListTasks Implemented
CancelTask Implemented; terminal tasks return TaskNotCancelableError
SubscribeToTask Implemented
GetExtendedAgentCard Returns ExtendedAgentCardNotConfiguredError
Push notification config methods Return PushNotificationNotSupportedError

Bindings: JSON-RPC 2.0 and HTTP+JSON. gRPC is not implemented.

The JSON-RPC route is served at both /a2a/jsonrpc and /a2a/jsonrpc/. A client that treats the interface URL as an HTTP base and posts to / resolves to the trailing-slash form, and a redirect there returns an empty body that JSON-RPC clients cannot parse.

Known gaps

Agent Cards are unsigned. Signed cards are the 1.0 mechanism for proving a card was issued by the domain owner. The agent-card-review skill on the published endpoint reports this against SuperOptiX's own card.

gRPC is not implemented. The TCK covers it, and the requirements are skipped rather than failed.

The Agent Payments Protocol (AP2), published alongside 1.0, is out of scope.

A2A protocol threats (A2ABreak)

A2ABreak (arXiv:2609.10871) is a specification-driven security analysis of A2A v1.0. It documents eleven protocol-level findings that a fully compliant adversary can exploit when the spec omits ownership, attestation, or integrity primitives. These are protocol risks, not SuperOptiX-specific CVEs.

The published SuperOptiX catalogue (isolate_callers=True) hardens the cross-client surface that operators actually hit:

  • Client-supplied contextId on new tasks is ignored; the server mints one.
  • Tasks are bound to an opaque per-caller key (cookie sox_a2a_caller or header X-SuperOptiX-Caller-Key).
  • ListTasks, GetTask, CancelTask, and SubscribeToTask only see that caller's tasks, so anonymous GET /tasks cannot dump every history.

Push notifications stay disabled (pushNotifications: false), which already mitigates the unverified-webhook finding.

The agent-card-review skill also flags unattested skill claims and notes that JWS (when present) authenticates the publisher, not capability truthfulness.

Operators embedding create_a2a_fastapi_app for multi-tenant anonymous traffic should pass isolate_callers=True. Authenticated or single-tenant deployments can leave the default (False) and still apply application-level ACLs.

Agent name collision (origin-bound identity)

Agent Card name is presentation metadata, not a stable peer identity (A2A Agent Name Collision, arXiv:2609.27624). SuperOptiX keys the peer registry and routing catalogue by normalized agent URL:

  • Route and store peers by normalized URL (origin-bound ID), never by remote Agent Card name.
  • Treat card name as a local/presentational alias only.
  • Reject ambiguous alias lookups with AmbiguousAgentName; never silently pick among colliding names.
  • skills_from_card and routing skill refs use origin-bound URL identity, not card.name.
  • Migrate any legacy name-keyed peer store to URL keys on load.
  • Keep connect/discover entrypoints URL-based.
  • Add regression tests: two peers, same card name, different URLs โ†’ first retained, second does not overwrite; alias get with two URLs raises.

See superoptix.protocols.a2a.registry.A2ARegistry, agent_identity_from_card, and skills_from_card in the routing catalogue.