Agentic AI

Architectural Insights: A2A as a Protocol for Peer AI Agents

An open standard from Google for inter-agent communication, enabling AI agents to collaborate as peers without exposing their internal workings.

Vishnu Vardhan Sai Lanka
Intermediate
July 30, 2025
15 min read

Introduction

The Agent2Agent (A2A) [1] protocol is an open standard from Google for inter-agent communication. It enables AI agents (regardless of framework or vendor) to talk to each other as peers, without exposing their internal workings. Under the hood, A2A's design reveals a series of details that go beyond a typical API. This outline attempts to detail some fundamental (maybe non-obvious) design choices in A2A, highlighting why they're clever or architecturally significant for AI engineers.

A2A Protocol Illustration

Illustration of the A2A Protocol Architecture; Source: AI-generated :XD

JSON-RPC 2.0

One immediate design choice is A2A's communication protocol: it runs on plain HTTP using JSON-RPC 2.0[2] as the message format. Instead of inventing a custom protocol or using heavyweight gRPC, A2A piggybacks on a well-understood JSON RPC schema. This has several subtle advantages:

  • Uniform Interface: JSON-RPC provides a standard envelope (jsonrpc, method, params, id) for all calls. A2A uses this to define methods like `"tasks/send"`, `"tasks/get"`, etc., without needing multiple REST endpoints. Every request and response conforms to one structure, simplifying client and server implementation.
  • Ease of Implementation: By using JSON over HTTP, A2A is instantly compatible with web infrastructure (proxies, auth headers, etc.) and developers can leverage existing JSON-RPC libraries. The spec explicitly notes the goal of reusing "well-understood standards (HTTP, JSON-RPC 2.0, Server-Sent Events)" to keep things simple.
  • Built-in Error Semantics: JSON-RPC defines error objects and codes. A2A extends these with agent-specific codes (e.g. TaskNotFoundError) within the reserved range. This reuse means error handling is consistent and easily extensible without a bespoke error format.
  • RPC Flexibility: Method names are strings, so adding new capabilities (e.g. a future `"tasks/resume"`) doesn't require a protocol overhaul – just define a new method. This matches A2A's extensibility philosophy.

Task Lifecycle: Long-Running, Stateful Interactions

A2A introduces the concept of a Task as the fundamental unit of work. This is more than a single request/response; a Task is a stateful conversation or job that can involve multiple messages and steps. Designing around tasks allows A2A to handle complex, long-running or multi-turn interactions gracefully:

  • Client-Generated Task IDs: Unusually, the client is responsible for generating a unique Task ID when starting a new task. The server then uses that ID for all future references. This inversion (versus typical server-generated IDs) means the client can create an ID (e.g., a UUID) and immediately use it in a request. It avoids an extra "create task" call – the first `tasks/send` both creates and identifies the task. It also guarantees the client can correlate any responses or events to the correct task without confusion. The server must stick to that ID in all updates. This design trades a bit of responsibility to the client for a simpler protocol flow. In practice, it works like starting a conversation: the client says "New Task #123: do X", and the agent replies referencing "Task #123."
  • Explicit Task State Machine: Every task has a state that the protocol tracks (e.g. `submitted`, `working`, `input-required`, `completed`, `failed`, etc.). Instead of implicit status, A2A makes the status visible and structured. For example, if an agent needs more information or a user decision to continue, it can transition the task to `input-required` state. The client sees this state and knows the task is waiting on it to send additional input (perhaps credentials or clarification). This explicit pause/resume mechanism is a clever way to support human-in-the-loop workflows. It's not just request-response; it's request … maybe more requests … then response. An AI agent can ask for missing info in a formal way, and the client can resume the task by sending another message with the same task ID.
  • Long-Running Tasks & Sessions: Tasks can naturally span a long time (minutes, hours, even days if needed). A2A was built "async first," meaning it anticipates tasks that don't finish immediately. The task remains addressable via its ID for its lifetime, and the server can send intermediate updates (more on that in streaming). Additionally, tasks have an optional sessionId for grouping. A session can tie multiple task IDs together (for example, a user's ongoing session might involve several related tasks with a common sessionId). This is useful for context: an agent might maintain some shared context or preferences across tasks in the same session. It's an architectural nod to conversation continuity, without hard-coding any particular approach to context sharing.
  • Embedded History and Metadata: Each Task can carry a history of recent messages and artifacts (outputs) produced. The client can request the server to include N latest messages in any response by setting `historyLength` in a request. This is subtle but important: it means if a client loses context (say it restarted), it can ask the agent to return some context with the next reply, avoiding complete resends. It also helps logging or debugging – the protocol itself can return conversation snippets. Arbitrary metadata on tasks is also allowed for extensibility (e.g. to attach trace IDs, domain-specific info, etc.).

