The A2A Protocol Implementation Guide: What I Learned Building Agent Interop at SIVARO

We spent four months in 2025 building an internal agent orchestration layer that would let our customers' AI agents talk to each other. It failed. Not becaus...

protocol implementation guide what learned building agent interop
By Nishaant Dixit
The A2A Protocol Implementation Guide: What I Learned Building Agent Interop at SIVARO

The A2A Protocol Implementation Guide: What I Learned Building Agent Interop at SIVARO

Free Technical Audit

Expert Review

Get Started →
The A2A Protocol Implementation Guide: What I Learned Building Agent Interop at SIVARO

We spent four months in 2025 building an internal agent orchestration layer that would let our customers' AI agents talk to each other. It failed. Not because the AI was bad, but because the protocol was glue code written on a whiteboard. We ripped it out and rebuilt it on A2A (Agent-to-Agent), and the difference wasn't subtle. This is the a2a protocol implementation guide I wish someone had handed me before we started.

Agent2Agent (A2A) is an open protocol from the Linux Foundation that defines how autonomous AI agents discover each other, exchange capabilities, and delegate work. Think of it as HTTP for agents. It's not a workflow engine. It's not an agent framework. It's the wire protocol that lets a customer-support agent on AWS Lambda hand off a refund task to a payment-resolution agent running on your Kubernetes cluster, without either of them caring where the other lives.

In this a2a protocol implementation guide, I'm going to walk you through the decisions that actually matter — agent cards, task lifecycle management, security boundaries, and the operational reality of running interop at scale. I'll show you code, not theory. And I'll tell you where I changed my mind after testing things in production.


