MODULE 12  Β·  Protocols & Standards

A2A (Agent-to-Agent): How AI Agents Talk to Each Other

MCP lets AI talk to tools. Function calling lets AI take actions. But what happens when you need AI talking to AI?

πŸ“… Mar 2026
⏱ 10 min read
🎯 Episode 81 of 98
A2AAgent ProtocolMulti-AgentInteroperability
In this episode

MCP lets AI talk to tools. Function calling lets AI take actions. But what happens when you need AI talking to AI?

Your travel agent needs to coordinate with a booking agent, a weather agent, and a budgeting agent. Each is built by a different team, runs on a different stack, and knows nothing about the others.

How do they discover each other? How do they negotiate capabilities? How do they hand off tasks and stream results?

Google's A2A (Agent-to-Agent) protocol answers all of this. It's HTTP-based, JSON-first, and designed for the world where autonomous agents need to collaborate across organizational boundaries.

Why Not Just Use MCP?

MCP connects a model to tools. The model is the brain; tools are the hands.

A2A connects an agent to another agent. Both are brains. Both can make decisions. The interaction is a conversation between peers, not a command-and-control hierarchy.

MCP A2A

RelationshipModel β†’ Tool (master/servant)Agent ↔ Agent (peer-to-peer)
IntelligenceOnly the model is smartBoth sides are smart
CommunicationJSON-RPC, command/responseTask-based, conversational
DiscoveryServer declares toolsAgent Card with capabilities
Use caseAI reads files, queries DBAI delegates to another AI

They're complementary, not competing. An agent uses MCP for its tools and A2A to collaborate with other agents.

⚑

A2A Architecture: Four Building Blocks

  1. Agent Card β€” The Business Card

Every A2A agent publishes an Agent Card β€” a JSON document describing who it is and what it can do:

json

{

"name": "Weather Agent",

"description": "Provides real-time weather data and forecasts for any location worldwide",

"url": "https://weather-agent.example.com",

"version": "1.0.0",

"capabilities": {

example
code
"streaming": true,
    "pushNotifications": true

},

"skills": [

example
code
{
      "id": "current-weather",
      "name": "Current Weather",
      "description": "Get current weather conditions for a city",
      "inputModes": ["text"],
      "outputModes": ["text", "json"]
    },
    {
      "id": "forecast",
      "name": "Weather Forecast",
      "description": "Get 7-day weather forecast",
      "inputModes": ["text"],
      "outputModes": ["text", "json"]
    }

],

"authentication": {

example
code
"schemes": ["bearer"]

}

}

Agent Cards are hosted at a well-known URL: https://agent.example.com/.well-known/agent.json

Any client can discover an agent by fetching its card. No registration, no marketplace, no middleman.

  1. Tasks β€” Units of Work

Everything in A2A revolves around tasks. A client creates a task, the agent processes it, and the task goes through states:

Task Lifecycle:

submitted β†’ working β†’ completed

example
code
β†˜ failed
                   β†˜ canceled
         β†’ input-required (agent needs more info)

json

{

"id": "task-abc123",

"status": {

example
code
"state": "working",
    "message": "Fetching weather data for Mumbai..."

},

"artifacts": [],

"history": [

example
code
{
      "role": "user",
      "parts": [
        {"type": "text", "text": "What's the weather in Mumbai today?"}
      ]
    }

]

}

  1. Messages β€” The Conversation

Tasks contain messages (called "history"). Each message has parts β€” text, files, or structured data:

json

{

"role": "agent",

"parts": [

example
code
{
      "type": "text",
      "text": "Here's the current weather in Mumbai:"
    },
    {
      "type": "data",
      "data": {
        "temperature": 32,
        "unit": "celsius",
        "conditions": "partly cloudy",
        "humidity": 75
      }
    }

]

}

Parts can carry multiple content types β€” text for humans, structured data for machines, files for media. This multi-modal approach means agents can communicate in whatever format makes sense.

  1. Artifacts β€” The Outputs

When an agent produces a result, it creates an artifact:

json

{

"name": "weather-report",

"parts": [

example
code
{
      "type": "text",
      "text": "Mumbai Weather Report - January 15, 2025\n\nTemperature: 32Β°C\nConditions: Partly Cloudy\nHumidity: 75%\nWind: 12 km/h NW"
    }

]

}

Artifacts are the final deliverables β€” they persist after the task completes and can be referenced by other agents.

πŸ”§

The A2A Protocol: HTTP Endpoints

A2A is a REST-ish protocol over HTTP. Here are the core endpoints:

Endpoint Method Purpose

/.well-known/agent.json GET Agent Card (discovery)

