Understanding A2A (Agent-to-Agent Protocol)
In real-world applications, We all know, A single AI agent usually cannot handle everything. Different tasks require different agents, so…
Understanding A2A (Agent-to-Agent Protocol)

In real-world applications, We all know, A single AI agent usually cannot handle everything. Different tasks require different agents, so systems are increasingly building multiple specialised agents. If we take a Travel example(❛Hello World❜ for this GenAI era 😁), One agent may search flights, another may recommend hotels, and another may check weather conditions. Each agent focuses on doing one specific job, which makes the system more efficient and easier to manage. Because of this, using multiple agents has become a common approach when building complex AI systems.
As companies build more complex AI systems, using multiple agents allows them to scale and add new capabilities without changing the whole system. However, when many agents exist, they must communicate with each other to complete a task together. This is why protocols like A2A are important.
A2A protocol provides a standard way for agents to exchange information and collaborate smoothly.

Firstly Let’s understand Problems Before A2A Protocol
Before A2A existed, every one has built their own custom way for agents to talk. This has created many problems.
1. No Standard Communication
Like, One agent might send data in JSON. Another agent expects a different format. They cannot understand each other.
2. Different Frameworks
Agents could be built using:
- LangGraph
- CrewAI
- Google SDK
- Other frameworks
Without a standard protocol, these agents which are developed in different frameworks cannot easily talk to each other.
3. No Standard Discovery
Agents never knew:
- What another agent can do
- What inputs it expects
- What outputs it returns
This makes collaboration difficult.
With A2A protocol:
Any AI agent irrespective of any framework can talk to any other AI agent.

