What A2A Is, and Is Not
A2A, short for Agent-to-Agent, provides a vocabulary for one Agent service to ask another Agent service to perform work. Google announced the protocol publicly in 2025, and the project now publishes 1.0.0 as its latest released specification. Version 1.0 turns the Protocol Buffer model into the normative data definition, separates abstract operations from transport bindings, and defines compatibility rules for independently implemented services.
Treat 1.0.0, the selected binding, SDK version, and advertised Agent interface as one tested compatibility profile. “Supports A2A” is too vague for a production contract.
A2A can help with:
- capability discovery through an Agent Card;
- structured messages and artifacts;
- short or long-running task lifecycle;
- status updates and, where supported, streaming or push delivery;
- delegation across independently operated Agent services.
It does not provide business authorization, tenant isolation, a trusted directory, safe tool execution, accurate model output, or exactly-once side effects. Those remain application responsibilities.
Choose the Boundary Before the Protocol
Use a local function when the work is owned by one service and has a narrow deterministic contract. Consider A2A when the remote service has an independently managed lifecycle, policy, or capability boundary.
| Question | A useful answer |
|---|---|
| Who owns the task state? | One named service with durable storage and retention policy. |
| Who authenticates the caller? | The remote service, using a trusted identity mechanism. |
| Who authorizes data and effects? | The service that owns the object and action. |
| What is the retry identity? | An application-generated idempotency key, not a model phrase. |
| What happens on disconnection? | A documented polling, callback, resume, or cancellation contract. |
| What evidence is retained? | Redacted lifecycle events, policy decisions, artifact references, and error classes. |
Do not introduce an Agent protocol merely to make a linear workflow appear autonomous.
A2A and MCP
The protocols address different boundaries, but the familiar “A2A is Agent-to-Agent; MCP is Agent-to-tool” shorthand is incomplete.
| Boundary | Typical purpose | Still required outside the protocol |
|---|---|---|
| A2A | delegate a task to a remote Agent service | service identity, task/object authorization, budgets |
| MCP | expose tools, resources, and prompts to an AI application | user identity, object authorization, side-effect policy |
| application workflow | coordinate local and remote work | approval, compensation, audit, business invariants |
An A2A delegate may use MCP internally; an MCP Host may call a remote service without A2A. Neither arrangement grants the model permission to read an object or perform an external write.
Pin the Specification Surface
Before implementing, record the exact inputs you have tested:
{
"a2a_specification": "1.0.0",
"canonical_data_model": "spec/[email protected]",
"abstract_operations": ["SendMessage", "GetTask", "CancelTask"],
"transport_binding": "HTTP+JSON/REST",
"agent_interface_protocol_version": "1.0",
"agent_card_location": "/.well-known/agent-card.json",
"sdk": "package-and-version",
"authentication": "deployment-specific",
"task_retention": "policy-reference",
"artifact_store": "approved-store"
}
In A2A 1.0, a2a.proto is the normative source for the canonical data model and abstract operations define behavior before a binding maps it onto the wire. The standard bindings are JSON-RPC, gRPC, and HTTP+JSON/REST. An Agent Card can advertise multiple interfaces, each with its own protocol version, so migration does not require every caller to switch in one cutover.
Do not assume that equivalent semantics mean interchangeable bytes. JSON field naming, enum representation, errors, headers, streaming, and authentication still follow the selected binding. Run the same conformance cases against every interface you advertise, and reject a response that claims a different protocol version than the negotiated interface.
Agent Cards Are Discovery Metadata
An Agent Card may describe a service name, endpoint, skills, input/output modes, and capabilities. It is useful for routing, but it is untrusted until your system establishes provenance.
Validate:
- the transport endpoint against an allowlist or trusted registry;
- service identity, issuer, audience, expiry, and key rotation where tokens are used;
- card schema and version;
- the requested skill against local policy;
- the caller, tenant, and target object for each task;
- response size, artifact type, and callback destination.
Descriptions, skill names, URLs, and examples can contain prompt injection or misleading claims. Never turn card text into executable policy, automatic installation, or tool permission.
Task Lifecycle Is a State Machine
The specification uses Task as the stateful unit of work and Message for an interaction turn; completed deliverables belong in Artifact objects. A2A 1.0 also defines abstract operations including SendMessage, SendStreamingMessage, GetTask, ListTasks, CancelTask, SubscribeToTask, and push-notification configuration operations. A production service should map these operations onto its own durable state machine.
accepted -> running -> waiting_for_input -> running -> terminal
| |
+-> canceled +-> succeeded | failed | rejected
Record both a protocol-facing status and an application status. GetTask, ListTasks, cancellation, and subscription must all enforce the same authenticated caller and tenant visibility rules; possession of a Task ID is not authorization. A terminal response should say whether the task was rejected by policy, failed before effect, failed after an uncertain effect, or completed with evidence.
Idempotency and Cancellation
Retries, duplicate messages, timeouts, and callbacks are ordinary distributed-systems behavior. For an effectful task:
- bind an idempotency key to principal, tenant, action, and normalized parameters;
- persist the decision before performing the effect;
- make cancellation cooperative and state-aware;
- return an “unknown outcome” class when the effect cannot be proven;
- use compensation only where the business operation supports it.
Do not convert a model refusal or a completed label into proof that a payment, deletion, or notification happened exactly once.
Messages, Artifacts, and Untrusted Content
Message parts and artifacts may carry text, structured data, files, references, or URLs. Treat all of them as untrusted input, including content emitted by another Agent.
Bound:
- bytes, item counts, nesting depth, and decompression;
- MIME allowlists and parser behavior;
- artifact storage location, expiry, access scope, and deletion;
- outbound fetch destinations and redirect count;
- telemetry fields and raw payload retention.
Prefer reference-based artifacts with authenticated retrieval over embedding large sensitive content into task messages. A file reference is not access permission; validate it at retrieval time.
Streaming and Push Delivery
Streaming and asynchronous notifications are delivery choices, not a substitute for task durability. A2A 1.0 defines SendStreamingMessage and SubscribeToTask at the abstract-operation layer, while each binding defines how ordered stream responses are carried. Push notifications use registered task notification configurations and deliver the same StreamResponse model to a callback. Regardless of binding, define:
- reconnect and resume semantics;
- event ordering and deduplication;
- token expiry and reauthentication;
- callback registration and destination allowlists;
- timeout, backpressure, and result-size budgets;
- who can observe or cancel a task.
Treat callback URLs as an SSRF boundary. Verify destination ownership, bind each configuration to its task and tenant, sign notifications, reject private or link-local destinations, cap redirects, and make receivers deduplicate delivery. Do not let a model or remote Agent choose an unrestricted callback target.
Authorization Is Per Task and Per Object
OAuth, bearer tokens, mTLS, workload identity, RBAC, and ABAC are possible deployment controls. They are not all mandatory A2A features, and an authenticated caller is not automatically allowed to perform every skill.
package main
import "fmt"
type verdict string
const (
allow verdict = "allow"
deny verdict = "deny"
confirm verdict = "confirm"
)
type taskRequest struct {
Skill string
ObjectID string
}
type authenticatedContext struct {
TenantID string
PrincipalID string
}
type objectAccess func(tenantID, principalID, objectID string) bool
func authorize(
context authenticatedContext,
request taskRequest,
allowedSkills map[string]struct{},
canAccessObject objectAccess,
effectClass string,
) verdict {
if _, ok := allowedSkills[request.Skill]; !ok {
return deny
}
if request.ObjectID != "" &&
!canAccessObject(context.TenantID, context.PrincipalID, request.ObjectID) {
return deny
}
if effectClass == "external_write" || effectClass == "destructive" {
return confirm
}
return allow
}
func main() {
context := authenticatedContext{TenantID: "tenant-a", PrincipalID: "user-42"}
request := taskRequest{Skill: "invoice.read", ObjectID: "invoice-7"}
allowed := map[string]struct{}{"invoice.read": {}}
canRead := func(tenantID, principalID, objectID string) bool {
return tenantID == "tenant-a" && principalID == "user-42" && objectID == "invoice-7"
}
fmt.Println(authorize(context, request, allowed, canRead, "read"))
}
This runnable Go example is an application policy fragment, not an A2A SDK implementation. The trusted caller context must come from authentication middleware, never from model-generated task fields. effectClass should come from a server-side skill registry, not from the request body; a real confirm result also needs an explicit approval and audit path.
A Minimal Production Architecture
caller
-> authenticate and authorize
-> validate task shape and budget
-> persist idempotency + lifecycle event
-> remote A2A service
-> independently authenticate, authorize, and validate
-> use bounded local tools and data access
-> store artifacts in an approved scoped store
-> collect redacted result evidence
-> apply local approval / compensation / user response
Each service should recheck policy rather than trusting upstream enforcement. Cross-tenant tests must prove that a valid token and task identifier from tenant A cannot reveal or modify tenant B data.
Test the Failure Paths
| Test | What it should prove |
|---|---|
| card validation | unknown issuer, endpoint, schema revision, and skill are rejected |
| task contract | malformed parts, oversized artifacts, and unsupported modes fail safely |
| authorization | tenant, object, and action policy is enforced independently of descriptions |
| lifecycle | duplicate delivery, restart, timeout, cancellation, and late callback are deterministic |
| side effects | retries do not duplicate effect; uncertainty is surfaced |
| streaming | reconnect, replay, ordering, expiry, and backpressure have bounded behavior |
| abuse | injection, poisoned artifacts, SSRF callbacks, and artifact exfiltration are blocked |
| operations | latency, queue depth, task cost, retention, and deletion are observable |
Use deterministic oracles for policy and effects. A language-model judge can review answer quality, but cannot prove authorization, provenance, or financial correctness.
Common Failure Modes
- treating an Agent Card, registry listing, badge, or SDK adapter as a trust decision;
- assuming every A2A implementation has the same discovery path or streaming transport;
- accepting a caller identity supplied in task content;
- using task completion as evidence of exactly-once side effects;
- forwarding remote artifacts or callback URLs without validation;
- trusting remote Agent output as instructions or authorization;
- retaining raw task payloads indefinitely;
- advertising framework support without testing the pinned version and transport;
- calling a remote Agent where a local deterministic workflow would be clearer.
Implementation Checklist
- [ ] Pin protocol, binding, SDK, and Agent Card schema revisions.
- [ ] Verify remote endpoint provenance before fetching or trusting a Card.
- [ ] Authenticate both directions where the deployment requires it.
- [ ] Authorize each skill, tenant, object, and side effect at the owning service.
- [ ] Persist idempotency, lifecycle transitions, and uncertainty states.
- [ ] Bound messages, artifacts, callbacks, streaming, retries, and telemetry.
- [ ] Treat remote messages, artifacts, and Card content as untrusted.
- [ ] Test restart, duplicate delivery, cancellation, expiry, cross-tenant, and SSRF cases.
- [ ] Define retention and deletion for tasks, artifacts, indexes, caches, and exports.
- [ ] Roll out behind explicit scope, budget, and rollback controls.
Conclusion
A2A can make independent Agent services interoperable, but it is not an autonomous trust fabric. Pin the version you run, treat discovery as untrusted metadata, make task state durable, and enforce identity, object access, and side effects outside the model. The safest adoption starts with one narrow, read-oriented delegated task and expands only after lifecycle, authorization, and failure evidence are sound.
Continue Reading
- MCP, A2A, and A2UI Protocol Stack explains which interoperability boundary each protocol owns.
- Multi-Agent Orchestration Patterns covers delegation, supervision, shared state, and failure handling above the wire protocol.
- Use the AI Agent and MCP glossary entries for concise concept boundaries.