/tasks/sendPOSTCreate or update a task
/tasks/sendSubscribePOSTCreate task + subscribe to SSE stream
/tasks/getPOSTGet task status and history
/tasks/cancelPOSTCancel a running task
/tasks/pushNotification/setPOSTRegister webhook for updates
/tasks/pushNotification/getPOSTGet notification config

Creating a Task

python

import httpx

Discover the agent

snippet
code
card = httpx.get("https://weather-agent.example.com/.well-known/agent.json").json()
print(f"Found agent: {card['name']} with skills: {[s['name'] for s in card['skills']]}")

Create a task

snippet
code
task = httpx.post(f"{card['url']}/tasks/send", json={
"jsonrpc": "2.0",
    "id": "req-1",
    "method": "tasks/send",
    "params": {
        "id": "task-001",
        "message": {
            "role": "user",
            "parts": [{"type": "text", "text": "7-day forecast for Mumbai"}]
        }
    }

}).json()

snippet
code
print(f"Task status: {task['result']['status']['state']}")

"completed" or "working"

Streaming Results

For long-running tasks, use Server-Sent Events:

python

import httpx

Subscribe to task updates via SSE

with httpx.stream("POST", f"{card['url']}/tasks/sendSubscribe", json={

example
code
"jsonrpc": "2.0",
    "id": "req-2",
    "method": "tasks/sendSubscribe",
    "params": {
        "id": "task-002",
        "message": {
            "role": "user",
            "parts": [{"type": "text", "text": "Detailed analysis of Mumbai weather trends"}]
        }
    }

}) as response:

example
code
for line in response.iter_lines():
        if line.startswith("data: "):
            event = json.loads(line[6:])
            status = event.get("result", {}).get("status", {})
            print(f"Status: {status.get('state')} - {status.get('message', '')}")
example
code
# Check for artifacts
            artifact = event.get("result", {}).get("artifact")
            if artifact:
                print(f"Got artifact: {artifact['name']}")

πŸ“Š

Multi-Agent Orchestration

Here's where A2A shines β€” a coordinator agent delegating to specialized agents:

python

class TravelPlannerAgent:

example
code
"""Coordinator that delegates to specialized agents."""
example
code
def __init__(self):
        # Discover agents
        self.weather = discover_agent("https://weather.example.com")
        self.flights = discover_agent("https://flights.example.com")
        self.hotels = discover_agent("https://hotels.example.com")
        self.budget = discover_agent("https://budget.example.com")
example
code
async def plan_trip(self, destination, dates, budget):
        # Launch parallel tasks to different agents
        weather_task = self.weather.send_task(
            f"Weather in {destination} during {dates}"
        )
        flights_task = self.flights.send_task(
            f"Cheapest flights to {destination} for {dates}"
        )
        hotels_task = self.hotels.send_task(
            f"Hotels in {destination} under ${budget/2} per night"
        )
example
code
# Wait for all agents to complete
        weather, flights, hotels = await asyncio.gather(
            weather_task, flights_task, hotels_task
        )
example
code
# Send combined results to budget agent
        budget_task = await self.budget.send_task(
            f"Analyze this trip plan:\n"
            f"Weather: {weather.artifacts}\n"
            f"Flights: {flights.artifacts}\n"
            f"Hotels: {hotels.artifacts}\n"
            f"Total budget: ${budget}"
        )
example
code
return budget_task.artifacts

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”

β”‚ Travel Planner β”‚ (Coordinator)

β”‚ Agent β”‚

β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

example
code
β”‚
       β”œβ”€β”€β†’ Weather Agent ──→ "32Β°C, partly cloudy, pack light"
       β”‚
       β”œβ”€β”€β†’ Flights Agent ──→ "Air India β‚Ή8,500, IndiGo β‚Ή7,200"
       β”‚
       β”œβ”€β”€β†’ Hotels Agent  ──→ "Taj: β‚Ή12K/night, OYO: β‚Ή2K/night"
       β”‚
       └──→ Budget Agent  ──→ "Total: β‚Ή45,000. Under budget by β‚Ή5K"

A2A vs Other Agent Protocols

Feature A2A (Google) MCP (Anthropic) AutoGen CrewAI

ScopeAgent ↔ AgentModel β†’ ToolsFrameworkFramework
TransportHTTP/SSEstdio/HTTPIn-processIn-process
DiscoveryAgent CardsServer lists toolsCode-definedCode-defined
Streamingβœ… SSEβœ… Notificationsβœ…βŒ
Cross-orgβœ… Designed for itβœ…βŒβŒ
AuthenticationBuilt-in schemesNot specifiedN/AN/A
Language-agnosticβœ… HTTP/JSONβœ… JSON-RPC❌ Python❌ Python

