SIVARO
MCP (Model Context Protocol)

The A2A Protocol for Multi-Agent Systems Tutorial

You've got five agents talking to each other, and it's a mess. Each one speaks its own dialect of JSON. None of them can find the right peer. Debugging a cro...

protocolmulti-agentsystemstutorial
By Nishaant Dixit
The A2A Protocol for Multi-Agent Systems Tutorial

The A2A Protocol for Multi-Agent Systems Tutorial

Free Technical Audit

Expert Review

Get Started →
The A2A Protocol for Multi-Agent Systems Tutorial

You've got five agents talking to each other, and it's a mess. Each one speaks its own dialect of JSON. None of them can find the right peer. Debugging a cross-agent call takes three hours. I've been there. That's exactly why Google dropped the Agent2Agent (A2A) protocol in April 2025, and why by mid-2026, it's become the de facto way to wire production systems together.

Here's the honest truth: most people think multi-agent systems are a model problem. Pick the right LLM, and everything works. That's wrong. The hard part isn't the intelligence. It's the plumbing. A2A solves the plumbing problem.

This a2a protocol for multi agent systems tutorial will show you what A2A actually is, how to set up discovery and routing, and where it beats MCP for AI agents running on AWS.

What the Hell is A2A, Really?

A2A is an open protocol that lets agents communicate directly. No middleware god-object. No brittle hardcoded endpoints. It standardizes how agents discover each other, send tasks, and share state.

The core architecture is a client-server model. One agent acts as the client, sending a task to another agent's server. The server responds with a task object that tracks the entire lifecycle.

Here's what an A2A message looks like:

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tasks/send",
  "params": {
    "agent_url": "https://agents.sivaro.dev/v1/revenue-forecaster",
    "task_id": "task-99472",
    "message": {
      "role": "user",
      "parts": [
        { "text": "Project Q3 revenue based on the last 12 months of pipeline data." }
      ]
    }
  }
}

Simple, right? A task gets an ID. The agent picks it up, processes it, and either returns a result or signals that it's still working. That task_id becomes the thread that ties everything together when you're orchestrating multiple agents.

The protocol defines four core primitives:

  • Agent Card – A discoverable JSON file that advertises an agent's capabilities.
  • Task – The unit of work sent between agents.
  • Message – The actual content of the interaction.
  • Artifact – Structured output from a completed task (files, JSON blobs, structured data).

If you're thinking, "This looks like a REST API patterned after human work tickets," you're not wrong. That's the point. A2A deliberately maps the messy reality of agent negotiations onto a predictable lifecycle: submitted, working, completed, or failed.

Agent Discovery: Why Your Agents Can't Find Each Other

At SIVARO, we hit this wall in early 2025. We had a procurement agent that needed to verify vendor risk with a separate compliance agent. The integration was a hand-rolled mess of webhooks and polling loops. Every time we scaled the fleet, something broke.

The fix was Agent Cards.

An Agent Card is a well-known JSON document hosted at a predictable URL. It advertises what the agent does, its authentication requirements, and its available capabilities. When an agent needs a peer, it queries a directory (or fetches the card directly) to see who can help.

json
// https://agents.sivaro.dev/v1/revenue-forecaster/agent-card.json
{
  "name": "Revenue Forecaster",
  "description": "Generates revenue projections from pipeline and historical data.",
  "url": "https://agents.sivaro.dev/v1/revenue-forecaster",
  "version": "1.2.0",
  "capabilities": {
    "streaming": true,
    "push_notifications": false,
    "state_transition_notifications": true
  },
  "security": {
    "scheme": "bearer",
    "credentials_requirement": "required"
  },
  "skills": [
    {
      "id": "forecast_revenue",
      "name": "Forecast Revenue",
      "description": "Takes pipeline data and returns a 90-day projection.",
      "input_modalities": ["text", "json"],
      "output_modalities": ["json"]
    },
    {
      "id": "historical_trend",
      "name": "Historical Trend Analysis",
      "description": "Analyzes 12-24 months of revenue data for seasonality.",
      "input_modalities": ["json"],
      "output_modalities": ["json"]
    }
  ]
}

At first I thought this was a branding problem — turns out it was routing. Once we standardized Agent Cards, we could build a simple registry service that agents query when they need a task done.

A2A agent discovery and routing setup is straightforward:

  1. Host Agent Cards at predictable endpoints.
  2. Stand up a registry (even a simple Redis cache works) that agents can query by capability.
  3. Implement a routing layer that reads the card, checks authentication, and forwards the task.

We tested three days of prototype code against our old webhook mess. The debugging time on cross-agent failures dropped by about 80%. Because we finally had a standard way to say "who offers X" and "how do I talk to them."

Building Your First A2A Handshake

Let me walk you through the actual setup. I'm going to assume you're working with Python, since that's what most of our clients at SIVARO use.

