A2A Production Readiness Checklist: Test, Observe, Roll Back
A server that answers one successful A2A request is not necessarily ready for production. Its public Agent Card can drift away from the deployed endpoint, an authenticated caller can still see another caller’s task, a stream can stop without a recovery path, and a webhook feature can turn into an SSRF path.
This guide turns those risks into a release decision. It assumes that the server already implements A2A v1.0.1. Use the Agent Card Implementation Guide to build and validate the card, and the Task Runtime Guide to validate task traces. Here, the job is different: prove that the deployed system, its public contract, and its recovery controls agree.
The release decision
Do not reduce readiness to one health check. Collect one reproducible piece of evidence for every row that applies to the capabilities declared by the server.
| Gate | Evidence to keep | Release blocker |
|---|---|---|
| Discovery | Public card URL, response headers, card digest, and validation output | The card is unreachable, invalid, or points to a different version, binding, or endpoint |
| Transport | TLS result for every production interface | A production HTTP binding uses plaintext, or a certificate does not validate |
| Authentication | One accepted request and missing, invalid, and expired credential tests | A protected endpoint accepts a bad credential, or returns the wrong challenge behavior |
| Authorization | Cross-user and cross-tenant task tests for read, cancel, subscribe, and push configuration operations | A caller can infer or access a task outside its scope |
| Task lifecycle | A trace for direct completion, interruption, failure, cancellation, and retry behavior | State changes or terminal behavior contradict the protocol or the product contract |
| Streaming | Initial result, ordered updates, terminal event, disconnect, and resubscribe evidence | The card declares streaming but reconnect cannot recover safely |
| Push notifications | URL validation, outbound authentication, duplicate delivery, and replay tests | Private or link-local destinations are reachable, or duplicate delivery corrupts state |
| Observability | Correlated request, task, context, and trace identifiers plus metrics and redacted logs | An operator cannot explain a failed task without exposing credentials or message content |
| Overload and recovery | Timeout, rate-limit, dependency failure, and restart tests | Load produces unbounded work, loses accepted state, or creates an uncontrolled retry loop |
| Rollback | Previous artifact/config identity and a rehearsed disable sequence | The team cannot withdraw a broken capability without publishing a false Agent Card |
The protocol defines interoperability and security requirements; it does not certify your deployment. A passing checklist is evidence for your own release decision, not an A2A certification.
1. Run the public discovery preflight
Run this from outside the production network. It deliberately checks the public response rather than a file from the build directory. The command requires curl and jq.
#!/usr/bin/env bash
set -euo pipefail
: "${A2A_CARD_URL:?Set A2A_CARD_URL to the public Agent Card URL}"
case "$A2A_CARD_URL" in
https://*) ;;
*) printf 'ERROR: production Agent Card URL must use HTTPS\n' >&2; exit 1 ;;
esac
work_dir="$(mktemp -d)"
cleanup() { rm -rf "$work_dir"; }
trap cleanup EXIT HUP INT TERM
status="$({ curl --silent --show-error --location \
--proto '=https' --tlsv1.2 \
--connect-timeout 5 --max-time 15 \
--dump-header "$work_dir/headers" \
--output "$work_dir/card.json" \
--write-out '%{http_code}' \
"$A2A_CARD_URL"; } 2>"$work_dir/curl.error")" || {
cat "$work_dir/curl.error" >&2
exit 1
}
if [[ "$status" != "200" ]]; then
printf 'ERROR: Agent Card returned HTTP %s\n' "$status" >&2
exit 1
fi
content_type="$(tr -d '\r' < "$work_dir/headers" \
| awk -F ': *' 'tolower($1) == "content-type" {print tolower($2)}' \
| tail -n 1)"
case "$content_type" in
application/json*) ;;
*) printf 'ERROR: expected application/json, got %s\n' "${content_type:-missing}" >&2; exit 1 ;;
esac
jq -e '
type == "object"
and ([.name, .description, .version] | all(type == "string" and length > 0))
and (.supportedInterfaces | type == "array" and length > 0)
and (.skills | type == "array" and length > 0)
and all(.supportedInterfaces[];
(.protocolBinding == "GRPC" and (.url | test("^([^/:]+|\\[[0-9A-Fa-f:]+\\]):[0-9]+$")))
or (.protocolBinding != "GRPC" and (.url | startswith("https://"))))
and ((.securitySchemes // {}) as $schemes
| [(.securityRequirements // [])[]?.schemes? | keys[]] as $refs
| ($refs | all(. as $name | $schemes | has($name))))
' "$work_dir/card.json" >/dev/null
digest="$(shasum -a 256 "$work_dir/card.json" | awk '{print $1}')"
printf 'OK: HTTP 200, JSON contract and production interfaces passed\n'
printf 'card_sha256=%s\n' "$digest"
jq -r '.supportedInterfaces[] | "interface=\(.protocolBinding) \(.protocolVersion) \(.url)"' \
"$work_dir/card.json"Example:
A2A_CARD_URL='https://agent.example/.well-known/agent-card.json' \
bash production-preflight.shThe script checks availability, TLS-only retrieval, media type, the basic card shape, production interface addresses, and references from card-level security requirements. It does not prove that the endpoint implements the declared operations or that a signature is valid. Run the full validator from the Agent Card guide, then keep its output beside this preflight result.
Record the card digest with the deployment identity. If the same release later serves a different digest, you have configuration drift even when both documents are valid.
2. Prove authentication and isolation
Test through every binding you publish, not only through an internal SDK. For a protected endpoint, keep the request and status for these cases:
| Case | Expected outcome |
|---|---|
| No credential | Rejected as unauthenticated; for HTTP, normally 401 with an appropriate WWW-Authenticate challenge |
| Malformed, expired, or wrong-audience credential | Rejected as unauthenticated without executing agent work |
| Valid identity without required permission | Rejected as forbidden; for HTTP, normally 403 |
| Valid identity with the required permission | Accepted and attributed to that identity |
Authentication is only the first boundary. Create a task as caller A, then try to GetTask, ListTasks, CancelTask, SubscribeToTask, and manage its push notification configurations as caller B. Run the same matrix across tenants when AgentInterface.tenant is used. A denial should not reveal whether a hidden task exists through response text, timing, or pagination counts.
The A2A security specification requires task results and operations to stay inside the caller’s authorized boundary. The protocol deliberately leaves the authorization model to the implementation, so the release evidence must name the identity, tenant, scope, and policy version that produced each result. See Authentication, Authorization, and Security Considerations .
3. Exercise each declared capability
An Agent Card is an operational promise. Test both the true and the unsupported path for each capability:
| Declaration | Positive test | Negative or recovery test |
|---|---|---|
streaming: true | Receive an initial Task or Message, ordered updates, and a terminal result | Disconnect mid-task, reconnect with SubscribeToTask, and prove no terminal result is silently lost |
streaming: false or absent | Normal non-streaming request works | Streaming operations return UnsupportedOperationError |
pushNotifications: true | Create, read, list, delete, and receive an authenticated notification | Reject unsafe destinations; deliver a duplicate and prove idempotent processing |
pushNotifications: false or absent | Task processing still works | All four configuration operations return PushNotificationNotSupportedError |
extendedAgentCard: true | An authorized caller receives the configured extended card | Missing credentials and insufficient permission are rejected without leaking the card |
| Required extension | A client that declares support completes the operation | A client that omits support receives ExtensionSupportRequiredError |
Feed captured task and stream events to the validator in the Task Runtime Guide
. Keep traces for a completed task, a failed task, a canceled task, INPUT_REQUIRED, and AUTH_REQUIRED when those paths exist in the product.
For push notifications, resolve and validate the destination again after every redirect and immediately before connecting. Block loopback, private, link-local, and otherwise disallowed destinations at the network boundary as well as in application code. Authenticate outbound calls, verify inbound calls, and process duplicate notifications idempotently. The detailed responsibility split is in Streaming and Asynchronous Operations .
4. Make failures explainable
One production task crosses more than one identifier. Log enough structure to correlate it without logging secrets or full message bodies:
timestamp, release_id, protocol_version, binding, operation,
request_id, trace_id, task_id, context_id, tenant_id,
caller_subject_hash, outcome, protocol_error, duration_msUse stable field names across the edge proxy and the A2A server. Redact authorization headers, cookies, webhook credentials, file contents, Parts, artifacts, and free-form messages by default. A task ID is useful for correlation but is not proof of authorization.
At minimum, graph request rate, latency, protocol errors, task terminal states, interrupted tasks, active streams, reconnects, webhook delivery attempts, retry age, dependency failures, and rejected unsafe destinations. Alert on user impact and stuck work, not on every individual failure.
5. Test overload, restart, and retry boundaries
Use a bounded load test that includes slow dependencies and long-running tasks. Verify all of the following before raising traffic:
- New work is rejected or queued at a defined limit; memory and goroutine/thread counts do not grow without bound.
- Client timeouts do not automatically cancel server work unless that behavior is explicitly implemented.
- A process restart preserves or deliberately fails accepted tasks according to the product contract.
- Retrying a timed-out request cannot create uncontrolled duplicate side effects. Use an application-level idempotency strategy where the operation needs one; A2A does not create that guarantee for your business action.
- A broken downstream dependency produces a bounded protocol failure or a recoverable task state, not an endless
WORKINGtask.
Keep the load profile, release identity, limits, and result. “The endpoint returned 200” is not enough evidence for this gate.
6. Prepare a capability-safe rollback
Rollback order matters. If the public card continues to advertise a capability after its endpoint is removed, conforming clients will keep calling a path you no longer support.
For a capability-specific failure:
- Stop new use of the capability at the traffic or feature gate.
- Publish an Agent Card that no longer declares the capability or affected interface.
- Respect the card’s cache behavior and allow discovery caches to refresh.
- Drain or terminate in-flight work with an explicit outcome.
- Roll back the service artifact or configuration.
- Rerun the public preflight and the affected negative tests.
For a full release rollback, keep the previous service artifact, configuration identity, Agent Card, database compatibility decision, and rollback owner together. Do not automatically retry a deployment step whose remote result is unknown; first read back the deployed identity and current card digest.
Failure-to-action map
| Observation | Likely boundary | First action |
|---|---|---|
| Card passes local validation but the public digest differs | CDN, deployment configuration, or stale origin | Fetch without relying on a local file, inspect cache headers, and compare the deployed release identity |
Valid token gets 401 | Authentication configuration | Check issuer, audience, time, and binding-specific credential transport before changing authorization |
| Caller B can see caller A’s task | Authorization/data query | Freeze release; scope lookup before returning existence or content |
Stream ends in SUBMITTED or WORKING | Connection or server failure | Use SubscribeToTask or GetTask; do not assume completion or failure |
| Push works only for internal URLs | Unsafe test design or SSRF exposure | Disable push creation, enforce public destination policy and egress controls, then retest redirects and DNS changes |
| Rollback restores code but clients still call the removed operation | Discovery contract drift | Correct the public Agent Card and wait for its declared cache window |
Evidence package
Store one small manifest with the release rather than screenshots scattered across chat:
release_id: "git-sha-or-artifact-digest"
protocol_version: "1.0"
tested_at: "2026-10-25T12:00:00Z"
agent_card_url: "https://agent.example/.well-known/agent-card.json"
agent_card_sha256: "..."
bindings: ["HTTP+JSON"]
capabilities: ["streaming"]
checks:
discovery: pass
authentication: pass
authorization_isolation: pass
task_lifecycle: pass
streaming_recovery: pass
push_security: not_applicable
overload_restart: pass
rollback_artifact: "previous-release-id"
owner: "team-or-role"The manifest is an index, not the evidence itself. Link it to machine-readable test output and redacted traces, retain it with the release, and rerun the applicable gates whenever the card, authentication policy, binding, capability flags, storage model, or edge routing changes.
Sources: A2A v1.0.1 specification , A2A Authentication, Authorization, and Security Considerations , A2A Agent Discovery , and A2A Enterprise-Ready Features .