The key distinction: AutoGen and CrewAI are frameworks (code libraries). A2A and MCP are protocols (standards). You can build with any framework and communicate via A2A.

πŸ’‘

Input-Required: Multi-Turn Agent Conversations

Agents can request more information mid-task, creating multi-turn conversations:

json

// Agent responds with input-required state

{

"id": "task-003",

"status": {

example
code
"state": "input-required",
    "message": "I found 15 hotels. What's your preference?"

},

"history": [

example
code
{
      "role": "user",
      "parts": [{"type": "text", "text": "Find hotels in Mumbai"}]
    },
    {
      "role": "agent",
      "parts": [{"type": "text", "text": "I found 15 hotels. Do you prefer: (1) Budget (under β‚Ή3K/night), (2) Mid-range (β‚Ή3K-8K), or (3) Luxury (β‚Ή8K+)?"}]
    }

]

}

python

Client sends follow-up

snippet
code
response = httpx.post(f"{url}/tasks/send", json={
"jsonrpc": "2.0",
    "id": "req-3",
    "method": "tasks/send",
    "params": {
        "id": "task-003",  # Same task ID β€” continues the conversation
        "message": {
            "role": "user",
            "parts": [{"type": "text", "text": "Budget please, under 3K"}]
        }
    }

}).json()

πŸ”¬

Building an A2A Agent

Here's a minimal A2A agent implementation:

python

from flask import Flask, request, jsonify, Response

import json

import uuid

snippet
code
app = Flask(__name__)
tasks = {}

Agent Card

AGENT_CARD = {

example
code
"name": "Sentiment Analyzer",
    "description": "Analyzes text sentiment using AI",
    "url": "http://localhost:5000",
    "version": "1.0.0",
    "capabilities": {"streaming": False, "pushNotifications": False},
    "skills": [{
        "id": "analyze",
        "name": "Sentiment Analysis",
        "description": "Determine if text is positive, negative, or neutral",
        "inputModes": ["text"],
        "outputModes": ["text", "json"]
    }]

}

@app.route("/.well-known/agent.json")

snippet
code
def agent_card():
return jsonify(AGENT_CARD)

@app.route("/tasks/send", methods=["POST"])

snippet
code
def send_task():
req = request.json
    params = req["params"]
    task_id = params.get("id", str(uuid.uuid4()))
    message = params["message"]
    text = message["parts"][0]["text"]
# Simple sentiment analysis (replace with actual model)
    sentiment = analyze_sentiment(text)
task = {
        "id": task_id,
        "status": {"state": "completed"},
        "artifacts": [{
            "name": "sentiment-result",
            "parts": [
                {"type": "text", "text": f"Sentiment: {sentiment['label']}"},
                {"type": "data", "data": sentiment}
            ]
        }],
        "history": [message, {
            "role": "agent",
            "parts": [{"type": "text", "text": f"Analysis complete: {sentiment['label']}"}]
        }]
    }
    tasks[task_id] = task
return jsonify({"jsonrpc": "2.0", "id": req["id"], "result": task})

def analyze_sentiment(text):

example
code
# Your ML model here
    return {"label": "positive", "score": 0.92, "text": text}

if __name__ == "__main__":

example
code
app.run(port=5000)

πŸ›‘οΈ

Practical Takeaways

A2A is for agent-to-agent communication β€” MCP for tools, A2A for peer agents

Agent Cards enable discovery β€” any agent can find and understand another via .well-known/agent.json

Tasks are the unit of work β€” submitted β†’ working β†’ completed/failed, with history and artifacts

SSE streaming handles long-running tasks β€” real-time progress updates

Input-required enables multi-turn β€” agents can ask for clarification mid-task

It's a protocol, not a framework β€” build with any language/framework, communicate via HTTP

πŸ“¦

What's Next?

Episode 82: Webhooks & Event-Driven AI β€” A2A uses SSE for streaming. But real production systems need more: webhooks, message queues, async pipelines. We'll explore how to build event-driven AI architectures that process triggers, handle backpressure, and scale to millions of events.

← Previous

Ep 80: Structured Output / JSON Mode

Next β†’

Ep 82: Webhooks & Event-Driven AI

Next: Episode 82 β€” Webhooks & Event-Driven AI

Request/response works for chatbots. Production AI systems aren't chatbots. They need event-driven architectures.

This is part of a 98-episode series covering AI engineering from tokens to production deployment.

← Previous Ep 80: Structured Output / JSON Mode: Making AI Responses Mach…