A2A Production Readiness Checklist: Test, Observe, Roll Back

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.

GateEvidence to keepRelease blocker
DiscoveryPublic card URL, response headers, card digest, and validation outputThe card is unreachable, invalid, or points to a different version, binding, or endpoint
TransportTLS result for every production interfaceA production HTTP binding uses plaintext, or a certificate does not validate
AuthenticationOne accepted request and missing, invalid, and expired credential testsA protected endpoint accepts a bad credential, or returns the wrong challenge behavior
AuthorizationCross-user and cross-tenant task tests for read, cancel, subscribe, and push configuration operationsA caller can infer or access a task outside its scope
Task lifecycleA trace for direct completion, interruption, failure, cancellation, and retry behaviorState changes or terminal behavior contradict the protocol or the product contract
StreamingInitial result, ordered updates, terminal event, disconnect, and resubscribe evidenceThe card declares streaming but reconnect cannot recover safely
Push notificationsURL validation, outbound authentication, duplicate delivery, and replay testsPrivate or link-local destinations are reachable, or duplicate delivery corrupts state
ObservabilityCorrelated request, task, context, and trace identifiers plus metrics and redacted logsAn operator cannot explain a failed task without exposing credentials or message content
Overload and recoveryTimeout, rate-limit, dependency failure, and restart testsLoad produces unbounded work, loses accepted state, or creates an uncontrolled retry loop
RollbackPrevious artifact/config identity and a rehearsed disable sequenceThe 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.sh

The 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:

CaseExpected outcome
No credentialRejected as unauthenticated; for HTTP, normally 401 with an appropriate WWW-Authenticate challenge
Malformed, expired, or wrong-audience credentialRejected as unauthenticated without executing agent work
Valid identity without required permissionRejected as forbidden; for HTTP, normally 403
Valid identity with the required permissionAccepted 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:

DeclarationPositive testNegative or recovery test
streaming: trueReceive an initial Task or Message, ordered updates, and a terminal resultDisconnect mid-task, reconnect with SubscribeToTask, and prove no terminal result is silently lost
streaming: false or absentNormal non-streaming request worksStreaming operations return UnsupportedOperationError
pushNotifications: trueCreate, read, list, delete, and receive an authenticated notificationReject unsafe destinations; deliver a duplicate and prove idempotent processing
pushNotifications: false or absentTask processing still worksAll four configuration operations return PushNotificationNotSupportedError
extendedAgentCard: trueAn authorized caller receives the configured extended cardMissing credentials and insufficient permission are rejected without leaking the card
Required extensionA client that declares support completes the operationA 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_ms

Use 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:

  1. New work is rejected or queued at a defined limit; memory and goroutine/thread counts do not grow without bound.
  2. Client timeouts do not automatically cancel server work unless that behavior is explicitly implemented.
  3. A process restart preserves or deliberately fails accepted tasks according to the product contract.
  4. 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.
  5. A broken downstream dependency produces a bounded protocol failure or a recoverable task state, not an endless WORKING task.

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:

  1. Stop new use of the capability at the traffic or feature gate.
  2. Publish an Agent Card that no longer declares the capability or affected interface.
  3. Respect the card’s cache behavior and allow discovery caches to refresh.
  4. Drain or terminate in-flight work with an explicit outcome.
  5. Roll back the service artifact or configuration.
  6. 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

ObservationLikely boundaryFirst action
Card passes local validation but the public digest differsCDN, deployment configuration, or stale originFetch without relying on a local file, inspect cache headers, and compare the deployed release identity
Valid token gets 401Authentication configurationCheck issuer, audience, time, and binding-specific credential transport before changing authorization
Caller B can see caller A’s taskAuthorization/data queryFreeze release; scope lookup before returning existence or content
Stream ends in SUBMITTED or WORKINGConnection or server failureUse SubscribeToTask or GetTask; do not assume completion or failure
Push works only for internal URLsUnsafe test design or SSRF exposureDisable push creation, enforce public destination policy and egress controls, then retest redirects and DNS changes
Rollback restores code but clients still call the removed operationDiscovery contract driftCorrect 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 .