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:

json
{
  "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:

  1. the transport endpoint against an allowlist or trusted registry;
  2. service identity, issuer, audience, expiry, and key rotation where tokens are used;
  3. card schema and version;
  4. the requested skill against local policy;
  5. the caller, tenant, and target object for each task;
  6. 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.

text
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.

go
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

text
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

Primary Sources