First, install the official SDK:

bash
pip install a2a-sdk

Now let's set up an A2A agent server. This is the part that trips people up — they think they need massive orchestration frameworks. You don't. Start with a minimal agent that can handle one skill.

python
from a2a_sdk.server import A2AServer, AgentCard, Skill

class SupportAgent(A2AServer):
    def __init__(self):
        super().__init__(
            AgentCard(
                name="Support Triage Agent",
                description="Routes customer support tickets to the appropriate team.",
                url="https://agents.sivaro.dev/v1/support-triage",
                skills=[
                    Skill(
                        id="triage_ticket",
                        name="Triage Customer Ticket",
                        description="Categorizes support tickets by urgency and topic."
                    )
                ]
            )
        )

    async def handle_task(self, task):
        # Parse the incoming message
        message = task.message
        ticket_text = message.parts[0].text

        # Your logic here — we use a small model for this, not GPT-4
        category = classify_ticket(ticket_text)
        urgency = assess_urgency(ticket_text)

        result = {
            "category": category,
            "urgency": urgency,
            "next_step": f"Route to {category} queue with {urgency} priority"
        }

        return {
            "artifacts": [
                {"name": "triage_result", "content": result}
            ]
        }

That's it. You now have an A2A server agent. The SDK handles the JSON-RPC transport, the task lifecycle, and the Agent Card advertisement.

Now, the client side. This is where another agent requests work.

python
from a2a_sdk.client import A2AClient

async def request_triage(agent_url: str, ticket_text: str):
    client = A2AClient(agent_url)

    result = await client.send_task(
        skill_id="triage_ticket",
        message={"role": "user", "parts": [{"text": ticket_text}]}
    )

    if result.status == "completed":
        return result.artifacts[0]["content"]
    else:
        # Handle failure or timeout
        log.error(f"Task {result.task_id} failed: {result.error}")
        return None

A few lessons from production. First, if your task runs longer than 30 seconds, don't wait synchronously. Use task status polling or webhooks. Second, never put sensitive credentials in the message — A2A supports enterprise authentication standards, use them.

Long-Running Tasks: The Streaming Reality

The example above handles instant work. But what about an agent that needs to generate a 40-page financial report, cross-referencing three data sources?

You need asynchronous task management. A2A supports this natively.

python
from a2a_sdk.server import A2AAsyncServer

class ReportAgent(A2AAsyncServer):
    async def handle_task(self, task):
        # Acknowledge receipt immediately
        await self.update_task_state(task.id, status="working", progress=0.0)

        try:
            # Do the long work
            report = await generate_report(task.message)

            # Push the final artifact
            await self.update_task_state(
                task.id,
                status="completed",
                artifacts=[{"name": "report.pdf", "content": report}],
                progress=1.0
            )
        except Exception as e:
            await self.update_task_state(
                task.id,
                status="failed",
                error={"code": "GENERATION_ERROR", "message": str(e)}
            )

The client, in turn, polls for status or subscribes to notifications. We've run agents on AWS Lambda that handle this pattern without issue — as long as you configure the timeout properly. The killer feature here is that the protocol treats "still thinking" as a first-class state. No more designing your own state machine to track every agent.

A2A vs MCP for AI Agents on AWS

A2A vs MCP for AI Agents on AWS

Here's where I'll anger some people. Most practitioners think MCP (Model Context Protocol) and A2A are competitors. In 2026, we stopped framing it that way at SIVARO. They solve different problems.

MCP connects an LLM to tools — databases, API endpoints, files. It's about one model accessing a controlled set of resources. Think of it as the last mile between the AI and your data.

A2A connects agents to agents — full loops of autonomous logic. You're not asking an LLM to call a database. You're asking one intelligent system to negotiate with another.

On AWS, the distinction matters because of how you architect your infrastructure:

  • MCP on AWS is natural with Lambda and API Gateway. You expose a function behind an MCP server. Fast, cheap, stateless.
  • A2A on AWS typically involves the same compute (Lambda, ECS, or SageMaker) but adds a discovery layer. I'd suggest using a DynamoDB table to store Agent Card metadata, and an API Gateway endpoint to serve those cards.

We tested both protocols in a production procurement system on AWS in Q4 2025. The finding? Use MCP to connect an agent to your internal data stores. Use A2A when an agent decides it needs another agent.

Trying to use MCP for agent-to-agent communication led to deep coupling. Every agent needed to know the exact MCP endpoint and tool schema of every other agent. It became a static web of dependencies. A2A's discovery layer meant an agent could simply say "I need a fraud check" and find the best available agent at runtime.

But don't ditch MCP entirely. In our AWS stack, each agent runs its own internal MCP connections to the data lake and vector database. A2A is the external nervous system. MCP is the internal muscle.

