The Agent-to-Agent (A2A) protocol is a new standard by Google that enables AI agents—regardless of their underlying framework or developer—to communicate and collaborate seamlessly. This protocol works using standard messages, agents cards (which explain what an agent is capable of doing) and task based execution. Agents can interact with each other via HTTP. A2A abstracts away communication complexities, making it easier for developers to create scalable multi-agent system.
This tutorial will help you learn the A2A Protocol’s core structure by implementing a demo agent.
Set up dependencies
First, we will set up the environment. Next, install uv. Mac OS X or Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
PowerShell is available for Windows.
Powershell -ExecutionPolicy Bypass -c "irm https://astral.sh/uv/install.ps1 | iex"
Then we will create a project directory, initializing it with the uv
Uv init A2a-demo
CD a2a Demo
Now we can create and activate the virtual environment. Mac OS or Linux users:
Uv venv
Source.venv/bin/activate
Windows:
The venv
.venvScriptsactivate
Install the dependencies required
uv add a2a-sdk python-a2a uvicorn
The Core Building Blocks
Agent Executor (agent_executor.py)
This step involves implementing the logic core of the agent. We do this by creating an Executor Agent, who is in charge of handling requests, and returns responses to the A2A format. The RandomNumberAgentExecutor How to easily wrap up a simple RandomNumberAgent The random number is generated between 1 to 100. Execute method, when receiving a new request, calls agent logic. The result is then pushed into an event queue and sent as standard A2A messages. The backend logic is created by this setup. A2A clients are able to interact with it. Look at the You can also read more about Full-Time Employees. Codes on GitHub
Random Import
from a2a.server.agent_execution import AgentExecutor
from a2a.server.agent_execution.context import RequestContext
from a2a.server.events.event_queue import EventQueue
from a2a.utils import new_agent_text_message
BaseModel - pydantic imported
class RandomNumberAgent(BaseModel):
"""Generates a random number between 1 and 100"""
async def invoke(self) -> str:
Random.randint = 1, 100
Return f"Random number generated: {number}"
class RandomNumberAgentExecutor(AgentExecutor):
def __init__(self):
RandomNumberAgent is the agent that represents self.()
async def execute(self, context: RequestContext, event_queue: EventQueue):
result = await self.agent.invoke()
await event_queue.enqueue_event(new_agent_text_message(result))
async def cancel(self, context: RequestContext, event_queue: EventQueue):
raise Exception("Cancel not supported")
Setup the A2A server and agent card (main.py).
In this section, we define the metadata that describes what our agent can do — this is called the Agent Card. Imagine it as a business card for the agent, with information such as its name, description and available skills. It also contains input/output type, version, etc.
We register an agent’s abilities, which are the tasks that it can perform. It includes, in our case, the ability to create a random, with prompts and tags.
After the metadata is complete, configure the A2A servers using A2AStarletteApplication. The agent card is provided by us and we connect it to our agent logic custom using A DefaultRequestHandlerThe. RandomNumberAgentExecutor The implementation we did earlier. Finaly, we use uvicorn on the server to start the agent listening for A2A message arrivals. 9999.
This setup enables our agent to receive standardized A2A messages, process them, and respond in a structured way — following the A2A protocol. See the The Full Story Codes on GitHub
import uvicorn
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
Import InMemoryTaskStore from a2a.server.tasks
Import AgentCapabilities from a2a.types.
from agent_executor import RandomNumberAgentExecutor
Def main():
# Define skill metadata
skill = AgentSkill(
id="random_number",
name="Random Number Generator",
description="Generates a random number between 1 and 100",
tags=["random", "number", "utility"],
examples=["Give me a random number", "Roll a number", "Random"],
)
# Define agent metadata
agent_card = AgentCard(
name="Random Number Agent",
description="An agent that returns a random number between 1 and 100",
url="http://localhost:9999/",
defaultInputModes=["text"],
defaultOutputModes=["text"],
skills=[skill],
version="1.0.0",
capabilities=AgentCapabilities(),
)
Use our agent executor to configure the Request Handler
request_handler = DefaultRequestHandler(
agent_executor=RandomNumberAgentExecutor(),
task_store=InMemoryTaskStore(),
)
A2A server: Create it.
server = A2AStarletteApplication(
http_handler=request_handler,
agent_card=agent_card,
)
# Run the Server
uvicorn.run(server.build(), host="0.0.0.0", port=9999)
If __name__ is equal to "__main__":
The main reason for this is that()
A2AClient: Interacting with Agent using client.py
We now create the A2A client. This script is used to perform three tasks.
- The Agent Card can be retrievedA2ACardResolver resolves the metadata for an agent. This retrieves the file agent.json from the widely-known endpoint. Included in this is the essential information about the agent such as its name, description and abilities.
- Initialize A2A clientWe create an A2AClient that handles communication protocols using the AgentCard. This client will send structured messages to an agent, and receive responses.
Send a Message to Receive a ReplyWe create a text message. “Give me a random number” Using A2A message structure: Message, TextPart, and Part. SendMessageRequest wraps the message with a unique Request ID. After the message has been sent, it is processed by the agent, who then prints a randomly generated number. Click here to see the You can also read more about Full-Time Employees. Codes on GitHub
import uuid
Import httpx
Import A2ACardResolver and A2AClient from A2A.client
From a2a.types Import (
AgentCard,
Message,
MessageSendParams,
Part,
Role,
SendMessageRequest,
TextPart,
)
PUBLIC_AGENT_CARD_PATH = "/.well-known/agent.json"
BASE_URL = "http://localhost:9999"
async def main() -> None:
Async client with HTTPx() As httpx_client
Get the Agent Card
resolver = A2ACardResolver(httpx_client=httpx_client, base_url=BASE_URL)
try:
print(f"Fetching public agent card from: {BASE_URL}{PUBLIC_AGENT_CARD_PATH}")
agent_card: AgentCard = await resolver.get_agent_card()
print("Agent card fetched successfully:")
print(agent_card.model_dump_json(indent=2))
Except Exception As e.
print(f"Error fetching public agent card: {e}")
You can return to your original language by clicking here.
# Initialize A2A clients with agent cards
client = A2AClient(httpx_client=httpx_client, agent_card=agent_card)
# Build message
message_payload = Message(
role=Role.user,
messageId=str(uuid.uuid4()),
parts=[Part(root=TextPart(text="Give me a random number"))],
)
Request = SendMessageRequest
id=str(uuid.uuid4()),
params=MessageSendParams(message=message_payload),
)
# Send a message
print("Sending message...")
response = await client.send_message(request)
# Print the response
print("Response:")
print(response.model_dump_json(indent=2))
If __name__ is equal to "__main__":
import asyncio
asyncio.run(main())
Running Agent and executing queries
We’ll first run the A2A server to test out our setup. The main.py script initializes and exposes the agent’s agent card. It also starts listening on port 9999 for requests. You can check out the You can also read more about Full-Time Employees. Codes on GitHub
We’ll start the client script once the agent is running. Client will retrieve the metadata of the agent, then send an A2A-structured query and get a reply. The query in our example is just a message such as “Give me a random number”, the agent returns a number between 0 and 100.

Click here to find out more The Full Story Codes on GitHub. The researchers are the sole owners of all credit. Also, feel free to follow us on Twitter Don’t forget about our 100k+ ML SubReddit Subscribe Now our Newsletter.



