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 | Compliance |
|---|---|
| MUST | 100% |
| SHOULD | 100% |
| MAY | 100% |
The published SuperOptiX endpoint and agents produced by
super a2a adapt score 86.3% MUST. 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 byte-identical.
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?"}]}}'
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.
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 runs the TCK against the harness on
changes to superoptix/protocols/** or superoptix/runtime/**. It publishes
the compatibility report as a build artifact and writes the score to the job
summary.
The job enforces a floor rather than demanding a perfect score:
env:
MIN_MUST_COMPLIANCE: "100.0"
A build fails when MUST-level compliance falls below the floor. Raise the value as gaps close; conformance can then only be maintained or improved.
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.