Multimodal Messages via Part Unions

A2A is built to be modality-agnostic from the ground up. Rather than assume all agent communication is text-based, it supports rich content like files and structured data. The way it does this is elegant: each message is composed of one or more Parts, where each Part is a typed content unit. This design allows a single message to include text, images, JSON data, etc., in combination. Key aspects include:

  • Discriminated Union of Part Types: The protocol defines a union of `TextPart`, `FilePart`, and `DataPart` (and could be extended with more). Each part has a `type` field as a discriminator and a content field. For example, a TextPart has `text` content, a FilePart has a `file` (with file details), and a DataPart carries a JSON object/array in a `data` field. By structuring messages this way, A2A can carry multimodal info in a single turn. An agent's reply could include a `TextPart` "Here is the chart:" and a `FilePart` with an image file of the chart – all as one message. This is more powerful than just sending a URL or a separate message for the image.
  • Structured Data Exchanges: The DataPart is particularly notable. It allows agents to exchange JSON data structures directly, which is useful for forms, parameters, or any machine-readable info. Instead of forcing every piece of information through natural language, agents can send a JSON payload when appropriate (for instance, one agent might send another a structured knowledge graph or a set of key-value results from a database). The DataPart comes with an optional `metadata` that could include a schema reference or version. This hints at future interoperability – an agent could declare "I'm sending you data of type X schema" and the other agent could validate or parse accordingly. For AI engineers, this design prompts interesting possibilities: agents could dynamically agree on data formats or fall back to JSON Schema to understand each other's data structures. It moves agent communication closer to API-like data exchange when needed, rather than everything being free-form text.
  • File Transfer Flexibility: Handling files between agents is tricky (images, PDFs, etc.). A2A's FilePart design smartly provides two ways to send a file: either embed it as Base64 bytes, or provide a URI where the file can be fetched. The protocol insists that exactly one of these is used to avoid ambiguity. This dual approach is subtle but powerful – small files or quick data (like a thumbnail or a short text snippet) can be inlined for simplicity, whereas large files can be hosted elsewhere (cloud storage, etc.) and just referenced to avoid bloat. The Agent Card even allows the agent to specify if it prefers certain file transfer methods or size limits (not explicitly in the snippet, but implied by capabilities). Notably, the reference can be any URI the agents agree on (could be an HTTP link, or an internal reference if they share a storage system). The decision to not mandate one single method (like always base64 in JSON) is an engineering pragmatism: it preserves performance and bandwidth when dealing with big media.
  • Multi-Part Messages: By allowing multiple Parts in one message, A2A anticipates richer interactions. An agent could send a primary answer as text and an additional context as structured data, or multiple files at once (e.g. a ZIP plus a textual summary). The protocol defines that a `Message` must contain at least one part, but can have many. This avoids the need for separate channels for attachments or out-of-band data – everything stays in one conversational context (one Task). For AI systems, this is architecturally neat: it means you can upgrade an agent to handle images or PDFs simply by adding new part types, without redesigning the control flow.

Example – FilePart Enforcement in Code

In the Python reference implementation, this rule is enforced with a validator that ensures only one of `bytes` or `uri` is non-null, raising an error if both or neither are provided. This prevents mistakes where an agent might accidentally send a huge file both inline and by link. Below is a snippet illustrating this check:


class FileContent(BaseModel):
    name: str | None = None
    mimeType: str | None = None
    bytes: str | None = None    # base64 content
    uri: str | None = None      # link to content

    @model_validator(mode='after')
    def check_content(self):
        if not (self.bytes or self.uri):
            raise ValueError("Either 'bytes' or 'uri' must be present in the file data")
        if self.bytes and self.uri:
            raise ValueError("Only one of 'bytes' or 'uri' can be present in the file data")
        return self

