AI Engineering Bootcamp · Week 3

Building Multi-Agent AI Systems

From Concepts to Production with ADK

AI Engineering Bootcamp

Aki Wijesundara

Aki Wijesundara

Instructor · Snapdrum

Agenda

Seven sections — ~90 minutes total

#SectionTimeType
1What Are AI Agents, Really?10 minLecture
2From Single Agent to Multi-Agent15 minLecture
3Introducing ADK — Your Build Tool10 minLecture + Demo
4MCP — Giving Agents Tools15 minLecture + Demo
5A2A — Agents Talking to Agents15 minLecture + Demo
6Full System: Putting It All Together15 minLecture + Demo
7Homework & Next Steps10 minAssignment

Agents = LLMs with a Loop

A chatbot does input → output. An agent does think → act → observe → repeat.

Four Core Components

📝 Instructions

System prompt — what to do, constraints, personality

🧠 Model

GPT, Gemini, Claude, Llama — the reasoning engine

🔧 Tools

Functions: search, query DB, send email, call APIs

📈 Memory

Short-term (session) and long-term (across sessions)

✅ Use agents when

Multi-step tasks, ambiguous requests, external systems, unknown paths

❌ Skip agents when

Simple prompt/response, deterministic workflow, latency-critical

The Agent Loop (ReAct)

💬 User Input
🧠 Reason (LLM)
🔧 Act (Tools)
👁 Observe (Results)
Done? → No → Back to Reason

Why Multi-Agent & How to Design It

One agent with 15+ tools breaks. Split by domain — each agent gets a focused job, short instructions, and limited tools.

Four Multi-Agent Patterns

🎯 Router / Delegation

Root reads intent, delegates to specialist. We build this today.

➡ Sequential Pipeline

Fixed order: A → B → C. Content gen, data processing.

🔀 Parallel Fan-Out

Simultaneous agents, merge results. Research, aggregation.

🔄 Loop / Refinement

Produce, review, loop until quality met. Writing, code gen.

Key Design Decisions

How many agents? — Split by domain, not by task
Who routes? — LLM (adaptive) or workflow (deterministic)
Data sharing? — Session state, output keys, or external DB
Failure handling? — Escalation, fallback to human, retry

The Multi-Agent Solution

🎯 Root (Router)
💰 Billing
🛠 Technical
🚨 Escalation

Why It Works

ModularitySpecializationReusabilityTestabilityScalability

What You Need From a Framework

Agent definitionsHierarchiesOrchestrationLLM routingShared stateDebug UITool connectivityDeployment

What is ADK?

Agent Development Kit — Google's open-source framework for building and deploying AI agents

🔌 Open Source

Python, TypeScript, Go, Java — multi-language from the start

🤖 Model-Agnostic

Use Gemini, Claude, Ollama, LiteLLM, or any model

☁️ Deployment-Agnostic

Run locally, on Cloud Run, GKE, or Vertex AI

🔗 Native MCP + A2A

Built-in support for both open protocols that matter for production agent systems

Designed to make agent development feel like software development — not prompt engineering with extra steps.

Why ADK Over Other Frameworks?

Comparing the major agent frameworks

FrameworkStrengthTrade-off
LangGraphGraph-based orchestration, strong communitySteeper learning curve
CrewAIRole-based multi-agent, easy to startLess control over execution flow
AutoGenResearch-grade multi-agent conversationsComplex setup, Microsoft-centric
ADKMulti-language, native MCP + A2A, built-in eval + dev UINewer ecosystem, still maturing

ADK's differentiator: natively supports both MCP (tool standard) and A2A (agent communication standard).

Your First Agent in ADK

Tools are plain Python functions. ADK wraps them automatically.

from google.adk.agents import Agent

def lookup_customer(email: str) -> dict:
    "Look up customer account information by email."
    return {"name": "Jane Doe", "plan": "Pro"}

def check_order_status(order_id: str) -> dict:
    "Check the current status of an order."
    return {"order_id": order_id, "status": "shipped"}

support_agent = Agent(
    name="support_agent",
    model="gemini-2.5-flash",
    instruction="You are a helpful customer support agent.",
    tools=[lookup_customer, check_order_status],
)

The LLM reads the function names and docstrings to decide when to call them. No schema definitions needed.

Your First Multi-Agent System in ADK

The root agent reads the description of each sub-agent and decides who handles the query

billing_agent = Agent(
    name="billing_agent",
    model="gemini-2.5-flash",
    description="Handles billing: invoices, payments, refunds.",
    instruction="Help customers with billing issues.",
    tools=[lookup_invoice, process_refund],
)

technical_agent = Agent(
    name="technical_agent",
    model="gemini-2.5-flash",
    description="Handles technical issues: bugs, outages, how-to.",
    instruction="Help customers with technical problems.",
    tools=[search_knowledge_base, check_system_status],
)

root_agent = Agent(
    name="support_router",
    model="gemini-2.5-flash",
    instruction="Route customer queries to the right specialist.",
    sub_agents=[billing_agent, technical_agent],
)

