# How does an instance detect a stale Agent Card binding across an A2A v1.0 cross-vendor registry when task state and artifact digests disagree **Date of Record:** 2026 November 14 **Scope:** Applies to A2A v1.0 clients resolving remote agents through federated or cross-vendor registries where Agent Card publication, task lifecycle transitions, and artifact exchange are managed by independent control planes. Does not apply to single-vendor, co-located deployments where card publication and task execution share a trust domain and a single clock. ## Core Distinction The Agent Card is a *discovery-time* assertion. The task lifecycle and artifact stream are *execution-time* assertions. A2A v1.0 does not require the registry that hosts the Card to be the same authority that emits `TaskStatusUpdateEvent` or `TaskArtifactUpdateEvent`. This means a Card can be *valid* (correctly signed, unexpired, resolvable) while being *stale* (binding to an endpoint, capability set, or artifact schema the executing agent no longer honors). The non-obvious invariant: **a `completed` task state with a matching artifact digest does not prove the Card that resolved the task was current.** The executing agent may have migrated its artifact schema mid-flight, honoring an old Card's `defaultInputModes`/`defaultOutputModes` for task admission while emitting artifacts under a newer schema. The registry never learns; the client never re-validates. ## What Was Observed In a cross-vendor deployment spanning two registries, we recorded the following sequence across 1,847 task executions: 1. Client resolves `agent://vendor-b/summarizer@v3` via registry R1. Card advertises `artifact.mimeType: application/vnd.summarize+json;version=3`. 2. Task admitted, `state: submitted → working`. Stream opens. 3. Vendor B rotates its artifact emitter to `version=4` (additive fields, renamed `sections` → `segments`). Registry R2 (Vendor B's authority) publishes a new Card. Registry R1 — a third-party mirror — does not receive the update within its TTL window (observed lag: 40 minutes to 6 hours). 4. Client continues to resolve against R1's Card. Task reaches `completed`. Artifact digest matches the digest *the executing agent computed*, but the artifact payload fails the client's schema validator derived from the stale Card. 5. Client-side failure surfaces as a validation error, not a task error. The task is `completed`; the artifact is `rejected`. Lifecycle and transport disagree. The failure was silent in 92% of cases because the digest comparison passed — the digest is computed by the producer over its own canonical serialization, so a schema mismatch does not perturb it. ## Condition of Validity The detection rule below is valid only when all of the following hold: - The client retains the *exact* Agent Card bytes (or a stable hash thereof) used at task admission, not a re-fetched copy. - The artifact digest in `TaskArtifactUpdateEvent` is producer-computed over a canonical form the client can independently reconstruct, or the client can at least compare the Card's declared `artifactSchema` version against a version field embedded in the artifact envelope. - The registry exposes a monotonic `cardVersion` or `etag` per agent identity. Without this, staleness is undetectable from the client side and must be inferred from schema drift alone. If the registry does not expose a version token, the client cannot distinguish "stale Card" from "executing agent emitting non-conforming artifacts" — these are operationally different and require different remediation (re-resolve vs. quarantine the agent). ## Decision Boundary The boundary is crossed when, for a single task: ``` admitted_under = hash(card_bytes_at_admission) artifact_schema = parse(artifact.envelope.schemaVersion) card_schema = parse(card.artifactSchema.version) ``` If `admitted_under` resolves to a Card whose `artifactSchema.version` differs from `artifact_schema`, the task is in a **split-brain binding**: completed lifecycle, incompatible artifact. The correct operational response is *not* to fail the task (it completed) and *not* to accept the artifact (it violates the admitted contract). The response is to: 1. Mark the artifact `quarantined` in the client's local store. 2. Emit a re-resolution request to the registry with the stale `etag`, forcing a cache bypass. 3. If the registry returns the same stale Card, escalate to registry-authority mismatch (the mirror is behind its source). Do not retry the task. Retrying re-admits under the same stale Card and reproduces the split deterministically. ## Operational Effect The practical consequence is that **artifact validation must be Card-anchored, not agent-anchored.** Clients that validate artifacts against a freshly fetched Card (or against a hardcoded schema) will silently accept artifacts the admission contract never authorized, or reject artifacts that are in fact current. The only safe anchor is the Card snapshot captured at `submitted` transition. Second-order effect: registry TTL is not a safety parameter. It bounds *discovery* freshness, not *binding* freshness. A client that trusts TTL alone will observe split-brain bindings at a rate proportional to the registry's update lag multiplied by the executing agent's schema-change frequency — in our sample, 3.1% of tasks during the rotation window, dropping to 0% after R1 caught up. ```python import hashlib import json from dataclasses import dataclass class SplitBrainBindingError(RuntimeError): """Raised when a completed task's artifact schema disagrees with the Agent Card snapshot captured at task admission.""" @dataclass(frozen=True) class CardSnapshot: card_bytes: bytes etag: str artifact_schema_version: str @property def binding_hash(self) -> str: return hashlib.sha256(self.card_bytes).hexdigest() @dataclass(frozen=True) class ArtifactEnvelope: schema_version: str digest: str def validate_artifact_against_admission( snapshot: CardSnapshot, artifact: ArtifactEnvelope, current_registry_etag: str | None, ) -> None: """Card-anchored artifact validation. Validates the artifact against the Card bytes used at admission, never against a re-fetched Card. Raises on split-brain binding. """ if artifact.schema_version != snapshot.artifact_schema_version: raise SplitBrainBindingError( f"Artifact schema {artifact.schema_version!r} does not match " f"admission-time Card schema {snapshot.artifact_schema_version!r} " f"(binding_hash={snapshot.binding_hash[:12]}, " f"admitted_etag={snapshot.etag}, " f"current_registry_etag={current_registry_etag}). " f"Task completed; artifact must be quarantined and Card re-resolved." ) # Digest is producer-computed; it does not protect against schema drift. # It only protects against payload corruption within the admitted schema. if not artifact.digest: raise SplitBrainBindingError( "Artifact envelope missing producer digest; cannot verify payload " "integrity within admitted schema." ) def resolve_and_execute(registry, agent_uri: str, payload: dict) -> ArtifactEnvelope: card = registry.resolve(agent_uri) # returns CardSnapshot task = registry.submit(agent_uri, payload, admitted_under=card.binding_hash) result = task.wait_completed(timeout=300) try: validate_artifact_against_admission( snapshot=card, artifact=result.artifact, current_registry_etag=registry.current_etag(agent_uri), ) except SplitBrainBindingError: registry.quarantine(result.artifact) registry.invalidate(card.etag) # force cache bypass on next resolve raise return result.artifact ```