Streaming and Async Patterns: SSE, Long Polling, and Webhooks

A2A Protocol Illustration

Modes of Interaction for task management.

Real-time responsiveness and asynchronous processing are crucial in multi-agent settings. A2A's designers recognized that some tasks will produce incremental results (e.g., streaming a large language model's output) and some tasks will take a long time where a client can't just hang the HTTP call open. To handle this, A2A builds in multiple interaction modes:

  • Synchronous Call (Blocking): The simplest mode is just calling `tasks/send` and getting a response when the task is done. This works if the agent can complete the task quickly. But A2A doesn't stop there – it treats this as just one possibility.
  • Server-Sent Events[3] (Streaming): For real-time updates, A2A uses SSE (Server-Sent Events), a unidirectional streaming mechanism over HTTP. If an agent's Agent Card sets `capabilities.streaming: true`, clients can invoke `tasks/sendSubscribe` to initiate a task and keep the HTTP connection open for a stream of results. The server responds with `Content-Type: text/event-stream` and then sends events as the task progresses. Each event is essentially a JSON-RPC Response chunk (with the same `id` as the original request) carrying either a status update or a new artifact chunk. This design piggybacks JSON-RPC over SSE – the events are just JSON text in the SSE channel.
  • Resuming Streams: Recognizing that network hiccups happen, A2A even provides a `tasks/resubscribe` method. If a client gets disconnected mid-stream, it can call `tasks/resubscribe` (with the same Task ID) to reopen the SSE stream for that task. The spec leaves it to the server whether it will replay missed events or just continue with new ones, but the capability is there. This is a thoughtful addition for robustness – it acknowledges real-world issues like dropped connections or clients moving between networks.
  • Asynchronous via Webhook (Push): Not all clients can keep a connection open. For example, a client might be a backend service that wants the result whenever it's ready, or an agent that handles many tasks in parallel and doesn't want a thread per task. A2A handles this with push notifications. If an agent's card says `pushNotifications: true`, the client can register a webhook URL for a task. Once set, the agent will, upon task completion (or significant updates), perform an HTTP POST to that URL with the task outcome. In effect, the agent becomes a client briefly, "pushing" the result out.
  • Polling as Fallback: If neither SSE nor push is available (or if a client simply prefers), there's always polling. The `tasks/get` method allows a client to ask for the current status and results of a task at any time. A typical pattern might be: client calls `tasks/send` (which returns immediately with task initial state), then the client periodically calls `tasks/get` until the task's `status.state` is `"completed"` or another terminal state.

Preserving Opacity and Security Boundaries

A driving principle of A2A is that agents can collaborate without revealing their secret sauce. Agents remain opaque to each other, sharing only what they choose to via the protocol. Several design decisions enforce or encourage this separation, which has implications for security and IP protection:

  • No Internal State Leakage: The protocol explicitly avoids any requirement for an agent to divulge its internal chain-of-thought, memory, or tooling. Agents communicate through Tasks, Messages, and Artifacts – outward-facing representations of their actions. The design goal is stated clearly: agents should achieve goals "without needing access to each other's internal state, memory, or tools".
  • No Implicit Identity Sharing: A2A deliberately keeps identity and auth at the HTTP layer instead of inside the JSON payload. There's no "user_id" or API key field in the JSON-RPC calls; instead, standard HTTP Auth (bearer tokens, Basic auth, mTLS, etc.) is used as per the Agent Card's instructions. This design choice decouples the protocol logic from authentication logic.
  • Fine-Grained Authorization via Skills: While A2A doesn't transmit who the user is in JSON, an agent can enforce policy on what another agent is allowed to do based on the skill or action invoked. The spec suggests that servers may authorize requests based on "the specific skills requested" and other factors like OAuth scopes of the client's token.
  • Security Considerations in Discovery: The Agent Card itself might contain sensitive data (like internal endpoint URLs or OAuth info). The spec recommends securing the Agent Card endpoint (e.g., require auth to fetch it, or restrict it) if needed. It also discourages putting any actual secrets in the card (like not embedding raw API keys).
  • Opaque vs Transparent Collaboration – A Philosophical Stance: It's worth noting that A2A's approach to keep internal reasoning hidden is a choice. One could imagine an alternative where agents share their intermediate thoughts or chain-of-thought to better coordinate. A2A explicitly does not do that – it treats agents more like microservices than like co-running processes. This has architectural benefits (clear interfaces, encapsulation) but also invites debate: might agents achieve deeper synergy if they shared more of their internal state?