The hybrid architecture we run now:

  • Agent A (Procurement Agent on ECS) → MCP to query S3 tables via Glue
  • Agent B (Risk Assessment Agent on Lambda) → MCP to look up compliance rules in a vector store
  • A2A protocol between Agent A and Agent B, with Agent Cards registered in API Gateway + DynamoDB

Practical Routing with Agent Cards on AWS

Setting up the discovery layer for A2A on AWS took me about two hours. Here's the pattern we now use for every client:

  1. Agent Card Storage: Put each Agent Card JSON in an S3 bucket or DynamoDB.
  2. Directory Lambda: Build a small function that queries the directory for agents matching a capability.
typescript
// DirectoryQuery Lambda (TypeScript)
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { QueryCommand } from "@aws-sdk/lib-dynamodb";

const ddb = new DynamoDBClient({ region: process.env.AWS_REGION });

export async function handler(event) {
  const skillId = event.arguments.skillId;
  const params = {
    TableName: "a2a-agent-registry",
    IndexName: "skillId-index",
    KeyConditionExpression: "skillId = :sid",
    ExpressionAttributeValues: { ":sid": skillId },
    Limit: 5
  };

  const result = await ddb.send(new QueryCommand(params));

  return result.Items.map(item => item.agentCardUrl);
}
  1. API Gateway: Expose this Lambda as a REST endpoint. Agents call it, get a list of agent card URLs, then fetch the full cards to verify auth requirements.

The latency overhead for discovery is ~20ms if your Lambda is warm. It's negligible. The benefit is your agents now scale horizontally — add a new fraud-detection agent, publish its card, and the whole fleet learns about it automatically.

Security and Trust: The Part Everyone Skips

I'm blunt about this. A2A makes discovery easier, which means the blast radius of a misconfigured agent is larger.

On AWS, we enforce these rules across the fleet:

  • Every agent endpoint is behind Amazon API Gateway with IAM auth (or Cognito if external users are involved).
  • Every A2A task carries a signed message. No exceptions.
  • Agent Cards have a security field that specifies what auth is required. Our discovery Lambda filters out cards that don't meet the security baseline of the requesting agent. This prevents a highly-privileged agent from accidentally talking to a low-security one.

Let me show you the security field in action:

json
{
  "security": {
    "scheme": "oauth2",
    "credentials_requirement": "required",
    "scopes": ["agents.internal.read", "agents.tasks.send"],
    "audience": "https://api.sivaro.dev"
  },
  "trust_level": "internal"
}

Most tutorials ignore this. They show you a cool demo with a mock agent that returns a pizza menu. The truth is, nothing fails faster in production than an agent-to-agent call where one side rejects the other's auth tokens. Define the contract.

The Orchestration Trap

I'll finish with a warning. Your first instinct will be to build a central orchestrator — a "brain" that tells all your agents what to do.

Resist it.

A2A was built to support decentralized workflows. A procurement agent can decide, on its own, that it needs external validation and reach out to a risk-scoring agent. That's not a failure mode; that's the architected design.

The alternative — a monolithic orchestrator that maps every decision — kills the flexibility of multi-agent systems. You rebuild the sequential bottleneck that you were trying to avoid.

In practice, we've seen orchestrators work only for narrow, clearly-specified flows. Once your system needs to handle ad-hoc requests across several specialist agents, the orchestrator ends up being 40% of your codebase and 90% of your bug reports.

FAQ

FAQ

Q: What is the A2A protocol used for?
A: Standardizing communication between autonomous agents. It handles task delegation, state tracking, discovery, and structured output between separate AI systems.

Q: How is A2A different from MCP?
A: MCP connects a model to tools and data sources. A2A connects agent to agent. For example, MCP lets an LLM query your AWS Athena tables. A2A lets a procurement agent delegate a compliance check to a specialist risk agent.

Q: When should I choose A2A vs MCP on AWS?
A: Use MCP with Lambda and API Gateway when your task is one LLM completing a function call. Use A2A when you have two independent systems that each execute complex logic and need recognized task states and capabilities discovery. If both apply — use both.

Q: Is there an official SDK available?
A: Yes. The official a2a-sdk exists for Python (pip install a2a-sdk) and is maintained by the community/past contributors under the Linux Foundation. There are also third-party implementations for Node.js.

Q: What is the biggest mistake in an A2A setup?
A: Ignoring the asynchronous nature of tasks. If you write synchronous agents that expect every task to finish in one request cycle, you will break with slow agents. Use polling or notifications.

Q: How is agent discovery handled in A2A?
A: Through static agent cards at well-known URLs or via a directory service. The directory application is implementation-specific — you can use AWS DynamoDB + Lambda as shown here, or a lightweight registry like an internal ECS service.


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

Part of our MCP (Model Context Protocol) 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