# Under what conditions must an instance treat a verified Agent Card signature as insufficient authority to reconcile a remote task state **Date of Record:** 2026 November 12 **Scope:** Applies to A2A v1.0 peers that discover remote agents via signed Agent Cards and then attempt cross-vendor task lifecycle reconciliation (state transitions such as `submitted → working → completed/failed/canceled`). Does not apply to intra-tenant agents sharing a single trust domain, nor to stateless request/response calls where no persisted task object crosses vendor boundaries. ## Core Distinction A signed Agent Card proves *who* the remote agent claims to be and *what capabilities it declares*. It does not prove *what the remote agent is currently doing to a specific task*. These are two distinct trust objects. The signature binds the card's contents (agent identity, endpoint, declared skills, supported task states) to the issuer's key at signing time. It does not bind the live task record, which is mutable state owned by the remote runtime and produced after discovery. The non-obvious invariant: **a valid signature on an Agent Card confers authority over the card, not over the task ledger.** Reconciliation decisions — accepting `completed`, rejecting `failed`, retrying `working` — must be gated on task-scoped evidence, not on the card signature. Teams routinely conflate the two because the card is the only cryptographically verified artifact in the handshake, and it is tempting to treat "verified peer" as "authoritative narrator of task state." ## What Was Observed In a cross-vendor integration between two A2A v1.0 runtimes (caller vendor A, executor vendor B), the following sequence recurred across three incidents: 1. Vendor A discovered B's Agent Card, verified the signature against B's published JWKS, and cached the card with a TTL of 24h. 2. Vendor B rotated its signing key and re-issued the card with an updated `skills` list and a narrowed `supportedTaskStates` set (dropping `canceled` from the declared transitions). 3. A long-running task (7h) had been started under the *old* card. Mid-flight, B's runtime began rejecting `cancel` transitions because the new card no longer declared them. 4. Vendor A's reconciler saw `cancel` rejected and, because it had cached the *old* card as "verified," treated the rejection as a transient error and retried for 40 minutes. The task eventually completed on B's side, but A's ledger was stuck in `working` until manual intervention. The failure was not the key rotation and not the rejection. It was A's reconciler treating the cached, signature-verified card as the authority over a task that had been created under a *different* card version. The signature was valid for the card it signed; it said nothing about which card version governed an in-flight task. ## Condition of Validity The signature-as-authority shortcut is valid **only** when all of the following hold: - The task was created *after* the currently cached card was fetched and verified. - The card version used at task creation is recorded in the task's provenance (e.g., a `card_digest` field on the task envelope). - The remote runtime guarantees that state transitions for a task are governed by the card version in force at creation, not the latest published card. When any of these fails — most commonly the third, because vendors rarely document it — the signature is necessary but not sufficient. The reconciler must treat card version skew as a first-class reconciliation input. The practical test: if `task.card_digest != current_cached_card.digest`, the reconciler must not infer task state from the current card's declared transitions. It must either re-fetch the historical card (if the vendor retains it) or defer to the task's own state channel (status endpoint, event stream) and treat the card only as identity proof. ## Decision Boundary The boundary is between **identity reconciliation** and **state reconciliation**: | Question | Authority | Evidence | |---|---|---| | Is this peer really vendor B? | Agent Card signature | JWKS verification | | May this peer perform skill X? | Agent Card `skills` | Signed card contents | | Is task T in state S? | Task-scoped channel | Status endpoint / event stream / signed task receipt | | May task T transition to state S'? | Card version in force at T's creation | `card_digest` on task envelope | Crossing from the left column to the right without changing evidence source is the failure. A verified card answers the first two; it cannot answer the last two for an in-flight task unless the vendor explicitly guarantees card-version pinning, which is not part of A2A v1.0's normative surface. ## Operational Effect Reconcilers that honor this boundary exhibit a characteristic behavior: on card digest mismatch for an in-flight task, they enter a `deferred` state, poll the task's own status channel, and only escalate if the status channel is also unavailable for a bounded window. This converts a class of "stuck task" incidents (observed: 40-minute retry storms, manual ledger repair) into bounded deferrals with a clear escalation path. The cost is one extra status poll per reconciliation cycle during version skew — negligible against the alternative of reconciling against the wrong authority. The invariant to encode: **card signature authorizes the peer, not the task.** Any code path that reads `card.supportedTaskStates` to decide a transition on an existing task must first assert `task.card_digest == card.digest`, or it is reconciling against an authority it does not have. ```python from dataclasses import dataclass class CardAuthorityError(RuntimeError): """Raised when a reconciler attempts to use a card's declared transitions to decide state for a task created under a different card version.""" @dataclass(frozen=True) class AgentCard: digest: str supported_task_states: frozenset[str] signature_verified: bool @dataclass(frozen=True) class TaskEnvelope: task_id: str card_digest: str # card version in force at task creation current_state: str def decide_transition( task: TaskEnvelope, target_state: str, cached_card: AgentCard, ) -> bool: """Return True if the cached card may authorize this transition. The card signature is necessary but not sufficient: it authorizes the peer, not the task. We must confirm the task was created under the same card version before consulting the card's declared states. """ if not cached_card.signature_verified: raise CardAuthorityError( f"card {cached_card.digest} failed signature verification; " "cannot reconcile task state" ) if task.card_digest != cached_card.digest: raise CardAuthorityError( f"task {task.task_id} was created under card " f"{task.card_digest}, but reconciler holds card " f"{cached_card.digest}; card signature does not confer " "authority over this task's transitions. Defer to the " "task's own status channel." ) return target_state in cached_card.supported_task_states def reconcile(task: TaskEnvelope, target_state: str, card: AgentCard) -> str: try: allowed = decide_transition(task, target_state, card) except CardAuthorityError as exc: return f"deferred: {exc}" if not allowed: return f"rejected: {target_state} not declared by card {card.digest}" return f"accepted: {task.task_id} -> {target_state}" ```