Extensibility and Future-Proofing the Protocol

A2A is labeled version 0.1.0 – it's early days. Yet the design shows signs that the authors intended it to evolve and integrate with other standards, rather than be a closed solution. Some notable extensibility decisions:

  • Metadata Everywhere: Almost every major object in the A2A schema has a `metadata` field for arbitrary key-value pairs (Task, Message, Part, Artifact, etc.). This is a classic forward-compatibility move. It means if future versions of A2A (or private extensions) need to include extra info – say a language code on a message, or a trace ID for monitoring – they can drop it into metadata without breaking the protocol.
  • Generic Method Space: The JSON-RPC method naming scheme (`"tasks/..."`) gives a namespace where new methods can live. Already, we see placeholders or potential for more: e.g., `tasks/cancel` exists, and the Agent Card has a flag `stateTransitionHistory` reserved for a future feature. One could imagine new methods like `tasks/pause` or `tasks/list` being added later.
  • Complementary Protocols, Not One Protocol to Rule Them All: A2A's scope is intentionally limited to agent-to-agent communication and task delegation. It consciously does not cover how an agent connects to its own tools or large language model. That's the domain of another emerging standard, Model Context Protocol (MCP)[4], which A2A is designed to complement rather than replace. This separation of concerns is an architectural decision: it keeps A2A focused and simpler.
  • Community and Open Evolution: While not a technical facet of the protocol itself, it's worth noting that A2A is open-sourced and clearly inviting community contributions. The design decisions we've highlighted (JSON-based, metadata fields, skill descriptors) all suggest an intent to make the protocol general-purpose and not tied to Google's internal needs alone.

Agent Cards for Discovery and Capability Negotiation

How do agents find each other and know what they can do? A2A's answer is the Agent Card, a small JSON manifest each agent publishes. Every A2A agent must provide an Agent Card describing itself. This design echoes practices like OpenAPI docs or service discovery, but tailored to AI agents:

  • Discovery as First-Class: The Agent Card is typically hosted at a well-known URL (by default `/.well-known/agent.json` on the agent's server). This means an agent can be "found" if you know its domain. Alternatively, Agent Cards can be listed in registries or exchanged out-of-band. By not hard-coding a central directory, A2A stays flexible.
  • Capabilities Advertisement: The Agent Card contains fields for the agent's protocol capabilities like `streaming` and `pushNotifications` support. This is a subtle negotiation mechanism. For example, if `streaming: true` is advertised, a client knows it can use SSE realtime updates with this agent; if false, the client will stick to polling or synchronous calls.
  • Skills and Modalities: Each agent lists its skills – essentially the tasks or domains it can handle – in the card. A skill comes with a name, description, example usage, and even preferred input/output formats. Listing skills transforms the black-box agent into something discoverable and invokable by other agents.
  • Default I/O Formats: The Agent Card can specify default input and output content types (MIME types) that the agent expects, plus overrides per skill. This is a clever way to set expectations on how to communicate. For example, an agent might generally take `text/plain` input, but for a particular skill it might also accept `application/json`.

Conclusion

In sum, A2A's design shows a maturation in the agent space: moving from ad-hoc integrations to protocol-driven interoperability. It carries lessons from software engineering into the AI realm, and it does so with a mix of conceptual simplicity and technical nuance. As AI practitioners, understanding these decisions helps us design better systems and perhaps contribute to the conversation of how agents should talk to each other. The hope is that by embracing protocols like A2A, we enable a richer AI ecosystem where specialized agents can freely cooperate – each secure in its own box, yet working together through a common language to achieve what none could do alone.

Citations

Related Articles