Let’s see A Simple Example Where A2A Helps
Imagine an Online Travel Planner Agent. User asks: “Plan a 3-day trip to Goa.”
The planner agent cannot do everything. It needs help from other agents like:
- Flight Agent
- Hotel Agent
- Weather Agent
With A2A:
- Travel Planner agent asks Flight Agent for flights
- Travel Planner agent asks Hotel Agent for hotels
- Travel Planner agent asks Weather Agent for weather
- Travel Planner agent combines results
Without A2A:
Every Agent connection must be manually built.
With A2A:
All agents follow same protocol, so they can easily communicate.
Now Let’s understand how A2A & MCP(Model Context Protocol) complement each other?
Both A2A and MCP solve different problems.
Because MCP helps AI models talk to tools in a standardised way. MCP is mainly about tool access. A2A, on the other hand, focuses on communication between agents. It allows one AI agent to request help or collaborate with another AI agent.
MCP is for Agents to talk to Tools. A2A is for Agents to talk to other Agents.
As I already mentioned above, A2A is framework-agnostic. You can also build agents using any Agentic Framework( Google ADK / LangGraph / CrewAI / Custom frameworks) available in the market.
A2A only defines how agents communicate.
Client-Server Architecture
A2A follows client-server architecture.
Client
The agent that asks another agent for help or requests a service.
Server
The agent that performs a task when it is asked with some request.
Example:
Travel Agent (Client)
|
v
Hotel Booking Agent, Flight Booking Agent, Weather Agent (Server)
The client agent sends a request to a server agent, and the server agent responds by executing the requested task. In a travel booking system, the Travel Agent receives a request from the user like “Book a hotel in Goa.” The Travel Agent then sends a request to the Hotel Booking Agent, which acts as the server agent.
The Hotel Agent may internally use a booking API to complete the reservation. That API call is considered a tool.
So in this system:
- A2A communication happens between agents (Travel Agent ↔ Hotel Agent).
- MCP is used by the agent to access tools (like a booking API).
This shows how A2A and MCP can work together in the same system.
MCP also uses client-server architecture.
Now Let’s understand the components in A2A.
Agent Cards
An Agent Card is like an identity card of an agent.
It describes:
- Agent Name
- Description
- What it can do
- What skills it has
- How to contact it
public_agent_card = AgentCard(
name='Hello World Agent',
description='Just a hello world agent',
url='http://localhost:9999/',
version='1.0.0',
default_input_modes=['text'],
default_output_modes=['text'],
capabilities=AgentCapabilities(streaming=True),
skills=[skill],
supports_authenticated_extended_card=True,
)
Client agents read the server agent’s Agent Card to understand what the agent can do.
Agent Skills
Agent skills describe what the agent can do. Each skill is like a function the agent exposes.
search_flights_skill = AgentSkill(
id='search_flights',
name='Search Flights',
description='Finds available flights between two cities',
tags=['flight', 'travel', 'booking'],
examples=[
'find flights from Hyderabad to Goa',
'show flights from Delhi to Bangalore'
],
)
search_hotels_skill = AgentSkill(
id='search_hotels',
name='Search Hotels',
description='Finds hotels available in a given city',
tags=['hotel', 'stay', 'travel'],
examples=[
'find hotels in Goa',
'show hotels in Mumbai'
],
)
Agent Executor
An Agent Executor is the component of a server agent that handles incoming requests and performs the task execution to generate a response. It inherits the AgentExecutor class provided by the A2A framework. When we inherit from AgentExecutor, we must implement two methods: execute and cancel, because these methods are defined in the base class AgentExecutor.
The execute method runs when another agent sends a request to this agent. Inside this method, we call the actual agent logic that performs the task. The agent itself contains the business logic, such as calling APIs, running tools, or interacting with an LLM.
If a server hosts multiple agents, each agent can have its own executor, or a single executor can manage multiple agents within the server. The choice depends on the design and requirements of the system.
Each executor implements the execute and cancel methods and triggers the corresponding agent logic. In the example below, the FlightAgent contains the logic for searching flights, and the FlightAgentExecutor receives the request and executes the FlightAgent agent.
from openai import OpenAI
client = OpenAI()
class FlightAgent:
async def search_flights(self, source: str, destination: str):
prompt = f"""
A user wants to travel from {source} to {destination}.
Suggest a few possible flights with airline names and approximate prices.
Keep the answer short and clear.
"""
response = await client.responses.create(
model="gpt-4.1-mini",
input=prompt
)
return response.output_text
class FlightAgentExecutor(AgentExecutor):
def __init__(self):
self.agent = FlightAgent()
async def execute(
self,
context: RequestContext,
event_queue: EventQueue,
) -> None:
source = context.request.payload.get("source")
destination = context.request.payload.get("destination")
result = await self.agent.search_flights(source, destination)
await event_queue.enqueue_event(
new_agent_text_message(result)
)
async def cancel(
self,
context: RequestContext,
event_queue: EventQueue,
) -> None:
raise Exception("cancel not supported")
Now Let’s look at the Enterprise Features in A2A like Security and Monitoring.
A2A supports enterprise requirements.
TLS (Transport Layer Security)
TLS is a technology that protects data when it travels between two systems.
When two agents communicate using TLS, the messages are encrypted, which means they are converted into a secure format while being sent over the network.
Agent A <---- encrypted communication ----> Agent B
This makes A2A safe to use in enterprise environments where agents may be exchanging sensitive information such as user data, business data, or internal system requests.
Authentication
Authentication in A2A is used to verify who is making the request. In other words, before an agent accepts a request from another agent, it must confirm that the requesting agent is trusted and allowed to communicate.
A2A does not create its own authentication system. Instead, it uses standard web authentication methods that are already widely used on the internet, such as API Keys, OAuth2, or tokens.
When a client agent wants to talk to a server agent, it must send its credentials through “HTTP headers”. These credentials prove the identity of the client agent. These credentials are not placed inside the A2A message payload. Instead, they are sent at the HTTP level, which is the normal way web systems handle authentication.
The server agent also tells other agents what authentication method it supports. This information is published in the Agent Card under the security section. So before calling an agent, the client can read the Agent Card and understand how it needs to authenticate.
When the server agent receives a request, it checks the credentials. If the credentials are missing or invalid, the server will reject the request.
Authorization
Authorization checks what an authenticated agent is allowed to do after its identity has been verified. Authentication answers “Who are you?”, while Authorization answers “What are you allowed to do?”.
In A2A, once a client agent successfully authenticates, the server agent decides whether the request is permitted or not. The rules for authorization depend on how the agent is implemented and the policies of the organisation using it.
Authorization can be applied at different levels. Sometimes it is based on which agent or application is making the request, and sometimes it may also depend on the end user on whose behalf the agent is acting.
Agents can also control access at the skill level. Since the Agent Card advertises the skills that an agent provides, the server can allow some agents to use certain skills while restricting others. For example, a client agent may be allowed to call the search_flights skill but not the book_flight skill.
Agents that connect to other systems, such as databases or booking APIs, must also check permissions before accessing sensitive data or performing important actions. In this situation, the agent acts like a gatekeeper, making sure that only authorised requests reach the underlying systems.
Observability and Monitoring
It is important to understand and track what agents are doing and how the system is performing. A2A makes this easier because it uses standard web technologies like HTTP, which allows companies to use common monitoring and logging tools.
With A2A, systems can track important details such as agent requests, responses, failures, and how long tasks take to execute.
A2A systems can also support distributed tracing, which means a request can be tracked as it moves across multiple agents. For example, a request might start from a Travel Planner Agent, then go to a Flight Agent, and later to a Weather Agent. Using tracing tools like OpenTelemetry, the system can follow the entire journey of that request. Special identifiers called trace IDs are passed through HTTP headers so the request can be tracked across all agents involved.
A2A systems can also expose metrics such as how many requests an agent receives, how many errors occur, and how long tasks take to complete. These metrics allow teams to monitor system health and plan for scaling when traffic increases.
Now Let’s understand another concept is A2A: Streaming and Async Operations.
We all know some tasks don't finish immediately and some tasks take time because they involve multiple steps, large outputs, or complex processing. A2A is designed to handle these situations using streaming and asynchronous communication.
Streaming:
Streaming is used when the client agent stays connected to the server agent and wants live updates while the task is running. Instead of waiting for the full result, the server agent can send updates/response step by step.
Example: User asks a Travel Agent to plan a full trip.
The server agent may send updates like:
Searching flights...
Checking hotel availability...
Comparing prices...
Trip plan ready.
This is done using Server-Sent Events (SSE).
SSE keeps the connection open so the server can continuously send updates.
Streaming is useful when:
- the task takes time
- partial results are useful
- the client wants real-time progress updates
Asynchronous Updates:
Sometimes tasks may take minutes, hours, or even days. In these cases, the client agent may not stay connected to the server. For such situations, A2A supports asynchronous notifications.
The client provides a webhook URL, and when the task finishes or reaches an important step, the server sends a notification to that URL.
Example: A research agent is generating a large report.
Instead of keeping the connection open, the system works like this:
- Client sends the request
- Server starts the task
- When the task finishes, the server notifies the client
Now that we have understood the key concepts of A2A, let’s look at a code example to see how we can implement A2A using the OpenAI Agent SDK.
Use Case Summary
A user from the client side can send travel-related requests such as booking a flight, hotel or cab for a specific destination. The server hosts multiple specialised agents like a Flight Agent, Hotel Agent, and Cab Agent. Based on the user’s request, the system routes the query to the appropriate agent in the server side, which performs the required booking and returns the response back to the user.
This is our project structure:
a2a_project
│
├── main.py
│
└── agents
| ├── flight_agent.py
| ├── hotel_agent.py
| ├── cab_agent.py
| └── executors
| └── travel_executor.py
│
├── client
├── client.py
Below is the client code used to connect to the server. I have added comments to explain the parts of the code which is A2A specific.
client.py
import asyncio
import httpx
from a2a.client import (
A2ACardResolver,
Client,
ClientConfig,
ClientFactory,
create_text_message_object,
)
from a2a.types import TransportProtocol
from a2a.utils.message import get_message_text
SERVER_URL = "http://localhost:9999"
def print_welcome_message():
print("Travel Planner Client")
print("Type a request like:")
print("book flight from Hyderabad to Goa")
print("book hotel in Goa")
print("book cab in Goa")
print("Type 'exit' to quit")
def get_user_query():
return input("\n> ")
async def interact_with_server(client: Client):
while True:
# getting user input
user_input = get_user_query()
if user_input.lower() == "exit":
print("bye!")
break
try:
# creating a message object with the user input to send to the server. A2A server expects the message to be in a specific message format.
request = create_text_message_object(content=user_input)
async for response in client.send_message(request):
task, _ = response
print(get_message_text(task.artifacts[-1]))
except Exception as e:
print("Error:", e)
async def main():
print_welcome_message()
#asynchronous client connection to interact with server
async with httpx.AsyncClient() as httpx_client:
# creating a resolver to fetch the agent card from the server
resolver = A2ACardResolver(
httpx_client=httpx_client,
base_url=SERVER_URL
)
try:
agent_card = await resolver.get_agent_card()
print("agent_card : ",agent_card)
# configuring the client connection with the fetched AgentCard and supported transports protocols (HTTP JSON and JSON-RPC in this case)
config = ClientConfig(
httpx_client=httpx_client,
supported_transports=[
TransportProtocol.jsonrpc,
TransportProtocol.http_json
],
# we are fetching the response in a streaming manner
streaming=agent_card.capabilities.streaming,
)
# creating the client with the above configuration and fetched agent card
client = ClientFactory(config).create(agent_card)
except Exception as e:
print("Error initializing client:", e)
return
await interact_with_server(client)
if __name__ == "__main__":
asyncio.run(main())
Below are the server-side files: travel_executor.py, main.py, and cab_agents.py, hotel_agents.py, flight_agents.py.
As mentioned earlier, we need to inherit the AgentExecutor class because of this, we override the execute and cancel methods. The execute method is responsible for running the agent logic on the server when a user sends a request, while the cancel method is used to handle task cancellation if supported.
The AgentExecutor acts as the main execution layer on the server side. It receives the user request, processes the input, triggers the appropriate agent logic, and sends the response back to the client through events. This is why implementing an executor is necessary when building an agent server using the A2A protocol.
travel_executor.py
from typing_extensions import override
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.events import EventQueue
from a2a.types import (
TaskArtifactUpdateEvent,
TaskState,
TaskStatus,
TaskStatusUpdateEvent,
)
from dotenv import load_dotenv
load_dotenv()
from a2a.utils import new_text_artifact
from agents import Runner
from agents_server.flight_agent import flight_agent
from agents_server.hotel_agent import hotel_agent
from agents_server.cab_agent import cab_agent
class TravelExecutor(AgentExecutor):
@override
async def execute(self, context: RequestContext, event_queue: EventQueue):
if not context.message:
raise Exception("No message provided")
query = context.get_user_input().lower()
if "flight" in query or "plain" in query:
# Invoking the Agent
result = await Runner.run(flight_agent, input=query)
elif "hotel" in query or "room" in query:
# Invoking the Agent
result = await Runner.run(hotel_agent, input=query)
elif "cab" in query or "car" in query:
# Invoking the Agent
result = await Runner.run(cab_agent, input=query)
else:
response_text = "I can help with flight, hotel, or cab bookings."
artifact = TaskArtifactUpdateEvent(
context_id=context.context_id,
task_id=context.task_id,
artifact=new_text_artifact(
name="response",
text=response_text,
),
)
await event_queue.enqueue_event(artifact)
status = TaskStatusUpdateEvent(
context_id=context.context_id,
task_id=context.task_id,
status=TaskStatus(state=TaskState.completed),
final=True,
)
await event_queue.enqueue_event(status)
return
# This block creates a response event that will be sent back to the client.
artifact = TaskArtifactUpdateEvent(
# Identifies the conversation context. Helps the system know which request this response belongs to.
context_id=context.context_id,
# Identifies the specific task being executed. Important when multiple tasks are running.
task_id=context.task_id,
artifact=new_text_artifact(
name="response",
text=result.final_output,
),
)
# This sends the actual content or output produced by the agent.
await event_queue.enqueue_event(artifact)
# After sending the response, the executor updates the task status.
status = TaskStatusUpdateEvent(
context_id=context.context_id,
task_id=context.task_id,
status=TaskStatus(state=TaskState.completed),
final=True,
)
# This informs the client that the task execution has completed.
await event_queue.enqueue_event(status)
@override
async def cancel(self, context: RequestContext, event_queue: EventQueue):
raise Exception("cancel not supported")
The below code sets up the A2A agent server. It first defines the available agent skills — booking flights, hotels and cabs — using AgentSkill. These skills are then registered in an AgentCard, which acts as the metadata describing the agent server, including its capabilities, supported input/output modes, and the skills it provides.
main.py
import uvicorn
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore
from a2a.types import AgentCapabilities, AgentCard, AgentSkill
from agents_server.executors.travel_executor import TravelExecutor
from dotenv import load_dotenv
load_dotenv()
flight_skill = AgentSkill(
id="book_flight",
name="Book Flight",
description="Books flights between cities",
tags=["flight", "travel"],
)
hotel_skill = AgentSkill(
id="book_hotel",
name="Book Hotel",
description="Books hotels in a city",
tags=["hotel", "travel"],
)
cab_skill = AgentSkill(
id="book_cab",
name="Book Cab",
description="Books cabs for travel",
tags=["cab", "transport"],
)
agent_card = AgentCard(
name="Travel Services Agent Server",
description="Handles flight, hotel and cab bookings",
url="http://localhost:9999/",
version="1.0.0",
default_input_modes=["text"],
default_output_modes=["text"],
capabilities=AgentCapabilities(streaming=True),
skills=[flight_skill, hotel_skill, cab_skill],
)
# request handler
request_handler = DefaultRequestHandler(
agent_executor=TravelExecutor(),
task_store=InMemoryTaskStore(),
)
# server
server = A2AStarletteApplication(
agent_card=agent_card,
http_handler=request_handler
)
if __name__ == "__main__":
uvicorn.run(server.build(), host="0.0.0.0", port=9999)
Below are the actual Agents we have in the server.
agents_server/cab_agent.py
from agents import Agent, function_tool
from dotenv import load_dotenv
load_dotenv()
@function_tool
async def book_cab(city: str):
return f"Cab booked in {city} for airport pickup."
cab_agent = Agent(
name="Cab Booking Agent",
instructions="""
You arrange cab transportation.
Use the book_cab tool to complete bookings.
""",
tools=[book_cab],
)
agents_server/flight_agent.py
from agents import Agent, function_tool
from dotenv import load_dotenv
load_dotenv()
@function_tool
async def book_flight(source: str, destination: str):
return f"Flight booked from {source} to {destination} with airline AI202."
flight_agent = Agent(
name="Flight Booking Agent",
instructions="""
You help users book flights.
Always use the book_flight tool to complete the booking.
""",
tools=[book_flight],
)
agents_server/hotel_agent.py
from agents import Agent, function_tool
from dotenv import load_dotenv
load_dotenv()
@function_tool
async def book_hotel(city: str):
return f"Hotel booked in {city} at SeaView Resort."
hotel_agent = Agent(
name="Hotel Booking Agent",
instructions="""
You help users book hotels.
Use the book_hotel tool when a hotel booking is requested.
""",
tools=[book_hotel],
)
That’s it…
You can run the server code (main.py) in one terminal and the client code (client.py) in another terminal, which allows the client to communicate with the server.
We have come to the end of our Agent-to-Agent (A2A) protocol understanding. I hope the core concepts of A2A, enterprise features like TLS authentication, authorization, streaming, and asynchronous support are very clear and we have also looked at a multi-agent travel booking code example to understand how it works in practice.
I will also recorded a video explaining it step by step shortly in my YouTube channel. You can find the video link in the comment section.
Thank You !!
메타데이터
- post_id
- 249f03777ff8
- slug
- understanding-a2a-agent-to-agent-protocol-249f03777ff8
- url
- https://medium.com/@mailpraveenreddy.c/understanding-a2a-agent-to-agent-protocol-249f03777ff8
- canonical_url
- https://medium.com/@mailpraveenreddy.c/understanding-a2a-agent-to-agent-protocol-249f03777ff8
- author_url
- https://medium.com/@mailpraveenreddy.c
- status
- ok
- fetched_at
- 2026-06-09 15:37:30