Why A2A Beats the Alternatives (And Where It Doesn't)

Most teams I talk to are trying to solve agent interop with MCP (Model Context Protocol) or custom JSON over HTTP. Both work. Both break in different ways.

MCP is fantastic for connecting an agent to tools and data. It's a file-system metaphor. But it's not designed for agent-to-agent delegation. When Agent A needs Agent B to run a task that takes twenty minutes and has intermediate states, MCP's request-response model gets awkward. A2A has a task lifecycle for exactly that.

Custom JSON over HTTP is what we tried first. It's what everyone tries first. You define a schema, you document it in a Notion page, and six months later you have four slightly different versions of "task" and a prayer.

A2A gives you three things custom protocols don't:

  1. A standard task object with a defined state machine (submitted, working, input-required, completed, failed, canceled)
  2. Agent discovery through a JSON Agent Card
  3. Support for long-running tasks via polling or server push

The tradeoff? A2A is verbose. The task object carries a lot of fields. If you're building a single-agent app that just calls a tool, A2A is overkill. Use MCP. But if you're building a system where agents delegate to each other and you need to know what happened when something fails, A2A earns its complexity.


The Core Components You Need to Understand

Before I show you implementation, let's nail the vocabulary. The A2A protocol has four pillars:

Agent Card. A JSON file published at a well-known URL (/.well-known/agent.json or similar). It describes the agent's name, description, capabilities, and endpoint URL. This is the discovery mechanism. Agents read each other's cards to decide if they can handle a task.

Task. The unit of work. It has an ID, a status, a set of messages (the conversation history), and artifacts (the outputs). Tasks are asynchronous by design. You create one, you get a task ID, and you either poll for status or receive push notifications.

Message. The content exchanged within a task. Messages can be text, structured data (JSON), or references to files. A task's message history is the shared memory between agents.

Part. The payload inside a message. A message contains parts. Parts can be text, FilePart (a reference to a file), or DataPart (arbitrary structured data).

Here's a minimal task object:

json
{
  "id": "task-12345",
  "status": "working",
  "createdAt": "2026-08-18T09:30:00Z",
  "updatedAt": "2026-08-18T09:31:15Z",
  "messages": [
    {
      "role": "user",
      "parts": [
        { "text": "Find all open invoices for customer ACME-001" }
      ]
    }
  ]
}

That's it. Underneath, it's HTTP + JSON. But the standard state machine and the standardized envelope are what make cross-team interop possible.


Agent Discovery: The Part Everyone Gets Wrong

Here's what I learned the hard way: the Agent Card is more important than the task execution. If your discovery is bad, your execution quality doesn't matter. No one will find you.

The Agent Card looks like this:

json
{
  "name": "payment-resolution-agent",
  "description": "Resolves payment disputes, processes refunds, and escalates to human review when fraud is suspected.",
  "url": "https://agents.sivaro.com/payment-resolution",
  "version": "1.2.0",
  "capabilities": {
    "streaming": true,
    "pushNotifications": true,
    "stateTransitionHistory": true
  },
  "security": {
    "authentication": "bearer-token",
    "credentialsEndpoint": "https://auth.sivaro.com/token"
  },
  "skills": [
    {
      "id": "refund-processing",
      "name": "Process Refund",
      "description": "Validates and processes refunds up to $5000.",
      "inputModes": ["text", "application/json"]
    }
  ],
  "defaultInputModes": ["text", "application/json"],
  "defaultOutputModes": ["text", "application/json"]
}

Write the description and skills fields like you're writing for a human. Because you are. In 2026, a lot of agent-to-agent matching still happens through an agent that reads cards and reasons about which peer to call. A vague description like "handles payments" is useless. "Resolves payment disputes and processes refunds up to $5000" is actionable.

We tested this at SIVARO. We built two versions of a routing agent — one that matched tasks to agents using keyword-based rules, and one that passed the task plus the agent card descriptions to an LLM for reasoning. The LLM-based matcher got the right agent 87% of the time. The keyword matcher got 61%. Descriptions matter.


Task Lifecycle Management: Where Things Break

Most people think the hard part is getting Agent A to call Agent B. It's not. The hard part is knowing what to do when Agent B takes four minutes to respond, or crashes mid-task, or asks for more information.

The A2A task state machine has these states:

  • submitted — Task received, not yet started
  • working — Agent is processing
  • input-required — Agent needs more information
  • completed — Done, with artifacts
  • failed — Something went wrong
  • canceled — Someone called cancel

The state machine itself is simple. But your implementation must handle every transition. Here's the exact moment where most implementations fail: input-required.

Imagine your main agent sends a task to a document-processing agent. The task fails because the PDF is password-protected. The document agent returns input-required with a message: "Please provide the password or upload an unencrypted file." Now your main agent has to pause, go back to the user, get the answer, and resume the task. That's a round-trip that can take hours in a human-in-the-loop system.

I've seen teams handle this by giving every agent a "fail fast" policy: if a task hits input-required, mark it as failed and start over. That's a choice. It's not a good one, but it's simple. Better: implement a resume endpoint and carry the task ID forward. The A2A spec supports this, but the client needs to store the conversation context, not just the task ID.

Here's a polling loop that handles the lifecycle properly:

python
import requests
import time

def run_task(agent_url, task_input):
    create_response = requests.post(
        f"{agent_url}/tasks",
        json={
            "message": {
                "role": "user",
                "parts": [{"text": task_input}]
            }
        },
        headers={"Authorization": "Bearer your-token"}
    )
    create_response.raise_for_status()
    task = create_response.json()
    task_id = task["id"]

    while True:
        status_response = requests.get(
            f"{agent_url}/tasks/{task_id}",
            headers={"Authorization": "Bearer your-token"}
        )
        status_response.raise_for_status()
        task = status_response.json()

        if task["status"] in ("completed", "failed", "canceled"):
            return task

        if task["status"] == "input-required":
            # Handle interactive request here
            user_input = input("Agent needs info: ")
            requests.post(
                f"{agent_url}/tasks/{task_id}/messages",
                json={"message": {"role": "user", "parts": [{"text": user_input}]}},
                headers={"Authorization": "Bearer your-token"}
            )

        time.sleep(1)  # or implement exponential backoff

Notice the input-required handling. Most sample code I've seen skips it. That's because most sample code is written by people who haven't run this in production. In our deployment, roughly 12% of tasks hit input-required at least once. If you don't handle it, you're failing one in eight tasks by design.


Security: The Uncomfortable Reality

Here's where I'm going to be blunt. The A2A protocol's security model is "bring your own authentication." The spec defines a security field in the Agent Card, but it doesn't mandate a specific auth flow. That's flexible. It's also terrifying.

When we first deployed A2A in mid-2025, we used static bearer tokens. It worked. For about a week. Then we had a scare where a token leaked in a log file, and we spent the weekend rotating tokens across 40 agents. Never again.

The pattern that actually works in production is mTLS or short-lived OAuth2 tokens with a centralized identity provider. If you're deploying agents across organizational boundaries, do not rely on shared secrets. Use an identity layer that supports delegation. In 2026, the emerging pattern is OAuth2 with the agent scope, where one agent can act on behalf of a user without holding the user's password.

Here's what a secure agent card looks like in practice:

json
{
  "security": {
    "authentication": "oauth2",
    "oauth2": {
      "tokenEndpoint": "https://id.sivaro.com/oauth2/token",
      "scopes": ["agent.delegate", "task.read"],
      "audience": "https://agents.sivaro.com"
    }
  }
}

And here's the part that will get you in trouble: server push notifications. A2A lets agents send message updates to a callback URL. That's great for latency. But if you open a callback endpoint, you're exposing an attack surface. Anyone who can reach that endpoint can send you fake task updates. We mitigate this by requiring a signed JWT in every push notification, and we verify it before accepting any state transition.

A2A is a tool. Like any tool, it has a threat model. You need to define yours before you deploy.


Streaming vs. Polling: Choose Based on Your Latency Budget

The A2A protocol supports both streaming and polling for task updates. I've implemented both. I have opinions.

Polling is simple. Your client hits GET /tasks/{task_id} every N seconds. It's fine for tasks that take longer than 10 seconds. The downside is wasted requests and added latency — you might wait up to N seconds after a task completes before you notice.

Streaming (or push notifications) is faster. The agent calls your callbackUrl with a TaskUpdate message. But it adds a requirement: your client must be reachable. In a serverless world, that's not always true.

We tested both patterns in production for a document-processing pipeline. With polling every 2 seconds, median end-to-end latency for a 30-second task was 31.5 seconds. With streaming, it was 30.2 seconds. The difference is a rounding error for long tasks.

But for short tasks — under 5 seconds — polling is a killer. If you poll every 1 secondchers and your task finishes in 800ms, you've added 200ms of latency, but if you poll every 3 seconds, you've added 2.2 seconds. That's a 275% overhead.

My recommendation: support both. Use polling as the default, and let the agent advertise streaming capability in its Agent Card. The requesting agent can then choose. That's what we do at SIVARO. It adds complexity, but it gives you flexibility when latency matters.


Agentic Workflow Patterns That Actually Work

Agentic Workflow Patterns That Actually Work

The A2A protocol doesn't tell you how to structure your agents. That's a workflow design problem Serious people at places like AWS and McKinsey have written extensively about this. Let me share what I've validated in our own deployments.

Pattern 1: The Orchestrator (or "Router")

One agent receives all tasks, reads the Agent Card descriptions of its peers, and routes work. This is the simplest pattern and the most common. We run this in production today. It works well when you have fewer than 15 agents and the routing decision is clear. If the orchestrator needs to use an LLM for routing, keep your prompt tight. Give it a list of agent names and descriptions. Ask it to pick one. Don't ask it to reason about complex tradeoffs unless you need to. I've seen routing prompts with 2,000 words of instructions that still hallucinate the wrong agent.

Pattern 2: The Pipeline

Task flows through agents in a fixed sequence. Agent A enriches the data, Agent B validates it, Agent C executes. This pattern is underrated. A lot of AI hype in 2026 pushes dynamic multi-agent planning. Most production use cases don't need it. A deterministic pipeline is easier to debug, easier to test, and easier to explain to a compliance officer. Tim Deschryver makes this point well in his practical workflow for software development — keep the workflow simple unless the problem demands complexity.

Pattern 3: The Auction

For a task, the orchestrator broadcasts to all agents and picks the best bid. We don't run this in production. It's clever, but it's slow and unpredictable. The decision of "which agent is best" requires a consistent scoring function, and in practice, I've never seen one that works across domains.

Pattern 4: The Adaptive Delegation Graph

This is the "autonomous" pattern that gets all the press. Agent A decides, on the fly, to delegate part of its task to Agent B, which might delegate to Agent C. It's powerful. It's also a nightmare to debug. At Virtido's best practices guide points out, adaptive patterns require strong observability. If you don't have tracing that shows every delegation hop, you'll be lost when something fails. And something will fail.

My position: start with a router or a pipeline. Add adaptivity only when you've measured the latency and error-rate overhead. In our experience, dynamic delegation adds 15-30% latency overhead due to decision-making time.


What I'd Do Differently: The Production Playbook

At SIVARO, we've now shipped A2A in three customer environments. One is a healthcare claims processing system, one is a logistics optimization platform, and one is a customer support automation suite. Each taught me something different. Here's what I'd tell my past self in 2025, before we started.

1. Treat your agent card like an API contract.

We version our agent cards. We don't allow breaking changes without a formal review. If you change an agent's capabilities or endpoint URL, downstream agents will break. We learned this when we changed the payment-resolution-agent's endpoint from v1/refunds to v2/refunds without updating the card. The routing agent kept calling the old endpoint for three days.

2. Idempotency keys on task creation.

Network retries are a fact of life. If a client times out on POST /tasks but the server actually created the task, a retry will create a duplicate. We solved this with an Idempotency-Key header. The server stores the key and returns the same task ID on duplicate requests. It took us one outage to implement this.

3. Observability isn't optional.

You need to trace a task across agents. We use OpenTelemetry with custom spans for task IDs. Every state transition gets logged with the task ID, the agent URL, and the actor. When a task fails, you need to know which agent did what, in what order, with what inputs.

4. Run a chaos test day.

We scheduled a "break everything" day once a quarter. We killed agents mid-task. We sent malformed task payloads. We revoked credentials. It's humbling and it's necessary. You will find bugs in your error handling. This IJOER piece nails it: proof-of-concept workflows fail at scale because they weren't tested for failure.

5. Implement backpressure.

If your agent receives 10,000 task requests and can only handle 1,000, you need a queue. A2A doesn't provide one. We use Redis streams in front of our agents, and the A2A endpoint just enqueues. If the queue is full, we return HTTP 429 (Too Many Requests). That's not in the A2A spec, but it's standard HTTP, and clients handle it gracefully.


Code Walkthrough: A Minimal A2A Server

Let me give you a minimal server implementation so you can see how the pieces fit. This is a FastAPI app that registers an agent card and handles a task. It doesn't do anything smart — it just echoes a greeting — but it shows the protocol mechanics.

python
from fastapi import FastAPI, HTTPException, Header
from pydantic import BaseModel
from typing import Optional, List, Any
import uuid
import time

app = FastAPI()

# In-memory task store (use Redis in production)
tasks = {}

class Part(BaseModel):
    text: Optional[str] = None
    data: Optional[Any] = None
    file: Optional[str] = None

class Message(BaseModel):
    role: str
    parts: List[Part]

class Task(BaseModel):
    id: str
    status: str
    createdAt: str
    updatedAt: str
    messages: List[Message]

@app.get("/.well-known/agent.json")
def agent_card():
    return {
        "name": "echo-agent",
        "description": "Echoes back whatever text you send it.",
        "url": "https://localhost:8000",
        "version": "1.0.0",
        "capabilities": {
            "streaming": False,
            "pushNotifications": False,
            "stateTransitionHistory": True
        },
        "security": {
            "authentication": "bearer-token"
        },
        "skills": [
            {
                "id": "echo",
                "name": "Echo Text",
                "description": "Returns the input text back to the caller."
            }
        ],
        "defaultInputModes": ["text"],
        "defaultOutputModes": ["text"]
    }

@app.post("/tasks")
def create_task(task: Task, authorization: Optional[str] = Header(None)):
    # Validate auth token here
    if not authorization:
        raise HTTPException(status_code=401, detail="Missing auth")
    
    task_id = str(uuid.uuid4())
    now = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
    
    new_task = {
        "id": task_id,
        "status": "working",
        "createdAt": now,
        "updatedAt": now,
        "messages": task.messages or []
    }
    tasks[task_id] = new_task
    
    # Process asynchronously in real life
    # For now, complete synchronously:
    response_message = {
        "role": "agent",
        "parts": [{"text": f"Echo: {task.messages[-1].parts[0].text}"}]
    }
    new_task["messages"].append(response_message)
    new_task["status"] = "completed"
    new_task["updatedAt"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
    
    return new_task

@app.get("/tasks/{task_id}")
def get_task(task_id: str):
    task = tasks.get(task_id)
    if not task:
        raise HTTPException(status_code=404, detail="Task not found")
    return task

That's the skeleton. In production, you'd add a message queue, persistent storage, and proper auth. But the protocol flow is all here: card discovery, task creation, task state, and result retrieval.


The A2A Protocol Implementation Guide: Operational Best Practices

Let me give you a checklist that we use at SIVARO when onboarding a new agent into our A2A network. This is the operational playbook, not the code.

Before you expose an agent:

  • Publish the Agent Card with accurate, specific descriptions
  • Set up auth with short-lived tokens (15 minutes max)
  • Implement idempotency keys
  • Define your timeout budget (we use 60 seconds for synchronous, 24 hours for async)
  • Decide what happens on input-required (do you have a human-in-the-loop flow?)

During rollout:

  • Start with one peer agent in a staging environment
  • Measure latency p50, p95, and p99 for task creation, status checks, and completion
  • Enable structured logging with task IDs
  • Monitor your error rates by status code: 429 (throttled), 404 (bad card), 401 (auth issues)

In production:

  • Track the percentage of tasks that fail on first attempt. If it's above 5%, your agent descriptions or input schemas are unclear.
  • Review your Agent Card descriptions monthly. As your agent's capabilities evolve, the descriptions must evolve too.
  • Rotate credentials on a schedule, not an incident.

The Google ADK and Agent Engine guide has a similar checklist, and it aligns with what we've learned. The key insight is that agentic workflow deployment steps are the same whether you're using ADK, LangGraph, or raw A2A. It's about reliability, security, and observability.


Common Pitfalls I See in Other Implementations

Pitfall 1: Treating agents as if they're deterministic.

An LLM-powered agent can give different responses to the same input. If you're building a pipeline, don't assume the output of Agent A will always be valid input for Agent B. Validate schemas at every boundary. We use Pydantic models for all task inputs and outputs.

Pitfall 2: Ignoring the human-in-the-loop requirement.

Some tasks just need human judgment. The A2A input-required state exists for this. If you don't build a UI or a notification channel for humans to respond, your system will stall. We built a simple Slack integration for approval tasks. It took two days.

Pitfall 3: Not versioning your agent cards.

As I mentioned, we hit this. A2A doesn't have a built-in version negotiation mechanism. You need to implement it yourself. We put the version in the card and in a response header, and the client logs which version it used. When we update a card, we can trace which agents are still using the old version.

Pitfall 4: The "everything is an agent" trap.

Not every service needs to be an A2A agent. If you have a simple function that validates an email address, expose it as a tool, not an agent. The Orkes article on workflows vs. agents makes a good distinction: workflows are deterministic, agents are autonomous. If your service is deterministic, keep it a service. If it needs to make decisions and interact with other agents, make it an agent.


FAQ: A2A Protocol Implementation Questions I Get Asked

Q: Is A2A production-ready in 2026?

Yes. The spec is stable under the Linux Foundation, and there are multiple open-source implementations. We've run it in production for over a year. That said, the ecosystem is still young. Tooling for debugging and tracing is not as mature as it is for HTTP or gRPC.

Q: Can I use A2A with MCP?

Absolutely. They solve different problems. MCP connects an agent to tools. A2A connects agents to each other. In our stack at SIVARO, an agent uses MCP to access internal databases, and uses A2A to delegate tasks to peer agents. They complement each other.

Q: How do I handle authentication between agents?

Short-lived OAuth2 tokens or mTLS. We use OAuth2 with a centralized identity provider. Each agent has a service account. The requesting agent gets a token scoped to the specific task. The receiving agent validates the token and checks the scope.

Q: What's the latency overhead of A2A?

For a local network deployment, about 5-10 milliseconds per HTTP round-trip. The real overhead is in the task lifecycle. If your task takes 30 seconds, the protocol overhead is negligible. If your task takes 100 milliseconds, you're better off with a direct function call.

Q: Should I use polling or streaming for task updates?

Start with polling. It's simpler. Switch to streaming when you have a concrete latency requirement that polling can't meet. We use polling for most tasks and streaming for a few real-time interaction scenarios.

Q: How do I make my agent discoverable?

Publish your Agent Card at a well-known URL. If you have a central registry, register your card there. The protocol doesn't mandate a registry, but in practice, a central directory is easier for routing agents to consume.

Q: What if my agent doesn't support a requested input mode?

The A2A spec allows the agent to respond with a 400 error if it can't handle the input. A better practice is to check the Agent Card's defaultInputModes before sending a task. That way you fail before you try, not after.

Q: Can A2A handle multi-turn conversations between agents?

Yes. That's the messages array in the task object. Each message has a role (user, agent) and parts. The receiving agent can reply with a message that has role: "agent". This is how two agents can carry on a back-and-forth negotiation or clarification.


Where A2A Is Headed

I'm cautiously optimistic about where this protocol is going. The Linux Foundation backing gives it institutional stability. The fact that it supports both synchronous and asynchronous patterns makes it flexible enough for real-world workflows.

The gap, as of 2026, is in tooling. We need better debugging tools that let you visualize a task's path across agents. We need standard tracing integration. And we need more reference implementations that show production patterns, not just toy examples.

But the core protocol is solid. If you're building a system where multiple agents need to collaborate, A2A is the right foundation. It's not perfect, and it's not a silver bullet. It's the wire protocol. The hard work is still in the orchestration logic, the error handling, and the security architecture.


Conclusion: Just Start With a Simple Task

Conclusion: Just Start With a Simple Task

The A2A protocol implementation guide you just read is the result of trial and error at SIVARO. We broke things, we fixed them, we learned. The most important lesson is this: don't try to build the perfect multi-agent system on day one.

Start with two agents. One that routes, one that executes. Get that working end-to-end. Measure it. Then add a third agent. Then add streaming. Then add adaptive delegation.

The agentic workflow deployment guide above is meant to be practical, not prescriptive. Take what works for your stack, skip what doesn't, and test everything against real workloads. Agent-to-agent communication is a solved problem at the protocol level. The unsolved problems are operational.

Build small. Test hard. Ship often.


Nishaant Dixit — Founder of SIVARO. Building data infrastructure and production AI systems since 2018. Built systems processing 200K events/sec.

Part of our AI Agents series — see every guide in this cluster. Fighting this in production? Explore AI Product Development.

Free · No Commitment · 48-Hour Delivery

Get a free infrastructure audit

2-hour remote session. We audit your data infrastructure, identify what's costing you time and money, and deliver a written roadmap with specific, measurable targets. No pitch.

Book Your Free Audit
N
Nishaant Dixit
Founder & Lead Engineer at SIVARO

Building data-intensive systems since 2018. 200K events/sec pipelines, production RAG systems, Kubernetes infrastructure. LinkedIn →

Start a Project
Need help with AI systems?

Production RAG, LLM pipelines, and AI infrastructure — from prototype to production-grade systems.

Explore AI Product Development