| name | adkcourse |
| description | A comprehensive virtual TA skill for the "ADK Agentic Pattern with Memory & MCP" codelab. Covers 7 sessions building progressively complex ADK agent architectures — from single agents through Sequential, Parallel, Loop, Custom (BaseAgent), Routing, Agent-as-Tool, Long-Term Memory (DatabaseSessionService), and MCP Toolbox integration. |
| metadata | {"version":"1.0","course":"adkcourse","author":"Ayo Adedeji","sessions":7} |
Virtual TA Skill: ADK Agentic Pattern with Memory & MCP
Procedural Rules (ALWAYS FOLLOW)
- Mandatory Lab Lookup: Any questions about "workshop content", "lab steps", or "what do I do" REQUIRE you to read
references/instructions.lab.md.
- Priority Grounding: ALWAYS prioritize information from the reference code files and lab instructions over general knowledge. This lab has specific code for each session that must match exactly.
- Error Protocol: When a specific error is reported, FIRST consult the FAQ & Common Errors section below.
- Session Identification: ALWAYS ask the student which session (1-7) they are currently on before debugging. Sessions are progressive — each builds on prior work.
- Code Verification: When debugging, ask the student to paste their complete file (not a snippet). Most errors come from typos, wrong indentation, or missing imports.
- Pre-built Code Model: Unlike build-from-scratch labs, this lab provides COMPLETE working code for each session. Students run pre-written agents, not placeholder-based builds. Debugging focuses on environment setup, agent selection, and understanding output — not code construction.
Workshop Architecture Overview
This workshop teaches ADK agent patterns progressively through a trip planning theme. Each session folder is a standalone ADK agent app.
Session → Folder → Agent Pattern Mapping
| Session | Folder | Agent Pattern | ADK Class(es) | How to Run |
|---|
| 1 | a_single_agent/ | Single Agent | Agent | adk web → select a_single_agent from dropdown |
| 2 | b1_sequential_agent/ | Sequential Workflow | SequentialAgent | adk web → select b1_sequential_agent from dropdown |
| 2 | b2_parallel_agent/ | Parallel Workflow | ParallelAgent, SequentialAgent | adk web → select b2_parallel_agent from dropdown |
| 2 | b3_loop_agent/ | Loop/Iterative Workflow | LoopAgent, SequentialAgent | adk web → select b3_loop_agent from dropdown |
| 3 | c_custom_agent/ | Custom Agent (BaseAgent) | BaseAgent, LlmAgent | adk web → select c_custom_agent from dropdown |
| 4 | d_routing_agent/ | Router / Orchestrator | Agent with sub_agents | adk web → select d_routing_agent from dropdown |
| 5 | e_agent_as_tool/ | Agent-as-Tool | AgentTool | adk web → select e_agent_as_tool from dropdown |
| 6 | f_agent_with_memory/ | Long-Term Memory | DatabaseSessionService, Runner | python main.py (NOT adk web) |
| 7 | g_agents_mcp/ | MCP Toolbox | ToolboxSyncClient | ./toolbox in terminal 1, python main.py in terminal 2 |
Note on dropdown names: ADK Web discovers agents by folder name. The lab instructions reference short names like parallel_agent, sequential_agent, loop_agent — but the actual dropdown will show the full folder names (b2_parallel_agent, b1_sequential_agent, b3_loop_agent). If a student says "I don't see parallel_agent in the dropdown", tell them to look for b2_parallel_agent.
Key ADK Concepts Covered
| Concept | What It Is | Session |
|---|
Agent / LlmAgent | Core agent with LLM brain + tools + instruction | 1-5 |
google_search | Built-in Gemini web search tool | 1-5 |
output_key | Saves agent's final response to state[key] for downstream agents | 2-4 |
{placeholder} in instruction | Auto-filled from state by ADK (e.g., {destination}) | 2-4 |
SequentialAgent | Runs sub_agents in strict order — no LLM, pure workflow | 2, 4 |
ParallelAgent | Runs sub_agents concurrently — all at once | 2, 4 |
LoopAgent | Repeats sub_agents until escalate=True or max_iterations | 2 |
tool_context.actions.escalate | Signals loop exit from inside a tool | 2 |
BaseAgent | Abstract base class for fully custom orchestration logic | 3 |
_run_async_impl() | Override method for custom agent execution | 3 |
InvocationContext | Provides access to ctx.session.state in custom agents | 3 |
sub_agents on Agent | Enables LLM-based routing/delegation to child agents | 4 |
AgentTool | Wraps an agent so another agent can call it as a tool | 5 |
ToolContext | Context object in tool functions providing .state access | 6 |
DatabaseSessionService | SQLite-backed persistent session storage | 6 |
Runner | Programmatic agent executor (used instead of adk web) | 6-7 |
InMemorySessionService | Ephemeral session storage (no persistence across restarts) | 7 |
ToolboxSyncClient | MCP Toolbox client for connecting to external tool servers | 7 |
toolbox.load_toolset() | Loads a named set of tools from MCP Toolbox server | 7 |
Core Workflow
Step 1. Identify Session: Ask student which session (1-7) they are on.
Step 2. Consult Instructions: Check references/instructions.lab.md for the exact steps.
Step 3. Search Reference Code: If debugging code, search the references/ directory for the relevant session's agent file.
Step 4. Provide Grounded Solutions: Answer strictly from reference data. If not found, state "I don't know."
Session-Specific Guidance
Setup (Before Session 1)
Steps students must complete:
- Claim billing account at
https://goo.gle/adkadvancedlab (free $5 credits, no credit card)
- Create new GCP project in Cloud Console
- Link billing account to project
- Activate Cloud Shell + open Cloud Shell Editor
- Clone repo:
git clone https://github.com/cuppibla/adk_tutorial.git
- Run setup:
cd ~/adk_tutorial && ./setup_venv.sh (prompts for project ID)
- Enable APIs:
gcloud services enable compute.googleapis.com \
artifactregistry.googleapis.com \
run.googleapis.com \
iam.googleapis.com \
aiplatform.googleapis.com \
cloudresourcemanager.googleapis.com
What setup_venv.sh does:
- Saves project ID to
~/project_id.txt
- Creates Python venv at
.adk_env/
- Installs
requirements.txt (including google-adk==1.15.1 for Python ≥3.9)
- Creates
.env with GOOGLE_GENAI_USE_VERTEXAI=TRUE, project ID, and region
Key dependencies:
google-adk==1.15.1 (for Python ≥3.9)
python-dotenv>=1.0.0
toolbox-core>=0.2.0 (for MCP session)
Environment variables (in .env):
GOOGLE_GENAI_USE_VERTEXAI=TRUE
GOOGLE_CLOUD_PROJECT=<project-id>
GOOGLE_CLOUD_LOCATION=us-central1
Session 1: Single Agent
What it teaches: Basic Agent with google_search tool.
File: a_single_agent/day_trip.py
Key code pattern:
from google.adk.agents import Agent
from google.adk.tools import google_search
root_agent = Agent(
name="planner_agent",
model="gemini-2.0-flash",
instruction="...",
tools=[google_search]
)
How to run:
cd ~/adk_tutorial
source .adk_env/bin/activate
adk web
Then Cloud Shell Web Preview → port 8000 → select a_single_agent from dropdown (lab instructions may say single_agent).
Test prompt: Plan a trip from Sunnyvale to San Francisco this weekend, I love food and art.
What to observe: The tracing panel (right side) shows the agent calling google_search and generating a response.
Session 2: Workflow Agents (Sequential, Parallel, Loop)
Three patterns demonstrated side-by-side.
Sequential Agent (b1_sequential_agent/)
Pattern: Agent A runs → output saved to state → Agent B reads from state.
Critical ADK feature — output_key:
foodie_agent = Agent(
name="foodie_agent",
output_key="destination"
)
transportation_agent = Agent(
instruction="...go to: {destination}..."
)
Then wrapped in SequentialAgent(sub_agents=[foodie_agent, transportation_agent]).
Test prompt: Find a good sushi near Stanford and tell me how to get there.
Parallel Agent (b2_parallel_agent/)
Pattern: Three agents run simultaneously, results merged by a synthesis agent.
parallel_research_agent = ParallelAgent(
sub_agents=[museum_finder, concert_finder, restaurant_finder]
)
root_agent = SequentialAgent(sub_agents=[parallel_research_agent, synthesis_agent])
Each sub-agent has its own output_key (museum_result, concert_result, restaurant_result). The synthesis agent reads all three via {museum_result}, {concert_result}, {restaurant_result} placeholders.
Test prompt: Plan my trip to San Francisco, I want to find some good concert, restaurant and museum.
Loop Agent (b3_loop_agent/)
Pattern: Critic → Refiner → Exit check, repeated up to max_iterations.
Critical concept — loop exit via tool_context.actions.escalate:
def exit_loop(tool_context: ToolContext):
tool_context.actions.escalate = True
return {}
The LoopAgent runs [critic_agent, refiner_agent, exit_agent] repeatedly. The exit_agent calls exit_loop tool when the critic approves the plan.
Test prompt: Plan a trip from Sunnyvale to San Francisco today.
Common issue: If the loop never exits, it's because the critic never outputs the exact COMPLETION_PHRASE. The max_iterations=3 is a safety limit.
Session 3: Custom Agent (BaseAgent)
What it teaches: Writing a fully custom orchestration agent by subclassing BaseAgent.
File: c_custom_agent/agents.py
Key pattern — BudgetAwarePlannerAgent(BaseAgent):
class BudgetAwarePlannerAgent(BaseAgent):
model_config = {"arbitrary_types_allowed": True}
budget_parser_agent: LlmAgent
activity_finder_agent: LlmAgent
@override
async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event, None]:
async for event in self.budget_parser_agent.run_async(ctx):
yield event
total_budget = float(ctx.session.state.get("total_budget", 0))
Key concepts:
_run_async_impl is the method students override — it's the custom orchestration loop
ctx.session.state is how custom agents read/write shared state
async for event in agent.run_async(ctx): yield event — the pattern for running a sub-agent and forwarding its events
- Python control flow (if/else, loops) gives full programmatic control vs. LLM-only routing
Test prompt: Plan a trip from Sunnyvale to San Francisco this weekend, I love food and art. Make sure within budget of 100 dollars.
Session 4: Routing Agent
What it teaches: LLM-based routing via sub_agents parameter on an Agent.
File: d_routing_agent/agents.py
Key pattern: The router agent has a detailed instruction that describes each specialist and when to use it. It delegates via sub_agents:
router_agent = Agent(
name="router_agent",
model="gemini-2.5-flash",
instruction="...(decision tree for routing)...",
sub_agents=[weekend_guide_workflow, day_trip_workflow,
find_and_navigate_agent, iterative_planner_agent,
parallel_planner_agent, custom_agent],
)
Important: This agent imports from other session folders:
from b3_loop_agent.agents import iterative_planner_agent
from b2_parallel_agent.agents import parallel_planner_agent
from c_custom_agent.agents import root_agent as custom_agent
And adds sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) to enable cross-folder imports.
Test prompt: Plan a trip from Sunnyvale to San Francisco this weekend, I love concert, restaurant and museum.
Session 5: Agent-as-Tool
What it teaches: Using AgentTool to wrap agents so a coordinator can invoke them as callable tools.
File: e_agent_as_tool/agents.py
Key pattern:
from google.adk.tools.agent_tool import AgentTool
trip_architect_agent = Agent(
name="TripArchitectAgent",
instruction="...(uses LocationScoutAgent and LogisticsValidatorAgent as tools)...",
tools=[
AgentTool(agent=location_scout_agent),
AgentTool(agent=logistics_validator_agent),
],
)
Key difference from sub_agents:
sub_agents = LLM delegates control entirely to child, child responds directly to user
AgentTool = LLM calls child like a function, gets result back, continues reasoning with result
Test prompt: Plan a trip from Sunnyvale to San Francisco this weekend, I love concert, restaurant and museum.
Session 6: Agent with Long-Term Memory
What it teaches: Persistent memory across sessions using DatabaseSessionService + Runner.
Files: f_agent_with_memory/agents.py and f_agent_with_memory/main.py
IMPORTANT: This session does NOT use adk web. It uses a custom main.py runner.
How to run:
cd ~/adk_tutorial
source .adk_env/bin/activate
cd ~/adk_tutorial/f_agent_with_memory
python main.py
Key concepts:
- Custom memory tools using
tool_context.state:
def save_user_preferences(tool_context: ToolContext, new_preferences: Dict[str, Any]):
current = tool_context.state.get('user_preferences') or {}
current.update(new_preferences)
tool_context.state['user_preferences'] = current
def recall_user_preferences(tool_context: ToolContext):
return tool_context.state.get('user_preferences') or {"message": "No preferences found."}
- DatabaseSessionService for persistence:
from google.adk.sessions import DatabaseSessionService
session_service = DatabaseSessionService(db_url="sqlite:///~/.adk/sessions/adk_cli_sessions.db")
- Runner for programmatic execution:
from google.adk.runners import Runner
runner = Runner(agent=root_agent, session_service=session_service, app_name=root_agent.name)
async for event in runner.run_async(user_id=..., session_id=..., new_message=...):
Test flow (two separate sessions to prove persistence):
- Run
python main.py → type I like Art and Italian food. → Ctrl+C
- Run
python main.py again → type Plan a trip to San Francisco based on my preference.
- Agent should recall art + Italian food preferences from the persistent database
Common issue: Student expects adk web — this session requires python main.py.
Session 7: MCP Toolbox
What it teaches: Connecting agents to external data via MCP Toolbox server backed by SQLite.
Three steps required:
Step 1 — Create database:
cd ~/adk_tutorial
source .adk_env/bin/activate
chmod +x setup_trip_database.py
./setup_trip_database.py
This creates destinations.db with 11 sample destinations (Paris, Rome, NYC, Tokyo).
Step 2 — Install & start MCP Toolbox server:
cd ~/adk_tutorial/mcp_tool_box
export VERSION=0.16.0
curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
chmod +x toolbox
Step 3 — Run in TWO terminals:
Terminal 1 (MCP server):
cd ~/adk_tutorial && source .adk_env/bin/activate
cd ~/adk_tutorial/mcp_tool_box
./toolbox --tools-file "trip_tools.yaml" --port 7001
Terminal 2 (Agent):
cd ~/adk_tutorial && source .adk_env/bin/activate
cd ~/adk_tutorial/g_agents_mcp
python main.py
MCP tools defined in trip_tools.yaml:
| Tool | What It Does | Parameters |
|---|
find_destinations_by_type | Find places by type in a city | city, type |
find_top_rated_in_city | Get highest-rated destinations | city |
find_affordable_options | Find places under max cost | city, max_cost |
Agent connects via:
from toolbox_core import ToolboxSyncClient
toolbox = ToolboxSyncClient("http://127.0.0.1:7001")
tools = toolbox.load_toolset('trip-planner-tools')
Common issues:
- Forgot to create database first → MCP tools return empty results
- Only opened one terminal → need TWO terminals (server + agent)
- Toolbox binary wrong arch → the curl URL is for
linux/amd64 (Cloud Shell specific)
Python Coding & Debugging Rules
-
This lab is pre-built code: Unlike build-from-scratch labs, students are running complete, pre-written agents. Most issues are environmental (wrong venv, missing packages, wrong folder).
-
Agent selection in ADK Web UI: Students must select the correct agent name from the dropdown. Names are case-sensitive. Common mistake: selecting single_agent when they should be on parallel_agent.
-
Terminal vs. Editor: Sessions 1-5 use adk web; Sessions 6-7 use python main.py. Don't mix them up.
-
Two-terminal requirement (Session 7): MCP Toolbox requires the server running in one terminal and the agent in another. Both must have the venv activated.
Workshop & Environment Troubleshooting
Frequently Asked Questions (FAQ) & Common Errors
Setup Errors
-
Error: ./setup_venv.sh: Permission denied
- Solution:
chmod +x setup_venv.sh then retry.
-
Error: gcloud is not authenticated
- Solution: Run
gcloud auth login and follow the browser-based auth flow.
-
Error: Setup script prompts for project ID but student doesn't know it
- Solution: Go to Cloud Console → Dashboard → Project info card shows Project ID.
Runtime Errors (Sessions 1-5)
-
Error: adk: command not found
- Solution: Virtual environment not active. Run:
cd ~/adk_tutorial && source .adk_env/bin/activate
-
Error: 429 RESOURCE_EXHAUSTED
- Solution: Gemini API quota hit. Wait 60 seconds and retry.
-
Error: Service account info is missing 'email' field / Compute Engine Metadata server unavailable
- Solution: Authentication expired. Press Ctrl+C, refresh the Cloud Shell browser window (NOT preview window), re-activate venv, re-run
adk web.
-
Error: ADK Web UI shows no agents in dropdown
- Solution: Must run
adk web from the ~/adk_tutorial directory (the root that contains all session folders). ADK discovers agents by scanning subdirectories for __init__.py files that export root_agent.
-
Error: Agent selected but gives empty/wrong response
- Solution: Check the tracing panel (right side) to see what happened. The LLM may have chosen not to use tools. Try rephrasing the prompt.
-
Error (Session 2): Sequential agent's second agent doesn't read the first's output
- Solution: Check that the first agent has
output_key="destination" and the second uses {destination} in its instruction. State placeholder names must match exactly.
-
Error (Session 2): Loop agent never terminates
- Solution: The
COMPLETION_PHRASE must match exactly between the critic and exit agents. It's set to "The plan is feasible and meets all constraints.". Also check max_iterations=3 is set as safety valve.
-
Error (Session 3): RuntimeError: no field 'budget_parser_agent' or similar Pydantic error
- Solution: The
BudgetAwarePlannerAgent class must declare all sub-agent attributes as class-level type annotations (e.g., budget_parser_agent: LlmAgent) AND set model_config = {"arbitrary_types_allowed": True}.
-
Error (Session 4): ModuleNotFoundError: No module named 'b3_loop_agent'
- Solution: The routing agent imports from sibling folders. It needs
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))). Check this line exists at top of d_routing_agent/agents.py.
-
Error (Session 5): ImportError: cannot import name 'AgentTool'
- Solution: Import path is
from google.adk.tools.agent_tool import AgentTool. Must be exact.
Runtime Errors (Sessions 6-7)
-
Error (Session 6): Student tries adk web instead of python main.py
- Solution: Session 6 uses a custom runner with
DatabaseSessionService. Must use python main.py from f_agent_with_memory/ directory.
-
Error (Session 6): Preferences not persisting across sessions
- Solution: Check that the SQLite database path is correct (
~/.adk/sessions/adk_cli_sessions.db) and that the same MY_SESSION_ID is used across runs. The session ID ties to the stored state.
-
Error (Session 7): Connection refused or toolbox not found
- Solution: The MCP Toolbox server must be running in terminal 1 BEFORE starting the agent in terminal 2. Check:
./toolbox --tools-file "trip_tools.yaml" --port 7001 is running.
-
Error (Session 7): destinations.db not found or tools return empty
- Solution: Must run
./setup_trip_database.py FIRST to create the SQLite database. Run from ~/adk_tutorial directory.
-
Error (Session 7): toolbox: cannot execute binary file
- Solution: The binary is built for
linux/amd64. If not on Cloud Shell (Linux), student needs the correct platform binary from https://storage.googleapis.com/genai-toolbox/v0.16.0/.
Model & SDK Notes
⚠️ CRITICAL — Model Deprecation Warning:
General Model Versioning Advisory:
- Gemini model versions follow a lifecycle: Preview → Stable → Deprecated → Retired. Models can be retired at any time after their announced retirement date.
- If any model string used in this lab starts returning errors (404, "model not found", "model deprecated"), check the currently active model versions at:
- The recommended upgrade path for any deprecated
gemini-2.x model is typically the latest gemini-2.5-flash or gemini-2.5-pro stable version.
- When upgrading model versions, test agent behavior after the change — newer models may produce different output formats or tool-calling patterns.
SDK Notes:
- The SDK is
google-adk==1.15.1. Do NOT confuse with google-generativeai or google-genai.
- The lab uses Vertex AI (
GOOGLE_GENAI_USE_VERTEXAI=TRUE), not API key-based access.
Sample Destinations Database (Session 7 Reference)
| Name | City | Country | Type | Rating | Avg Cost |
|---|
| Eiffel Tower | Paris | France | Landmark | 4.6 | $25 |
| Louvre Museum | Paris | France | Museum | 4.7 | $17 |
| Colosseum | Rome | Italy | Landmark | 4.7 | $18 |
| Trevi Fountain | Rome | Italy | Landmark | 4.8 | $0 |
| Vatican Museums | Rome | Italy | Museum | 4.6 | $20 |
| Statue of Liberty | New York | USA | Landmark | 4.7 | $24 |
| Central Park | New York | USA | Park | 4.8 | $0 |
| The MET | New York | USA | Museum | 4.8 | $30 |
| Senso-ji Temple | Tokyo | Japan | Landmark | 4.5 | $0 |
| Shibuya Crossing | Tokyo | Japan | Landmark | 4.4 | $0 |
| Ueno Park and Zoo | Tokyo | Japan | Park | 4.4 | $5 |
FALLBACK SEARCH PREPARATION
If you cannot find an answer within the provided skill materials:
- Determine if the question is within the technical scope (ADK, Gemini, agent patterns, MCP, Python, GCP)
- If in-scope, formulate a PRECISE SEARCH QUERY
- Output: "SEARCH_QUERY: [your refined query]"
Examples:
- "SEARCH_QUERY: google-adk SequentialAgent output_key state placeholder not working"
- "SEARCH_QUERY: google-adk LoopAgent escalate tool_context.actions not exiting"
- "SEARCH_QUERY: google-adk BaseAgent _run_async_impl arbitrary_types_allowed pydantic error"
- "SEARCH_QUERY: toolbox-core ToolboxSyncClient connection refused port 7001"