A2A Tasks, Artifacts and Inter-Agent Authority
A2A can carry long-running work between independent agents, but a protocol task is not a permission. Each consequential hop needs task-bound authority, independently accepted artifacts and verified outcomes.
TLDR
- A2A standardises discovery, messages, tasks, lifecycle states and artifacts. It does not make a remote agent authorised to act for a person, organisation or case.
- The decisive mechanism is separation: task state coordinates work, authority leases constrain effects, artifact attestations preserve provenance, and resource-local checks decide whether an operation is allowed.
- Never forward ambient bearer power. Exchange a narrower grant bound to subject, actor, audience, task, resource, operation, purpose, evidence state, expiry and replay rules.
AUTH_REQUIRED,COMPLETED, a signed Agent Card and a valid artifact each prove something useful. None alone proves business approval, factual validity or observed effect.- Adopt A2A for independent, long-running collaborators only when protocol conformance and authority conformance are tested separately. Use a simpler call for local, reversible, low-consequence work.
A fictional bank is preparing the annual review of Northwind Renewables. Its case agent can read the authorised credit file, but a specialist operated by another company performs covenant extraction. The case agent discovers that specialist through an Agent Card, sends a request, receives a task identifier and watches the task move from submitted to working. Halfway through, the specialist returns TASK_STATE_AUTH_REQUIRED. It needs access to a licensed document repository before it can finish.
The easy implementation forwards the case agent's existing bearer token. The task resumes and ends in TASK_STATE_COMPLETED. A polished covenant report arrives as an artifact. Every protocol event looks healthy. Yet the forwarded token can read every case assigned to the relationship manager, not merely the two documents needed for this task. It is valid for forty-five minutes, while the delegated work takes three. The remote agent can also reuse it outside the A2A task. The protocol exchange succeeded while the authority design failed.
Now change only one feature. Issue the specialist a three-minute grant that names the represented user, the calling agent, the receiving agent, the task, the two document identifiers, the read operation and the annual-review purpose. Bind it to the current case-state digest. Forbid redelegation. Mark the nonce as consumed after use. The specialist receives enough power to complete the task, but no ambient power survives the hop.
This is the central answer. A2A should move task state and deliverables across an agent boundary; a separate authority mechanism should move only the minimum right needed for one bounded operation. The two lifecycles interact, but neither may silently stand in for the other. The current A2A 1.0 specification makes that boundary explicit: the AUTH_REQUIRED state signals that more authorisation is needed, while leaving the scope, representation, validity and revocation of that authorisation to the implementation, credential issuer or extension.[1]
Part I
The protocol coordinates work
A remote agent is different from a local function. It may run in another process, release on another schedule, use another framework, hold different credentials and remain opaque to the caller. Work can outlive an HTTP response. The remote side may request clarification, pause for authorisation, stream partial artifacts, fail after producing useful intermediate state or continue after the original client disconnects.
A2A gives those interactions a common vocabulary. The current released specification defines interoperable operations over JSON-RPC, HTTP with JSON and gRPC. A client can discover an agent's advertised skills and security schemes, send a message, receive either an immediate message or a stateful task, subscribe to updates, retrieve a task later and obtain artifacts.[1] The official task guidance describes contextId as a grouping identifier for related messages and tasks, and a task as the stateful unit that progresses towards an interrupted or terminal state.[2]
That is valuable precisely because it avoids embedding one operator's workflow engine inside another operator's product. The caller does not need the callee's private plan, memory architecture or tool graph. It needs a stable contract for requesting work, observing progress and receiving a deliverable. A2A standardises the seam, not the internal mind of either agent.
Swipe horizontally to inspect the complete figure.
AUTH_REQUIRED may trigger an authority process but cannot substitute for it.The coordination win has a boundary
A protocol can authenticate a calling workload, protect the transport and scope task visibility to a caller. Those are necessary controls. They do not answer the business question, “May this agent perform this exact operation on this resource for this purpose on behalf of this person now?” A service identity identifies the software making the request. A business principal identifies whose rights or duties are represented. A task identifier identifies protocol work. These are different entities.
The distinction becomes visible in long-running work. At 09:00, the caller may be entitled to request a document analysis. At 09:04, the case may close, the user's assignment may change or the policy version may be superseded. The remote task can still exist, yet its authority may have expired. Conversely, a valid business approval can exist before an A2A task is created. Task existence, task continuity and task success are facts about coordination, not proofs of continuing permission.
Published and official boundary
A2A 1.0 requires authenticated requests and authorisation scoping for task resources. It also states that the protocol does not define the scope or revocation semantics of authorisation obtained during an AUTH_REQUIRED pause. Research threat models therefore treat identity binding, replay, privilege escalation and task integrity as deployment concerns that must be tested around the protocol.[1][12][13]
Four objects, four proof limits
The cleanest way to prevent authority leakage is to say what each A2A object is allowed to mean. An Agent Card, Message, Task and Artifact are all useful. Trouble begins when one is promoted into evidence it was never designed to carry.
Agent Card
Can establish: advertised endpoint, interfaces, protocol version, skills, capabilities and security schemes. A signature can protect card integrity when its trust path is accepted.
Cannot establish: that a skill is competent, safe for this case, authorised for this user or currently entitled to a downstream resource.
Message
Can establish: what one participant sent, associated task or context identifiers and typed text, file or data parts.
Cannot establish: that embedded instructions are trusted, that all messages will be retained or that critical output was durably delivered.
Task
Can establish: server-issued task identity, current lifecycle state, context association, history selected by the server and attached artifacts.
Cannot establish: the represented business principal, the full mandate, the truth of an output, approval of a decision or occurrence of an external effect.
Artifact
Can establish: a named output with an identifier and one or more content parts associated with a task.
Cannot establish: acceptable provenance, untampered content, sufficient evidence, current entitlement, semantic correctness or a verified business outcome.
The specification reinforces two of these limits. Results should be delivered as artifacts rather than ordinary messages, and messages must not be treated as a reliable mechanism for critical information because histories and streaming updates may be incomplete.[1] It also treats a server-generated contextId as an opaque grouping identifier. A context can preserve conversational continuity, but it should not be overloaded into a portable user identity or entitlement.
A signed Agent Card deserves the same discipline. Signing helps a client detect alteration and bind the card to a trusted signer. It does not certify the claims inside the card. A signed statement that an agent offers “credit decisioning” proves who made that statement, assuming the key is trusted. It does not prove that the agent may decide this customer's credit. Discovery and certification remain separate.
Thought experiment 1: change only the represented person
Hold the endpoint, caller agent, task text, context, resource and time fixed. Maya may read the covenant pack; Dev may view only workflow status. Both requests are sent by the same orchestrator workload. The A2A task can be identical at the wire level. If the remote side authorises only the service identity, it gives Dev whatever Maya could receive. The missing variable is the represented subject, not the task.
This thought experiment also reveals why putting a user name inside a message is insufficient. The content is controlled by the caller and may be influenced by a model. A receiving agent needs a verifiable binding from an accepted issuer between the subject and the actor. The human identity belongs in the authority decision, not in prose that the model can rewrite.
Thought experiment 2: change only the authorising state
Hold the subject, actor, task, operation and resource fixed. At 10:00, policy version seven permits access because the case is in annual review. At 10:03, the case enters legal hold and policy version eight blocks external retrieval. The same task and credential arrive at 10:04. A grant bound only to identity still passes; a grant bound to the earlier case-state digest fails. The missing variable is current authorising state.
Time alone cannot solve that problem. A one-minute token can be stale after ten seconds if the material state changes. A one-hour token may remain safe for an immutable public resource. Expiry limits exposure, while state binding limits semantic drift. High-consequence tasks often need both.
Part II
Part III
flowchart TD A["The protocol coordinates work"] --> B["Four objects, four proof limits"] B --> C["Authority must narrow at every hop"] C --> D["Artifacts need acceptance, not<br/>admiration"] D -. "re-test" .-> A classDef first fill:#0a1424,stroke:#D95545,stroke-width:2px,color:#e6f3ff classDef step fill:#0a1424,stroke:#3f7da0,stroke-width:1px,color:#e6f3ff class A first class B,C,D step
Artifacts need acceptance, not admiration
The remote agent finishes and attaches a report. The task is completed. What may the caller conclude?
It may conclude that the remote service reported successful completion and supplied an artifact associated with the task. It may not yet conclude that the artifact is authentic, complete, current, semantically correct or sufficient for the caller's decision. Protocol completion is a statement by the producer; artifact acceptance is a decision by the consumer.
This distinction is particularly important because A2A intentionally leaves artifact version relationships to the client. The official task guidance says the client is best placed to decide which artifact version is acceptable and that tracking artifact mutation is outside the protocol.[2] That is a sensible separation of concerns. It also means the consuming system needs an acceptance policy.
Protocol receipt
Task identifier, state transition, timestamps and retrieval history.
Proves: what the remote A2A endpoint reported.
Artifact receipt
Artifact identifier, digest, producer, media type, source references and acceptance result.
Proves: which deliverable was received and whether it passed the consumer's checks.
Authority receipt
Subject, actor, audience, operation, resource, policy decision, lifetime and consumed nonce.
Proves: why this operation was permitted at that moment.
Effect receipt
Observed postcondition from the authoritative system, including idempotency key and readback.
Proves: whether the intended external state change occurred exactly once.
An acceptance policy should validate structure before interpretation. Check the artifact's task and context bindings, content type, size, producer identity, digest and declared extensions. Treat text, files, URLs and structured data as untrusted input. The A2A media-type security requirements call for schema validation, sanitisation and protection against file-reference SSRF, while the sample repository warns implementers to treat external messages and artifacts as untrusted.[1][4]
Then validate evidential fitness. A covenant summary may need source document identifiers, page spans, extraction method, policy version, freshness and unresolved contradictions. A generated chart may need its dataset digest and transformation. A signed artifact proves integrity relative to a key; it does not prove that the producer used sufficient evidence. Provenance models such as W3C PROV distinguish entities, activities and agents, while in-toto attestations bind artifact subjects by digest.[11] Those concepts can inform an A2A artifact extension without pretending that software-build provenance and analytical evidence are identical.
Worked example 1: the negative control
A research agent asks a remote public-data agent for the latest released population table from a named government dataset. The service is publicly accessible, the operation is read-only, the result is reversible and no user-specific entitlement changes the answer. The caller authenticates the service, pins the agent endpoint, validates the artifact digest and source URL, and records the dataset release.
In this case, a bespoke task-bound user delegation token may add little. Ordinary service authentication plus artifact validation can be enough. This is the serious negative control: not every A2A task needs a represented human principal or a custom authority envelope. The mechanism becomes necessary when access or effects depend on who is represented, which case is in scope, why the data is used, what state authorised the operation or whether the remote agent may delegate again.
The negative control protects the argument from becoming “more controls are always better”. Controls create latency, coupling, operational state and failure modes. Apply them where one varying causal feature changes permission.
Effects require readback
Suppose the remote agent is asked to place a hold on a payment. It returns a completed task with an artifact saying “hold placed”. That statement is not an effect receipt. The downstream payment service may have timed out after accepting the command, applied the hold twice through retries or rejected it after the agent generated its report. The caller needs an idempotency key and an authoritative readback from the payment system.
A2A is the coordination envelope around the remote worker. It cannot transform the remote worker's narrative into ground truth about another system. The final control remains local to the effect owner: verify the postcondition, classify an ambiguous commit as an unknown outcome, and prevent retries from multiplying the effect.
A cross-domain worked case
Return to Northwind Renewables. The synthetic scenario has three operators. The bank runs the case orchestrator. A specialist company runs a covenant-reading agent. A data provider exposes the licensed filing repository. The bank and specialist deploy independently and cannot share application sessions or internal agent state.
The goal is narrow: produce an evidence pack for one annual review. The specialist may read two named filings and extract five covenant fields. It may not view other customer documents, approve a waiver, write into the credit system or delegate further.
Swipe horizontally to inspect the complete figure.
Step 1: discover, then verify the discovery claim
The orchestrator retrieves the specialist's Agent Card over an approved channel. It checks the supported A2A interface, protocol version, relevant skill, security scheme and card signature. A policy registry confirms that this provider is approved for covenant extraction. The card helps routing; the registry and assurance evidence decide whether the provider may enter this workflow.
Step 2: create the task without smuggling power inside the message
The orchestrator sends a message that names the requested deliverable, document references and expected artifact schema. It does not include a reusable user token, a database connection or an instruction to “use any available source”. The specialist returns a server-generated task and begins work. The task is visible only to the authenticated and authorised caller.
Step 3: answer the authorisation pause out of band
The specialist reaches the licensed repository and transitions the task to TASK_STATE_AUTH_REQUIRED. The status message describes the requested operation in structured data. The bank's authority service evaluates Maya's assignment, case state, provider approval and purpose. It exchanges the upstream subject and actor information for a sender-constrained, audience-restricted grant naming the specialist, repository, two filing identifiers and read-only operation.
The A2A task stores no ambient secret. The orchestrator returns only a handle or protected credential through the agreed extension or secure channel. The specialist verifies the grant, and the repository verifies it again. The nonce is consumed on successful access. A policy change or task mismatch causes denial even if the A2A task still exists.
Step 4: produce an artifact that can be checked
The specialist creates a JSON artifact containing the five extracted fields, document and page references, extraction confidence, contradictions, producer identity, task and context identifiers, schema version and content digest. It transitions the task to completed. The bank does not yet copy the fields into the credit memo.
Step 5: accept, reject or quarantine
The bank validates the artifact schema, digest, producer, task binding and source references. It independently retrieves two cited spans and compares them with the extracted values. A contradiction moves the artifact to human review. A clean artifact becomes evidence for the next workflow step, not an approval of the annual review. The authority receipt, artifact receipt and protocol trace remain linked by task ID and grant ID.
This case shows the architecture change. The task carries coordination. The grant carries bounded authority. The artifact carries a deliverable plus provenance. The bank's acceptance gate carries accountable judgement. None is asked to do another's job.
Part IV
The A2A authority conformance lab
The official A2A Technology Compatibility Kit validates protocol implementations across gRPC, JSON-RPC and HTTP with JSON, organising tests around normative requirements and generating compatibility reports.[3] That answers an important question: does this endpoint speak the protocol correctly?
It does not, by itself, answer whether a bank's authority semantics are correct. That is not a criticism of the TCK. Business authority is intentionally implementation-specific. Protocol conformance and authority conformance are orthogonal release gates. A deployment can pass one and fail the other.
What the lab varies
A good test does not merely replay one successful task. It changes one causal feature at a time and expects the policy to fail for the right reason. Keep the text constant and change the subject. Keep identity constant and widen the operation. Keep the task constant and alter the case-state digest. Replay a consumed nonce. Attach a valid artifact to another task. Modify one byte after calculating the digest.
The expected result is not always “reject the whole task”. An invalid invocation should be denied before the effect. A malformed artifact may be quarantined while the protocol task remains retrievable. A transient status message can be lost without losing the final artifact. The lab therefore tests typed outcomes: permit, deny, request input, quarantine, retry or mark unknown.
Runnable fixture runner
The browser runner tests the contract logic with synthetic values. It treats cryptographic signature verification as an injected boolean so the policy branches remain visible. The full Python script below signs and verifies a demo HMAC using only the standard library.
Ready.
Executable Python 3.11 artifact: a2a_authority_lab.py
The script uses HMAC only to keep the demonstration dependency-free. It is not a production token profile. Expected output contains eight PASS lines: one allowed positive case and seven denied or quarantined mutations.
"""Synthetic A2A authority conformance lab.
The script demonstrates policy logic, not a production token format.
It uses HMAC only so the example is runnable with the Python standard library.
A real deployment should use its approved authorization server, asymmetric or
sender-constrained credentials, key rotation, revocation and resource-local policy.
"""
from __future__ import annotations
from copy import deepcopy
from datetime import datetime, timedelta, timezone
import hashlib
import hmac
import json
from typing import Any
DEMO_KEY = b"replace-with-a-real-key-management-system"
ALLOWED_TASK_STATES = {
"TASK_STATE_SUBMITTED",
"TASK_STATE_WORKING",
"TASK_STATE_AUTH_REQUIRED",
}
def canonical_json(value: Any) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8")
def sha256_text(text: str) -> str:
return hashlib.sha256(text.encode("utf-8")).hexdigest()
def sign(payload: dict[str, Any]) -> str:
return hmac.new(DEMO_KEY, canonical_json(payload), hashlib.sha256).hexdigest()
def issue_lease(payload: dict[str, Any]) -> dict[str, Any]:
return {"payload": deepcopy(payload), "signature": sign(payload)}
def verify_lease_signature(lease: dict[str, Any]) -> bool:
supplied = str(lease.get("signature", ""))
expected = sign(lease.get("payload", {}))
return hmac.compare_digest(supplied, expected)
def parse_utc(value: str) -> datetime:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
if parsed.tzinfo is None:
raise ValueError("timestamp must include a timezone")
return parsed.astimezone(timezone.utc)
def authorize_invocation(
*,
task: dict[str, Any],
lease: dict[str, Any],
invocation: dict[str, Any],
now: datetime,
live_state_digest: str,
consumed_nonces: set[str],
) -> tuple[bool, list[str]]:
"""Check whether one proposed remote operation is within the task-bound lease."""
errors: list[str] = []
payload = lease.get("payload", {})
if not verify_lease_signature(lease):
errors.append("invalid lease signature")
if task.get("status") not in ALLOWED_TASK_STATES:
errors.append("task is not in an invocable state")
bindings = {
"task_id": task.get("id"),
"context_id": task.get("context_id"),
"subject": invocation.get("subject"),
"actor": invocation.get("actor"),
"audience": invocation.get("target_agent"),
"resource": invocation.get("resource"),
"operation": invocation.get("operation"),
"purpose": invocation.get("purpose"),
}
for field, actual in bindings.items():
if payload.get(field) != actual:
errors.append(f"{field} binding mismatch")
amount = invocation.get("amount")
maximum = payload.get("parameter_bounds", {}).get("max_amount")
if amount is not None and maximum is not None and amount > maximum:
errors.append("parameter bound exceeded")
if payload.get("state_digest") != live_state_digest:
errors.append("authorizing state has changed")
try:
not_before = parse_utc(payload["not_before"])
expires_at = parse_utc(payload["expires_at"])
if now < not_before:
errors.append("lease not yet active")
if now >= expires_at:
errors.append("lease expired")
except (KeyError, TypeError, ValueError):
errors.append("invalid lease lifetime")
requested_depth = int(invocation.get("delegation_depth", 0))
max_depth = int(payload.get("max_delegation_depth", 0))
if requested_depth > max_depth:
errors.append("delegation depth exceeded")
if requested_depth > 0 and not payload.get("may_redelegate", False):
errors.append("redelegation forbidden")
nonce = str(payload.get("nonce", ""))
if not nonce:
errors.append("missing nonce")
elif nonce in consumed_nonces:
errors.append("replay detected")
if errors:
return False, errors
consumed_nonces.add(nonce)
return True, ["task-bound authority accepted"]
def accept_artifact(
*,
task: dict[str, Any],
artifact: dict[str, Any],
expected_producer: str,
allowed_media_types: set[str],
) -> tuple[bool, list[str]]:
"""Validate the returned deliverable separately from invocation authority."""
errors: list[str] = []
if artifact.get("task_id") != task.get("id"):
errors.append("artifact belongs to another task")
if artifact.get("context_id") != task.get("context_id"):
errors.append("artifact context mismatch")
if artifact.get("producer") != expected_producer:
errors.append("unexpected artifact producer")
if artifact.get("media_type") not in allowed_media_types:
errors.append("unsupported artifact media type")
content = str(artifact.get("content", ""))
if artifact.get("digest") != sha256_text(content):
errors.append("artifact digest mismatch")
if not artifact.get("source_refs"):
errors.append("artifact has no source references")
return (not errors, errors or ["artifact accepted"])
def base_fixture() -> tuple[dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any], datetime, str]:
now = datetime(2026, 8, 30, 8, 0, tzinfo=timezone.utc)
state_digest = sha256_text("case-472|policy-v7|evidence-v19")
task = {
"id": "task-472",
"context_id": "ctx-88",
"status": "TASK_STATE_AUTH_REQUIRED",
}
payload = {
"grant_id": "grant-901",
"issuer": "https://auth.example.test",
"subject": "user:maya",
"actor": "spiffe://bank.test/agents/case-orchestrator",
"audience": "spiffe://specialist.test/agents/covenant-reader",
"task_id": task["id"],
"context_id": task["context_id"],
"resource": "credit-case:472",
"operation": "read:covenant-evidence",
"purpose": "annual-review",
"parameter_bounds": {"max_amount": 0},
"state_digest": state_digest,
"not_before": now.isoformat().replace("+00:00", "Z"),
"expires_at": (now + timedelta(minutes=5)).isoformat().replace("+00:00", "Z"),
"max_delegation_depth": 0,
"may_redelegate": False,
"nonce": "once-77",
}
invocation = {
"subject": payload["subject"],
"actor": payload["actor"],
"target_agent": payload["audience"],
"resource": payload["resource"],
"operation": payload["operation"],
"purpose": payload["purpose"],
"amount": 0,
"delegation_depth": 0,
}
content = "Covenant evidence summary for synthetic case 472."
artifact = {
"artifact_id": "artifact-19",
"task_id": task["id"],
"context_id": task["context_id"],
"producer": payload["audience"],
"media_type": "application/json",
"content": content,
"digest": sha256_text(content),
"source_refs": ["public-registry:synthetic-44", "policy:annual-review-v7"],
}
return task, issue_lease(payload), invocation, artifact, now, state_digest
def run_case(name: str, mutate: str | None = None) -> tuple[str, bool, list[str]]:
task, lease, invocation, artifact, now, state_digest = base_fixture()
consumed: set[str] = set()
if mutate == "wrong_subject":
invocation["subject"] = "user:dev"
elif mutate == "broader_operation":
invocation["operation"] = "approve:credit-limit"
elif mutate == "stale_state":
state_digest = sha256_text("case-472|policy-v8|evidence-v20")
elif mutate == "expired":
now += timedelta(minutes=10)
elif mutate == "replay":
consumed.add(lease["payload"]["nonce"])
elif mutate == "artifact_task_swap":
artifact["task_id"] = "task-999"
elif mutate == "tampered_artifact":
artifact["content"] += " altered"
allowed, authority_notes = authorize_invocation(
task=task,
lease=lease,
invocation=invocation,
now=now,
live_state_digest=state_digest,
consumed_nonces=consumed,
)
if not allowed:
return name, False, authority_notes
accepted, artifact_notes = accept_artifact(
task=task,
artifact=artifact,
expected_producer=lease["payload"]["audience"],
allowed_media_types={"application/json"},
)
return name, accepted, artifact_notes
def main() -> None:
fixtures = [
("positive", None, True),
("wrong subject", "wrong_subject", False),
("broader operation", "broader_operation", False),
("stale state", "stale_state", False),
("expired lease", "expired", False),
("replayed lease", "replay", False),
("artifact task swap", "artifact_task_swap", False),
("tampered artifact", "tampered_artifact", False),
]
failures = 0
for name, mutation, expected in fixtures:
_, actual, notes = run_case(name, mutation)
verdict = "PASS" if actual == expected else "FAIL"
failures += verdict == "FAIL"
print(f"{verdict:4} | {name:20} | decision={actual!s:5} | {', '.join(notes)}")
if failures:
raise SystemExit(f"{failures} fixture(s) did not match their expected decision")
if __name__ == "__main__":
main()
Reading the test result correctly
A passing positive fixture means the exact synthetic invocation matched the lease and the artifact passed the selected checks. It does not establish that the source evidence is true, the model extraction is accurate or the key management is adequate. A negative fixture is more diagnostic: it shows that a specific mutation is detected at the intended boundary.
The highest-value metrics are therefore not generic task success and not raw protocol availability. Track cross-subject false permits, cross-task replay, stale-state permits, overbroad-operation permits, artifact task swaps, digest failures, unverified completion claims and unknown outcomes. Zero observed failures in a fixed fixture set is evidence about that fixture set, not proof against every adversary.
| Mutation | Protocol remains valid? | Authority decision | Artifact decision | Expected diagnostic |
|---|---|---|---|---|
| No change | Yes | Permit | Accept | All bindings hold |
| Subject only | Yes | Deny | Not reached | Subject mismatch |
| Operation only | Yes | Deny | Not reached | Scope widening |
| State digest only | Yes | Deny | Not reached | Authorising state changed |
| Nonce replay | Yes | Deny | Not reached | Single-use violation |
| Artifact task ID only | Yes | Permit | Quarantine | Cross-task substitution |
| Artifact content only | Yes | Permit | Quarantine | Digest mismatch |
| Public read, user changes | Yes | Permit | Accept | Negative control: subject is irrelevant |
Part V
stateDiagram-v2 [*] --> Claim state "The coordination win has a<br/>boundary" as Claim state "A task-bound authority lease" as Test state "Check twice, at least" as Revision Claim --> Test: expose mechanism Test --> Decision: evidence survives Test --> Revision: rival survives Revision --> Claim: narrow or rebuild Decision --> [*]
Failure injection and the architecture decision
The strongest competing design is simpler: keep all agents inside one process and call specialists as functions or tools. That design avoids a network protocol, cross-domain trust and independent deployment. Where it fits, it is often better. A2A earns its complexity when the remote participant owns its runtime and semantics, when work is long-running or asynchronous, when artifacts need a durable lifecycle, or when independently built agents must interoperate without a shared release.
A second objection says OAuth already solves delegation. OAuth provides mature building blocks for limited access, token exchange, audience restriction, fine-grained authorisation and sender constraint. It does not define A2A task states, artifact streaming, context grouping or task retrieval. A2A provides those coordination semantics while intentionally leaving business authorisation open. The two mechanisms are complementary because they solve different layers.
A third objection says the receiving agent should simply be trusted. Trust reduces some threats but does not remove accidental scope reuse, stale credentials, confused deputy behaviour, task mix-ups or incident reconstruction. Cross-domain systems fail through ordinary integration mistakes as often as through malicious agents. A narrow grant also protects a cooperative provider from receiving power it never wanted.
Local and reversible
Same operator, short call, shared release, no durable artifact lifecycle.
Prefer: function call, typed tool or local workflow step.
Independent but low consequence
Different runtime or vendor, long-running work, public or low-sensitivity resources.
Prefer: A2A with service authentication, task scoping and artifact validation.
Independent and consequential
User or case-specific data, write effects, redelegation or changing authorising state.
Prefer: A2A plus task-bound authority, provenance and outcome verification.
Authority cannot be bounded
Ambient credentials, unknown actor chain, unverifiable artifacts or no authoritative readback.
Decision: do not delegate the consequential operation.
Release tests that matter
Before release, run the official TCK against every supported transport and version. Then test your own authority semantics against live policy and resource servers. Verify that task listing, retrieval, cancellation, subscription and push configuration reveal only authorised tasks. Inject expired credentials, wrong audiences, swapped tasks, excessive delegation depth, unavailable revocation services and duplicate notifications. A2A push delivery can be at least once, so receivers need idempotent processing and task-specific notification credentials.[1]
Run artifact attacks separately. Substitute another task's artifact. Preserve the digest but change provenance metadata. Preserve metadata but alter content. Supply a malicious file URL. Remove a required source reference. Stream chunks out of order. Send a completed task with no acceptable deliverable. The consumer must fail closed for consequential use without turning every malformed artifact into an unrecoverable protocol incident.
Finally, test effects. Drop the connection after the protected resource accepts a command but before the agent receives confirmation. Retry with the same idempotency key. Retry with a different key. Return a completed task while the resource is unavailable. The correct state may be “unknown outcome”, not “failed” and not “completed”. That uncertainty must be visible to the supervising workflow.
Explicit failure boundary
The task-bound lease does not make a compromised workload trustworthy. It does not repair a malicious authorisation server, a false approval, a corrupted source document, collusion between agents or an incorrect resource policy. State digests are useful only if the selected state is authoritative and complete. Artifact attestations are useful only if keys, producers and verification policy are trustworthy. A sender-constrained token still grants its holder the rights inside it.
The mechanism also stops helping when the business permission cannot be expressed before action. Some judgements require a human to see the actual proposed output, not merely a task description. In those cases, the remote agent may prepare an artifact under read authority, but approval and execution must wait for review of the concrete proposal. Do not pre-authorise an unspecified future effect because a task is expected to produce one.
Failure boundary
When the resource owner cannot independently verify a bounded grant and the caller cannot independently verify the returned artifact or effect, A2A should remain a communication channel only. It must not become the path for an irreversible operation.
A compact architecture decision record
| Question | Evidence required | Default decision |
|---|---|---|
| Does the callee deploy independently or own distinct semantics? | Ownership and release boundary | If no, prefer a local typed call |
| Can work outlive one request or produce versioned artifacts? | Task horizon and recovery needs | If yes, A2A task lifecycle may earn its cost |
| Does permission depend on subject, case, purpose or live state? | Policy decision variables | If yes, require task-bound authority |
| Can every hop narrow and verify the grant? | Actor chain, audience and delegation policy | If no, block consequential redelegation |
| Can the artifact be tied to producer, task, inputs and digest? | Acceptance schema and provenance | If no, treat it as untrusted advisory text |
| Can the effect owner return an authoritative postcondition? | Readback and idempotency design | If no, keep action human-controlled or reversible |
| Do protocol and authority fixtures both pass? | TCK report plus authority lab report | Release only when both are green |
Source ledger
The article targets A2A specification 1.0.0, the latest released version at technical review. Official specifications establish protocol and OAuth semantics. Research sources support the security boundary and competing delegation mechanisms. The worked cases, task-bound lease, four-receipt model, layered conformance stack and executable lab are practitioner synthesis.
- Official A2A Protocol Working Group. Agent2Agent Protocol Specification 1.0.0. Used for task, message, artifact, Agent Card, authentication, authorisation-scoping, extension and security semantics.
- Official A2A Protocol Working Group. Life of a Task. Used for contexts, parallel tasks, task continuity and client responsibility for artifact version acceptance.
- Official A2A Project. A2A Technology Compatibility Kit. Used for protocol conformance across gRPC, JSON-RPC and HTTP with JSON.
- Official A2A Project. A2A samples and security warning. Used for the rule that external Agent Cards, messages, artifacts and status data remain untrusted input.
- Standard IETF. RFC 8693, OAuth 2.0 Token Exchange. Used for subject, actor, delegation and impersonation semantics.
- Standard IETF. RFC 8707, Resource Indicators for OAuth 2.0. Used for intended protected-resource binding.
- Standard IETF. RFC 9396, OAuth 2.0 Rich Authorization Requests. Used for structured, fine-grained authorisation details.
- Standard IETF. RFC 9449, Demonstrating Proof of Possession. Used for sender-constrained OAuth tokens and replay resistance.
- Standard IETF. RFC 9700, OAuth 2.0 Security Best Current Practice. Used for minimum privilege, audience restriction and sender constraint.
- Official SPIFFE Project. SPIFFE Concepts. Used for workload identity and trust-domain distinctions.
- Standard W3C. PROV Data Model; in-toto. Attestation Statement v1. Used as adjacent provenance mechanisms, not as direct A2A artifact standards.
- Research Habler, Huang, Narajala and Kulkarni. Building a Secure Agentic AI Application Leveraging A2A Protocol. Threat-model analysis covering spoofing, replay, privilege escalation, prompt injection and task integrity.
- Research Anbiaee et al. Security Threat Modeling for Emerging AI-Agent Protocols. Comparative preprint supporting lifecycle-aware and protocol-centric risk analysis.
- Research Prakash. AIP: Agent Identity Protocol for Verifiable Delegation Across MCP and A2A. Emerging preprint and reference implementation for attenuated delegation and provenance-bound completion.
- Practitioner Schifter. What We Learned Putting A2A Into Production. A current field report showing why one deployment added asker identity and provenance through local conventions and an extension. It is experience from one system, not universal evidence.
- Practitioner The task-bound authority lease, four-receipt model, conformance layers, mutation matrix, worked banking scenario and lab are proposals in this article. They require validation inside each organisation's threat model, identity system, policy engine and resource estate.
sequenceDiagram participant Q as Question participant M as Mechanism participant E as Evidence participant D as Decision Q->>M: The protocol coordinates work M->>E: The coordination win has a boundary E-->>M: compare rival explanations E->>D: Interoperability should move work, not ambient power D-->>Q: revise scope or act
Interoperability should move work, not ambient power
A2A solves a real systems problem. Independent agents need a shared way to advertise capabilities, exchange messages, manage long-running tasks, stream progress and deliver artifacts. Recreating those semantics as private RPC contracts across every pair of operators produces brittle integration and inconsistent recovery.
The architecture fails when interoperability is allowed to imply authority. An Agent Card advertises. A message communicates. A task coordinates. An artifact delivers. None can silently become the represented subject, business mandate, evidence acceptance decision or verified postcondition. The right design keeps protocol state, authority state, evidence state and effect state linked but distinct.
That changes the implementation decision. Use A2A at an authentic agent boundary. Authenticate workloads. Scope task visibility. When work touches user-specific data or consequential effects, issue a short-lived grant bound to subject, actor, audience, task, resource, operation, purpose and live authorising state. Narrow it at every hop. Verify it again at the resource. Consume or expire it independently from the task. Treat every returned artifact as untrusted until the consumer validates its binding, digest, provenance and fitness. For mutations, observe the real postcondition.
Then test two things separately. The official TCK should show that the endpoint speaks A2A. The authority conformance lab should show that wrong subjects, wider operations, stale state, replay and artifact substitution fail at the intended boundary. A green protocol report without those tests is interoperability evidence, not an assurance case.
The practical rule is simple: let A2A carry the work far enough to cross an organisational boundary, while letting authority travel only as far as one explicitly bounded operation.