No explicit routing logic needed. The LLM reads description and delegates automatically.

Demo 1 — Multi-Agent Support System in ADK

Building the multi-agent customer support system from the diagram

Architecture

🎯 Root Agent (Router)
💰 Billing
🛠 Technical
🚨 Escalation

What to Notice

  • The router agent has no tools — it only routes
  • Each specialist has a focused instruction and limited tools
  • Try a billing question, then a technical question
  • Watch the routing in the trace view
  • ADK Dev UI shows the full execution path

What is MCP?

In Demo 1, we hardcoded tools as Python functions. In reality, agents need databases, SaaS platforms, APIs. MCP is the standard.

🔌 Think of it as USB for AI Agents

One universal connector instead of a custom cable for every device.

Client-Server Architecture

  • MCP Server — exposes tools, resources, data (Supabase, Asana, filesystem)
  • MCP Client — your agent, discovers and uses those tools
🗃

Databases

Customer records, order data

☁️

SaaS Platforms

Asana, Jira, Salesforce

🔗

Internal APIs

Knowledge bases, CRMs

📁

File Systems

Documents, logs

How MCP Works

Your agent discovers tools at runtime through a client-server protocol

🤖 Your Agent
(MCP Client)

"What tools do you have?"

MCP Protocol
🗃 MCP Server
(Supabase)

"I have: query, insert, update"

📥 Consuming

Your ADK agent connects to an MCP server, auto-discovers tools, and calls them transparently via McpToolset

📤 Exposing

Wrap your ADK tools as an MCP server. Any MCP client can use them: Claude Desktop, Cursor, other agents

MCP in ADK — The Code

No hardcoded database functions. The agent discovers tools from the MCP server automatically.

from google.adk.agents import Agent
from google.adk.tools.mcp_tool import McpToolset, StdioConnectionParams
from mcp.client.stdio import StdioServerParameters

supabase_mcp = McpToolset(
    connection_params=StdioConnectionParams(
        server_params=StdioServerParameters(
            command="npx",
            args=["-y", "@supabase/mcp-server-supabase@latest",
                  "--access-token", TOKEN],
        ),
    ),
)

billing_agent = Agent(
    model="gemini-2.5-flash",
    name="billing_agent_mcp",
    instruction="You are a billing specialist with real database access.",
    tools=[supabase_mcp],
)

The agent discovers every available tool from the MCP server automatically. Zero custom integration code.

What Can You Connect via MCP?

Pre-built MCP servers in the ecosystem

🗃

Databases

Supabase, Spanner, AlloyDB, Postgres (MCP Toolbox)

📋

Project Mgmt

Asana (30+ tools), Atlassian (Jira + Confluence)

☁️

Google Cloud

BigQuery, Bigtable, Cloud API Registry

🎨

Media

Imagen, Veo, Chirp 3 HD, Lyria (Genmedia MCP)

📁

File Systems

Local filesystem access, document reading

🔗

Custom APIs

Any API via Apigee or your own MCP server

The MCP ecosystem is growing fast. If it doesn't exist, build your own with FastMCP.

Tool Filtering — Security in Production

You don't have to expose all tools. Use tool_filter to whitelist.

McpToolset(
    connection_params=StdioConnectionParams(...),
    tool_filter=[
        "read_file",
        "list_directory",
        "search_files",
    ],  # Only these tools available to the agent
)

🚨 In production, always filter. Don't give an agent write access if it only needs to read.

Demo 2 — Agent + Supabase via MCP

The Billing Agent now connects to a real Supabase database via MCP

Setup

  • Supabase project with tables: customers, orders, support_tickets
  • Supabase MCP server running locally
  • Billing Agent connected via McpToolset

What to Notice

  • We didn't write a single database query function
  • Agent auto-discovers table operations from MCP server
  • In the trace: see MCP tool discovery, then actual queries
  • Agent figures out how to join data across tables on its own

What is A2A?

MCP connects agents to tools. But what connects agents to other agents across systems? A2A is the open standard.

🔧

MCP

Giving an agent a toolkit
agent ↔ tool

👥

Multi-Agent

Teammates in the same room
agent ↔ agent, same app

🌐

A2A

Calling a colleague at another office
agent ↔ agent, across network

A2A lets a Python agent talk to a Java agent, or your support system call a partner's shipping agent — without knowing how it's built.

How A2A Works

Two steps: expose and consume

Step 1: Expose

Make your agent available on the network

Your Agent
to_a2a()
A2A Server

Gets an Agent Card + network endpoint

Step 2: Consume

Use someone else's agent

Your Agent
RemoteA2aAgent(url)
Remote A2A

Feels like a local sub-agent. ADK handles networking.

Agent Cards — Discovery

Every A2A agent has an Agent Card — a JSON file describing what it can do

What the agent does
What inputs it accepts
What outputs it returns
Where to reach it (URL)

Think of it as…

An API spec, but for agents. ADK auto-generates Agent Cards when you use to_a2a().

A2A in Code — Expose & Consume

Two sides of the same protocol

Expose (make network-accessible)

