Getting Started with Google A2A: A Hands-on Tutorial for the Agent2Agent Protocol
Bridging LangGraph, CrewAI, and ADK Agents
What is this about?
On the one hand we’re living in an exciting time for AI agents, but on the other hand it isa bit of a framework jungle out there! LangGraph, CrewAI, Google ADK… how do we get agents built with these different tools to actually talk to each other and collaborate on complex tasks?
Enter the Agent2Agent (A2A) protocol, an open standard initiated by Google aiming to build those bridges. Instead of just talking about interoperability, I wanted to see it in action. So, I dove into the official A2A repository and set up their multi-agent demo UI.
In this tutorial I walk through the entire setup process. It involves spinning up multiple Python-based agents (using LangGraph, CrewAI, and ADK) and connecting them via a central web UI.
This post will recap those setup steps (for easy copy-pasting!) and then peek under the hood to understand the key A2A concepts making this cross-framework communication possible.
Why should we care?
If we want to build anything beyond a single-agent toy project, we will likely hit this interoperability wall sooner or later. Imagine wanting your data analysis agent (maybe in LangGraph) to hand off results to a report-writing agent (perhaps CrewAI). How do they coordinate?
A2A offers a potential standard way:
- Compose Specialists: Build complex workflows by combining agents that are best-in-class for specific tasks, regardless of their internal framework.
- Avoid Lock-in: Promote flexibility by defining a common communication layer.
- Standard Interaction: Provides predictable ways for agents to discover capabilities, manage tasks, and exchange different kinds of data (not just text!).
This demo makes these ideas tangible.
⚠️ Please note: This tutorial refers to a specific version of the official A2A GH repo as it was on 16 April 2025. The A2A repo is constantly changing and therefore the instructions below might not work in the exact same way anymore with the current version of the repo!
Let’s Get Our Hands Dirty: Setting up the A2A Demo
Alright, time to roll up our sleeves and get this multi-agent demo running. Make sure you have Git, Python 3.12, and uv installed. The goal is to have the web UI talking to three different Python agents (LangGraph, CrewAI, ADK) via the A2A protocol.
Step 1: Clone the Repo & Set Up the Virtual Environment
First things first, grab the code and create an isolated Python environment using uv. Working in a virtual environment is crucial to keep dependencies tidy and avoid conflicts with other projects.
# Clone the repository
git clone https://github.com/google/A2A.git
cd A2A
# Create the virtual environment (named .venv) in the project root
uv venv
# Activate the environment (commands below use Python/pip from here)
# Linux/macOS:
source .venv/bin/activate
# Windows CMD: .\.venv\Scripts\activate.bat
# Windows PowerShell: .\.venv\Scripts\Activate.ps1
We fetched the project code and then used `uv venv` to create a dedicated Python sandbox inside the .venv folder. The source command makes our terminal session use this sandbox for all subsequent Python-related commands. Our command prompt should now have an (A2A) prefix (or similar):
Step 2: Install Dependencies
Now, we need to install the necessary Python packages. The A2A samples have a slightly tricky structure: common dependencies are in samples/python, but the CrewAI agent has its own specific requirements defined in its sub-directory. We’ll use uv sync twice to handle this, installing everything into our single activated .venv.
# Navigate to the main Python samples directory
cd samples/python
# Install common dependencies + LangGraph + ADK requirements
# (Reads samples/python/pyproject.toml)
uv sync
# Now, go into the CrewAI agent's directory
cd agents/crewai
# Install CrewAI specific dependencies into the SAME activated environment
# (Reads samples/python/agents/crewai/pyproject.toml)
uv sync
# IMPORTANT: Go back up to the main Python samples directory.
# We'll run the agent start commands from here.
cd ../..
# (You should now be back in the A2A/samples/python directory)
We ran uv sync first in samples/python to install packages like fastapi, mesop, langgraph, google-generativeai, etc. Then, we specifically went into agents/crewai and ran uv sync again. This read its pyproject.toml and added crewai and crewai-tools to the same .venv we already activated. Now, our single environment has all the packages needed for all Python components. We finished by returning to samples/python, which is the expected location for launching the agents via uv run.
Step 3: Configure the API Key (for the Agents)
The sample agents use Google’s Gemini models, so they need an API key. We’ll create a .env file in the root of the A2A project directory:
# Create the .env file in the ROOT A2A directory
# (We are currently in A2A/samples/python, so use ../../)
echo "GOOGLE_API_KEY=YOUR_API_KEY_HERE" > ../../.env
We created a simple text file named .env at the top level of the A2A project. The Python agent scripts are configured to load this file automatically when they start, giving them access to the GOOGLE_API_KEY. We place it here because the uv run agents/… command context seems to pick it up correctly from the root relative to the pyproject.toml we initially synced from.
Make sure to replace YOUR_API_KEY_HERE with you actual API key (you can get it for free at https://aistudio.google.com/app/apikey)
Step 4: Run the A2A Agents (Each in its Own Terminal!)
This is where the “multi-agent” part comes alive. Each agent runs as an independent web server. We need to open separate terminal windows for each one.
Critical: In each new terminal we open for the agents below, we MUST:
- Navigate to the A2A/samples/python directory.
- Activate the same virtual environment: `source ../../.venv/bin/activate`
# --- 🖥️ Terminal 1: LangGraph Agent (Port 10000) ---
# (Assuming you are in A2A/samples/python and venv is active)
echo "Starting LangGraph Agent on port 10000..."
uv run agents/langgraph
# (Leave this terminal running)
# --- 🖥️ Terminal 2: CrewAI Agent (Port 10001) ---
# (IN A NEW TERMINAL: cd /path/to/A2A/samples/python && source ../../.venv/bin/activate)
echo "Starting CrewAI Agent on port 10001..."
uv run agents/crewai
# (Leave this terminal running)
# --- 🖥️ Terminal 3: ADK Agent (Port 10002) ---
# (IN A NEW TERMINAL: cd /path/to/A2A/samples/python && source ../../.venv/bin/activate)
echo "Starting ADK Agent on port 10002..."
uv run agents/google_adk
# (Leave this terminal running)
The warning we might see simply points out that the activated path (/path/to/A2A/.venv) is different from the default relative path (A2A/samples/python/.venv) it was checking. For this specific setup workflow (activating the venv at the root, then running uv sync in subdirectories), this warning can usually be safely ignored. The dependencies are being installed into the correct shared virtual environment (A2A/.venv)
Step 5: Run the Demo UI Application
Finally, let’s start the web interface that will act as our client and orchestrator.
# --- 🖥️ Terminal 4: Demo UI (Port 12000) ---
# (Open another NEW terminal)
# Navigate to the ROOT A2A directory
cd /path/to/A2A # Adjust path if needed
# Activate the same virtual environment
source .venv/bin/activate
# Navigate specifically into the Demo UI directory
cd demo/ui
# Create the .env file HERE - the UI backend needs its own key access
echo "GOOGLE_API_KEY=YOUR_API_KEY_HERE" > .env
# Run the Demo UI application (FastAPI backend + Mesop frontend)
uv run main.py
# (Leave this terminal running)
We started the fourth server process. This one runs the demo/ui/main.py script, which includes both the Mesop web frontend logic and a FastAPI backend. This backend service acts as our primary “Host Agent” — it will receive commands from the web UI and then use its internal A2A clients to talk to the agent servers we started earlier. Note the separate .env file placement here, as dictated by the UI’s code structure.
Step 6: Connect and Interact!
Now for the payoff:
Let’s open the browse and navigate to http://localhost:12000.
We should now see this UI:
Register Agents: This is crucial! The UI doesn’t automatically know about the agents.
- Click the robot icon (🤖) in the sidebar.
- Click the arrow up icon (⬆️) to add an agent.
- Enter localhost:10000 (NO http://), click “Read” (wait for details), then “Save”.
- Repeat for localhost:10001.
- Repeat for localhost:10002.
Start Chatting:
- Click the message icon (💬) and the “+” button.
- Send prompts like the examples mentioned earlier and see how the different agents respond within the same interface!
We launched the web UI and manually told its backend where to find the running A2A agent servers. Now, when we type in the chat, the backend can discover capabilities (via the AgentCards fetched during registration) and route our requests appropriately using the A2A protocol.
We’ve now successfully bridged agents built with LangGraph, CrewAI, and ADK using the A2A standard! From here, we can explore the different views (Task List, Event List) in the demo to see more of the protocol details in action.
Taking it for a Spin
Alright, the setup is complete! We’ve got three distinct AI agents running locally (LangGraph, CrewAI, ADK) and the Demo UI acting as our central hub, all connected via the A2A protocol. Now, let’s actually talk to them and see what happens “under the hood.”
Let’s fire up the Demo UI in our browser (http://localhost:12000), click the message icon (💬), start a new conversation (+), and try these prompts:
1. Querying the Currency Agent (LangGraph)
This agent is built with LangGraph and uses tools to fetch real-time data. We can try, for example, this prompt:
How much is 50 USD in JPY?
We type the message and hit send. Since this agent supports streaming (we saw streaming: true in its AgentCard), the Demo UI’s backend might initiate a streaming connection (tasks/sendSubscribe). If it does, we might briefly see an intermediate status message like “Working…” or “Looking up exchange rates…” appear from the agent. Finally, we’ll get a text response with the conversion (e.g., “50 USD is 7162.5 JPY”).
A2A Concepts in Action:
- Task Initiation: Our UI backend sent a tasks/send or tasks/sendSubscribe request over HTTP containing our prompt as a TextPart.
- Streaming (Optional but Supported): If tasks/sendSubscribe was used, those “Working…” messages arrive as TaskStatusUpdateEvents via Server-Sent Events (SSE). The final answer might be in the last TaskStatusUpdateEvent or a separate TaskArtifactUpdateEvent containing a TextPart.
- Basic Text I/O: Both our input and the agent’s final output are simple TextParts within A2A messages/artefacts.
We can see this in the event list in the UI:
Let’s Try Another Prompt (Multi-Turn):
What's the exchange rate for Canadian Dollars?
What We’ll See: The agent likely can’t fulfil this directly. It should respond by asking us for clarification, like: “Which currency do you want to convert CAD to?”.
A2A Concepts in Action:
- input-required State: The agent realises it needs more info. It sets the A2A task status to input-required and sends back its clarifying question. The task pauses on the agent side, waiting for us.
- Continuing a Task: When we reply (e.g., “To EUR”), our UI backend sends our answer using the same Task ID as the original request. The agent receives this, now has enough info, and proceeds to complete the task. This shows how A2A manages conversational context within a single task.
2. Commanding the Image Agent (CrewAI)
This agent uses CrewAI and Google Gemini to generate images. Its AgentCard told us it doesn’t support streaming.
Our prompt:
Generate an image of an astronaut riding a bicycle on the moon.
What We’ll See: After we send the prompt, there might be a short delay (image generation isn’t instant!). We might see a generic “Working…” indicator from the UI’s host agent (not via A2A streaming from the CrewAI agent itself). Then, the generated image should appear directly embedded in the chat window:
A2A Concepts in Action:
- Request/Response (tasks/send): Since streaming isn’t supported, the UI backend uses the standard tasks/send method. It waits for the agent to fully complete the image generation before getting a single response back over HTTP.
- FilePart Artefact: This is the cool part here. The image isn’t just described in text; the CrewAI agent packages the image data (likely as base64 encoded bytes) into an Artefact containing a FilePart. This part includes the bytes and a mimeType (like image/png). Our Demo UI receives this artifact and knows how to decode the bytes and render an image because of the mimeType
3. Filing with the Expense Agent (Google ADK)
This agent simulates an expense process and showcases structured data exchange using forms.
Our prompt:
I need to file an expense for coffee.
What We’ll See: The agent will likely respond saying it needs more details. Crucially, we should see an interactive form appear directly within the chat, asking for “Date,” “Amount,” and “Purpose.” We can fill these fields directly in the UI and click a “Submit” button (rendered by the UI based on the agent’s response). After submission, the agent should confirm the expense was filed:
A2A Concepts in Action:
- DataPart Artefact (Form Request): The agent signals it needs structured input by returning an Artefact containing a DataPart. The JSON data within this part describes the form structure (fields, types, labels). Our Demo UI is specifically coded to recognise this DataPart structure and render it as an interactive HTML form.
- input-required State (Implicit): The task effectively moves to a state where it’s waiting for our input via the form.
- DataPart Message (Form Response): When we click “Submit” in the UI, the UI backend gathers our input, creates a new Message containing a DataPart (where the data field holds our filled values like {“date”: “2023–10–27”, “amount”: “5.00”, …}), and sends this message back to the agent using the same Task ID.
- Structured Data Exchange: This demonstrates A2A’s ability to go beyond simple text or opaque files, allowing for structured data exchange, enabling richer interactions like forms.
Experiment Further!
Now it’s your turn! Try different prompts. Ask the currency agent for historical rates, ask the image agent for different styles, or try giving the expense agent partial information initially. While you’re doing that, click around the “Task List” (✔️) and “Event List” (📝) views in the demo UI. They reveal the underlying A2A states and message flows for each interaction. Seeing it work and then peeking behind the curtain is the best way to really grasp how A2A connects these different agent worlds.
Under the Hood: What’s Actually Happening via A2A?
Okay, it works, but how? This isn’t magic; it’s the A2A protocol facilitating the interaction:
- Agent Discovery (AgentCard 📇): When we clicked “Read” while registering localhost:10000, the UI sent an HTTP GET to http://localhost:10000/.well-known/agent.json. The agent server dynamically generated a JSON response based on its coded configuration (it’s not a static file in these samples!). This AgentCard is like a business card, telling the UI the agent’s name, URL, skills, and crucial capabilities (like “does it support streaming?”). This discovery step is key for the orchestrator to know who to talk to.
- Task Lifecycle (➡️⏳❓✅): Every interaction we kick off becomes an A2A task. The protocol defines states like submitted, working, input-required (like when the currency agent needs clarification), and completed/failed. We can see these states change in the demo’s “Task List” view (the checklist icon ✔️). This structured lifecycle is essential for managing potentially long-running agent actions.
- Rich Content (Parts & Artefacts 📄🖼️📊): A2A isn’t limited to text. Communication happens via Message objects containing Parts. We saw TextPart (chat messages), FilePart (the CrewAI agent returning the image), and DataPart (the ADK agent sending the expense form). Agent outputs are often wrapped in Artefacts. This flexibility is vital for agents dealing with diverse data.
- Streaming (SSE 📨): The LangGraph agent supports streaming (capabilities.streaming: true in its card). When the demo backend calls its tasks/sendSubscribe method, the agent sends back updates (like “Looking up rates…”) using standard Server-Sent Events (SSE) over HTTP. This allows for real-time feedback during longer tasks, unlike the CrewAI agent which uses simple request/response (tasks/send).
- “Negotiation” (Capability Check & Clarification): While there isn’t deep bargaining, A2A enables: (1) The initial capability check via the AgentCard. (2) The client potentially telling the agent which output formats it accepts (acceptedOutputModes). (3) The very visible input-required state, where the agent negotiates with the user (via the UI) for more information.
Conclusion
Congrats, we’ve gone hands-on with the Agent2Agent (A2A) demo, successfully setting up a local environment where agents built with completely different frameworks — LangGraph, CrewAI, and Google ADK — are communicating and collaborating through a single web UI. We didn’t just read about interoperability; we built a small-scale example of it.
This demo, while relatively simple, serves as a practical glimpse into the potential A2A unlocks. The core challenge it addresses — enabling disparate AI systems to talk to each other — is something many of us are grappling with as we build increasingly complex applications. A2A’s approach, providing a standard communication layer built on familiar web technologies (HTTP, JSON-RPC, Server-Sent Events), feels pragmatic.
For us as builders, the key takeaways from this exercise are:
- Composability: We saw how a central “host” could discover (via the AgentCard) and delegate tasks to specialised agents (currency conversion, image generation, form handling). This hints at a future where we can build more powerful systems by plugging together best-in-class agents, rather than trying to build monolithic ones or getting locked into a single framework’s ecosystem.
- Standardised Interactions: Instead of inventing custom APIs for every agent-to-agent link, A2A provides defined methods (tasks/send, tasks/get, tasks/sendSubscribe), a clear task lifecycle, and standard ways to handle different data types (TextPart, FilePart, DataPart). This predictability is essential for building reliable multi-agent systems.
- Flexibility: The protocol accommodates various interaction patterns — simple request/response, real-time streaming updates via SSE, and even interactive elements like forms, as demonstrated by the different agents.
Where Do We Go From Here?
A2A is an emerging open standard, and the ecosystem around it is still developing. The demo showcases the core concepts effectively, but there’s naturally more depth to explore.
Dive Deeper into the Code: Now that you’ve seen it run, I encourage you to explore the A2A GitHub repository further. Check out:
- specification/json/a2a.json: The formal source of truth for the protocol structure.
- samples/python/common: The reusable Python client/server libraries and type definitions (using Pydantic).
- The specific agent adapters (e.g., samples/python/agents/langgraph/task_manager.py) to see how they bridge the framework logic to A2A.
Experiment More: Try modifying the agents or the host logic. Can you add another agent? Can you create a more complex workflow in the host agent?
Consider Your Projects: Think about where a standard like A2A could simplify communication between different AI components you’re building or planning to build.
Engage with the Community: The project has GitHub Discussions and Issues for feedback and questions.
While A2A isn’t a silver bullet for all multi-agent challenges, it represents a significant step towards a more open and interoperable ecosystem. By providing a common language, it allows us, as developers, to focus more on building intelligent, specialized agents and less on the plumbing needed to connect them. The journey is just beginning, but this hands-on demo shows the foundation is solid and the potential is definitely exciting.