from google.adk.agents import Agent
from google.adk.a2a.utils.agent_to_a2a import
    to_a2a

shipping_agent = Agent(
    name="shipping_status_agent",
    model="gemini-2.5-flash",
    instruction="Look up shipping status.",
    tools=[get_shipping_status],
)

app = to_a2a(shipping_agent, port=8001)
# uvicorn agent:app --port 8001

Consume (use remote agent)

from google.adk.agents.remote_a2a_agent import
    RemoteA2aAgent

remote_shipping = RemoteA2aAgent(
    name="shipping_agent",
    agent_card="http://localhost:8001",
)

root_agent = Agent(
    name="customer_support",
    sub_agents=[billing, technical,
                remote_shipping],
)

The remote agent sits alongside local sub-agents. The LLM routes to it like any other — any framework, any language.

Real-World A2A Use Cases

Where agent-to-agent communication makes sense

🏢 Microservices Architecture

Order Agent ↔ Inventory Agent ↔ Shipping Agent ↔ Payment Agent — each an independent service, A2A as the communication layer.

🌐 Cross-Org Collaboration

Your support agent calls a partner's warranty verification agent. You don't know their tech stack.

🔄 Cross-Language

Python orchestrator talks to a Java compliance agent and a Go data processing agent. A2A standardizes communication.

💲 Third-Party Services

A financial data provider exposes real-time stock prices through an A2A agent. Your advisor agent consumes it.

Demo 3 — A2A in Action

A separate Shipping Agent that our support system talks to via A2A

Terminal 1 — Shipping Agent (port 8001)

  • Standalone agent with tools
  • Exposed via to_a2a()

Terminal 2 — Support System (port 8000)

  • Multi-agent support system from Demo 1
  • Now includes RemoteA2aAgent

What to Notice

  • Two separate processes, two terminals
  • Support agent doesn't know how shipping agent works
  • Reads the Agent Card, discovers capabilities, delegates
  • In the trace: see the A2A network call
  • Shipping agent could be swapped for any A2A-compatible agent

The Complete Architecture

Three layers, one system

Your ADK Application
🎯 Root Agent (Router)
💰 Billing
MCP: Supabase
🛠 Technical
MCP: Knowledge
🌐 RemoteA2a: Shipping
↓ A2A
🚚 Shipping Agent
(separate service)

When to Use What

A decision guide for the three patterns

You need to…Use
Connect an agent to a database, API, or file systemMCP
Split a complex task into focused agents in one appMulti-Agent
Call an agent running as a separate serviceA2A
Build a predictable pipeline (step 1 → 2 → 3)Workflow Agents
Let the LLM decide which agent handles a requestLLM Delegation
Make your agent usable by anyone, any frameworkA2A (expose)

Demo 4 — The Full System

Everything together in one demo

Components

  • Root Agent routes customer queries
  • Billing Agent connects to Supabase via MCP
  • Technical Agent uses a knowledge base MCP server
  • Shipping goes to remote agent via A2A

Test Scenarios

"What's the status of my order #1234?"
→ Billing Agent → MCP → Supabase
"My app keeps crashing on login"
→ Technical Agent → MCP → Knowledge Base
"Where is my package?"
→ A2A → Remote Shipping Agent

Evaluation & Deployment

Before you ship: test. When you ship: pick the right target.

ADK Evaluation Framework

🎯 Response Quality

Is the final answer correct? Define test cases with expected outputs.

👁 Trajectory

Did the agent call the right tools and route to the correct sub-agent?

Deployment Options

ADK Dev UILocal dev & debugging
CLIQuick testing, CI/CD
Agent EngineManaged production (Vertex AI)
Cloud RunContainerized, custom infra
GKEKubernetes, multi-service

All paths support A2A — agents can be exposed and consumed regardless of where they run.

Homework Assignment

Build a Multi-Agent Customer Support System with MCP and A2A

1. Supabase Setup

Create a Supabase project with customers, orders, support_tickets tables. Seed with 10+ records per table.

2. Multi-Agent System in ADK

Root router + at least 2 specialist sub-agents. At least one connected to Supabase via MCP.

3. Returns Agent via A2A

Separate service with check_return_eligibility + initiate_return. Exposed via to_a2a().

4. Connect & Test

Connect Returns Agent via RemoteA2aAgent. Test 3 scenarios: billing (MCP), returns (A2A), escalation.

Deliverables

  • Working code in a GitHub repo
  • README with architecture diagram
  • Screen recording (2-3 min) of test scenarios in ADK Dev UI

Stretch Goals

LoopAgent (draft/review)Tool Filtering (read-only)Eval Test Cases (5+)Expose via A2A

Resources

Everything you need to build with ADK, MCP, and A2A

Three Things to Remember

🔧

MCP

How agents access tools and data
agent ↔ tool

👥

Multi-Agent

How agents work together locally
agent ↔ agent, same app

🌐

A2A

How agents collaborate across boundaries
agent ↔ agent, across network

These three patterns compose. Start with the concepts, pick the right pattern, then implement with ADK.