Build a Maternity Companion Agent
Build a secure multi-agent maternity health system with ADK 2.0.
Introduction
30 Second Summary
Pregnancy is a time when timely information can mean the difference between a routine check-up and an emergency. In many communities, pregnant mothers lack consistent health monitoring between clinic visits.
In this project, you will build a multi-agent maternity companion that onboards mothers, monitors symptoms, triages risk against WHO danger-sign protocols, and escalates emergencies to health workers.
What You'll Build
A collaborative multi-agent system built with Google ADK 2.0's collaborative workflow pattern, where a Coordinator agent dynamically delegates tasks to specialized sub-agents for intake, monitoring, triage, and escalation.
By the end of this project, you'll have:
- A working five-agent system with Coordinator, Intake, Monitoring, Triage, and Escalation agents using ADK 2.0's mode='task' and mode='single_turn' delegation patterns.
- WHO danger-sign triage that scores symptoms against clinical protocols and triggers health worker alerts on red flags.
- A secure Cloud Run deployment with Secret Manager for credentials and authenticated-only access.
- Secret Mission: Add a Reminder Agent for ANC visit schedules, supplement tracking, and vaccination reminders.
Are there any prerequisites?
This project assumes you have Python 3.10+, uv, and the Antigravity CLI (agy) already installed. You'll also need a Google Cloud account with billing enabled and a Google AI Studio API key.
Set Up and Scaffold the Project
Your multi-agent maternity companion needs a proper project structure before you can start building individual agents. ADK 2.0 has a specific discovery contract: it looks for a root_agent inside an agent.py file, nested in a Python package that exports the agent module.
In this step, you'll scaffold the project with Agents CLI and set up the file structure that ADK expects. You'll also configure your API key and confirm the dev UI loads.
In this step, get ready to:
- Scaffold the project structure with Agents CLI.
- Create the agent package files for tools, danger signs, and agent logic.
- Confirm the ADK dev UI loads at localhost:8000.
Scaffold the project
The Agents CLI generates a ready-to-run project skeleton with boilerplate agent code, tests, and configuration. You'll use the --prototype flag to skip Terraform and CI/CD files you don't need yet.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project by running this command:
agents-cli create maternity-companion --prototype --yes
What does this command do?
This scaffolds a minimal ADK agent project at maternity-companion/ with boilerplate agent code, tests, and eval sets.
- --prototype skips Terraform and CI/CD infrastructure. Just agent code, tests, and eval sets.
- --yes auto-accepts defaults (ADK template, in-memory session storage) so you don't have to answer prompts.
- Move into the new project folder and install dependencies by running:
cd maternity-companion
agents-cli install
What does agents-cli install do?
This installs all Python dependencies via uv sync. It reads the project's pyproject.toml and installs everything your agent needs to run locally.
- Confirm the project folder exists by running:
ls
You should see files like pyproject.toml, agents-cli-manifest.yaml, and an app/ directory in the output.
Next, you'll set up environment variables so ADK can authenticate with the Gemini API.
- Create a .env file in the root of your maternity-companion folder by running:
echo "GOOGLE_API_KEY=your-api-key-here" > .env
- Open .env in your editor and replace your-api-key-here with your actual Google AI Studio API key.
Why a .env file?
ADK automatically loads environment variables from a .env file in your project root. No extra library like python-dotenv is needed.
The GOOGLE_API_KEY variable is what ADK uses to authenticate with the Gemini API when running your agents locally.
- Create a .gitignore file to keep secrets and generated files out of version control by running:
printf '.env\n__pycache__/\n*.pyc\n.venv/\n' > .gitignore
What are we excluding?
- .env keeps your API key out of Git.
- __pycache__/ and *.pyc are Python's compiled bytecode files. They're auto-generated and shouldn't be committed.
- .venv/ is the virtual environment directory. It's machine-specific and shouldn't be shared.
Scaffold command failing?
- Make sure you ran cd ~/Desktop first. The scaffold command creates a folder in your current directory.
- If you see a permissions error, check that agents-cli is available by running agents-cli --help in your terminal.
Need help troubleshooting? Help me fix my agents-cli scaffold error
Create the project file structure
ADK's discovery contract requires a Python package with an __init__.py that imports an agent module containing a root_agent. You'll create a maternity_companion package with placeholder files for agents, tools, and danger sign data.
- Create the maternity_companion package directory and its placeholder files by running:
mkdir -p maternity_companion
touch maternity_companion/__init__.py maternity_companion/agent.py maternity_companion/tools.py maternity_companion/danger_signs.py
- Confirm the files were created by running:
ls maternity_companion
You should see __init__.py, agent.py, tools.py, and danger_signs.py in the output.
Now you need to wire up the discovery contract and pin the ADK dependency.
- Open maternity_companion/__init__.py in your editor and add the following import:
from . import agent
Why this one-line import?
This is ADK's discovery contract. When you run adk web maternity_companion, ADK imports the package and looks for root_agent inside agent.py. Without this import, ADK can't find your agent.
- Create a requirements.txt file in the project root (alongside .env) with the pinned ADK version by running:
echo "google-adk==2.3.0" > requirements.txt
- Save all files.
Why pin the version?
ADK 2.3.0 is the version this project is built against. Pinning ensures anyone running your project gets identical behavior, even as newer ADK releases ship.
Time to verify that ADK can discover and load your agent package.
- Start the ADK dev UI by running this command from inside the maternity-companion project folder:
adk web maternity_companion
You should see the ADK Web Server start message in your terminal, including the line For local testing, access at http://localhost:8000.
- Open http://localhost:8000 in your browser.
- Confirm you see the ADK Web interface with your agent listed in the upper-left dropdown.
What should I see?
The dev UI is a chat interface for testing agents. Right now the agent is an empty placeholder, so it won't respond to messages yet. As long as the server starts and the UI loads, your discovery contract is working correctly.
- Press Ctrl+C in your terminal to stop the dev server when you're done verifying.
Dev UI not loading?
- Make sure you're running the command from the maternity-companion project root (the folder containing the maternity_companion/ package), not from inside maternity_companion/ itself.
- Check that maternity_companion/__init__.py contains exactly from . import agent. A typo here breaks the discovery contract.
- If port 8000 is already in use, try stopping other local servers first.
Still stuck? Help me get adk web working
✔️ Awesome, I've got everything!
Great. Double check that you've saved all your files before moving on.
ⓧ I'd like to double check the full code
Here's what each file should contain at this point:
from . import agent
GOOGLE_API_KEY=your-api-key-here
.env
__pycache__/
*.pyc
.venv/
google-adk==2.3.0
The files maternity_companion/agent.py, maternity_companion/tools.py, and maternity_companion/danger_signs.py should exist but be empty. You'll fill them in during the next steps.
Your project skeleton is in place and ADK can discover it. Next up, you'll build the Intake and Monitoring agents that onboard mothers and track symptoms.
Build the Intake and Monitoring Agents
Your project scaffold is ready and the dev UI confirmed everything is wired up correctly. Now it is time to build the first two agents that handle the conversational side of maternal care.
In this step, you will create an Intake Agent that onboards new mothers and a Monitoring Agent that runs periodic symptom check-ins. Both agents use ADK 2.0's task mode, which allows multi-turn conversations where the agent can ask follow-up questions before completing its job.
In this step, get ready to:
- Define the Intake Agent with a structured profile schema.
- Define the Monitoring Agent with a symptom report schema.
- Implement the tool functions both agents use to persist data.
Define the Intake Agent
The Intake Agent collects a mother's profile: gestational age, risk factors, location, gravida, and para. It uses mode='task' so it can ask clarifying questions across multiple turns before calling finish_task with a structured result.
- In maternity_companion/agent.py, add the module docstring, imports, the MotherProfile schema, and the intake_agent definition by pasting the following code:
"""Maternity Companion Agent - ADK 2.0 Collaborative Workflow."""
from google.adk import Agent
from pydantic import BaseModel, Field
from .tools import (
save_mother_profile,
get_mother_profile,
record_symptoms,
)
# --- Pydantic Schemas ---
class MotherProfile(BaseModel):
gestational_age_weeks: int = Field(description="Gestational age in weeks")
risk_factors: list[str] = Field(description="List of identified risk factors")
location: str = Field(description="Mother's location or nearest town")
gravida: int = Field(description="Number of pregnancies including current")
para: int = Field(description="Number of previous deliveries")
# --- Sub-Agents ---
intake_agent = Agent(
name="intake",
mode="task",
model="gemini-3-flash-preview",
output_schema=MotherProfile,
output_key="mother_profile",
instruction=(
"You are a maternal health intake agent. Collect the following from the mother:\n"
"1. Gestational age (how many weeks pregnant)\n"
"2. Risk factors (previous complications, chronic conditions, age under 18 or over 35)\n"
"3. Location (town or area for facility referral)\n"
"4. Gravida (number of pregnancies) and Para (number of deliveries)\n\n"
"Ask questions conversationally and warmly. Once you have all information, "
"call save_mother_profile to store it, then call finish_task with the structured profile."
),
description="Onboards a new mother by collecting gestational age, risk factors, and location.",
tools=[save_mother_profile],
)
What does this code do?
MotherProfile is a Pydantic schema that defines the structured data the Intake Agent must collect before it can finish its task.
The intake_agent uses mode='task' so it can hold a multi-turn conversation with the mother. The output_key='mother_profile' stores the result in session state, making it available to other agents later.
ADK auto-injects finish_task into the agent's context. The agent calls it once it has gathered all profile fields.
You won't be able to run this file yet because the tools module is still empty. You will implement those functions in substep 2.3.
Define the Monitoring Agent
The Monitoring Agent performs symptom check-ins. It also uses mode='task' because it needs to ask follow-up questions about the severity and nature of each symptom.
- Still in maternity_companion/agent.py, add the SymptomReport schema and monitoring_agent definition below the intake_agent code you just pasted:
class SymptomReport(BaseModel):
symptoms: list[str] = Field(description="List of reported symptoms")
severity: str = Field(description="Overall severity: mild, moderate, severe")
gestational_age_weeks: int = Field(description="Current gestational age")
monitoring_agent = Agent(
name="monitoring",
mode="task",
model="gemini-3-flash-preview",
output_schema=SymptomReport,
output_key="symptom_report",
instruction=(
"You are a maternal health monitoring agent. Perform a symptom check-in.\n"
"Ask about: bleeding, headaches, swelling, fever, abdominal pain, "
"baby movement, and any other concerns.\n\n"
"Be warm and reassuring. After collecting symptoms, call record_symptoms "
"to store them, then call finish_task with the structured report."
),
description="Performs periodic symptom check-ins and tracks symptoms over time.",
tools=[record_symptoms, get_mother_profile],
)
What does this code do?
SymptomReport defines the structured output the Monitoring Agent produces after each check-in.
The monitoring_agent has two tools: record_symptoms to persist the data, and get_mother_profile to read the mother's context from session state. This lets it tailor questions based on known risk factors.
Why mode='task' for both agents?
ADK 2.0 offers three delegation modes: chat, task, and single_turn.
The task mode lets an agent ask the user clarifying questions before calling finish_task. Intake and Monitoring are conversational by nature. They need to probe, confirm, and follow up before producing a final structured report.
In Step 3 you will build Triage and Escalation agents that use single_turn instead, because those agents act autonomously with no user interaction.
You still cannot run the file because the imported tool functions do not exist yet. That is next.
Implement the Intake and Monitoring tools
ADK automatically wraps plain Python functions as tools using their docstrings and type hints. You do not need any decorator or special registration. The function signature becomes the tool schema that the LLM sees.
- First, open maternity_companion/danger_signs.py and add a placeholder that the tools module will import:
"""WHO IMPAC maternal danger signs for pregnancy risk assessment."""
WHO_DANGER_SIGNS = []
Why add an empty list?
The tools module imports WHO_DANGER_SIGNS so the file can be loaded without errors. You will populate this list with the full WHO danger sign dataset in Step 3.
- Save danger_signs.py.
- Now open maternity_companion/tools.py and paste the following code:
"""Tool functions for the maternity companion agent system."""
from .danger_signs import WHO_DANGER_SIGNS
def save_mother_profile(
gestational_age_weeks: int,
risk_factors: str,
location: str,
gravida: int,
para: int,
) -> dict:
"""Save the mother's profile to the session for future reference.
Args:
gestational_age_weeks: How many weeks pregnant the mother is.
risk_factors: Comma-separated list of risk factors.
location: Mother's location or nearest town.
gravida: Number of pregnancies including current.
para: Number of previous deliveries.
"""
profile = {
"gestational_age_weeks": gestational_age_weeks,
"risk_factors": [r.strip() for r in risk_factors.split(",")],
"location": location,
"gravida": gravida,
"para": para,
}
return {"status": "saved", "profile": profile}
def get_mother_profile() -> dict:
"""Retrieve the mother's stored profile from the session."""
return {"status": "retrieved", "message": "Profile available in session state under mother_profile key."}
def record_symptoms(symptoms: str, severity: str) -> dict:
"""Record the mother's reported symptoms for this check-in.
Args:
symptoms: Comma-separated list of symptoms reported.
severity: Overall severity assessment: mild, moderate, or severe.
"""
symptom_list = [s.strip().lower() for s in symptoms.split(",")]
return {
"status": "recorded",
"symptoms": symptom_list,
"severity": severity,
"count": len(symptom_list),
}
What does this code do?
- save_mother_profile takes typed parameters and returns a dictionary. ADK reads the function signature and docstring to generate the tool schema the LLM uses.
- get_mother_profile is a simple retrieval stub. The actual session state is managed by ADK's output_key mechanism, but this tool gives the monitoring agent a way to signal it wants the profile data.
- record_symptoms normalizes the symptom text (lowercase, stripped) so the triage agent can match keywords reliably later.
- Save tools.py.
- Verify the tools module imports cleanly by running this command:
python -c "from maternity_companion.tools import save_mother_profile, record_symptoms; print('OK')"
- You should see OK printed with no errors.
Seeing an ImportError?
- Make sure you saved danger_signs.py with the WHO_DANGER_SIGNS = [] placeholder. Without it, the import from tools.py will fail.
- Check that you are running the command from the maternity-companion directory (the parent of maternity_companion/).
- Confirm that __init__.py contains from . import agent. This is needed for the ADK discovery contract.
Help me debug an ImportError in my ADK maternity companion tools module.
✔️ Awesome, I've got everything!
Great. Double check you have saved both agent.py and tools.py.
ⓧ I'd like to double check the full code
Here is the full maternity_companion/agent.py at this point:
"""Maternity Companion Agent - ADK 2.0 Collaborative Workflow."""
from google.adk import Agent
from pydantic import BaseModel, Field
from .tools import (
save_mother_profile,
get_mother_profile,
record_symptoms,
)
# --- Pydantic Schemas ---
class MotherProfile(BaseModel):
gestational_age_weeks: int = Field(description="Gestational age in weeks")
risk_factors: list[str] = Field(description="List of identified risk factors")
location: str = Field(description="Mother's location or nearest town")
gravida: int = Field(description="Number of pregnancies including current")
para: int = Field(description="Number of previous deliveries")
class SymptomReport(BaseModel):
symptoms: list[str] = Field(description="List of reported symptoms")
severity: str = Field(description="Overall severity: mild, moderate, severe")
gestational_age_weeks: int = Field(description="Current gestational age")
# --- Sub-Agents ---
intake_agent = Agent(
name="intake",
mode="task",
model="gemini-3-flash-preview",
output_schema=MotherProfile,
output_key="mother_profile",
instruction=(
"You are a maternal health intake agent. Collect the following from the mother:\n"
"1. Gestational age (how many weeks pregnant)\n"
"2. Risk factors (previous complications, chronic conditions, age under 18 or over 35)\n"
"3. Location (town or area for facility referral)\n"
"4. Gravida (number of pregnancies) and Para (number of deliveries)\n\n"
"Ask questions conversationally and warmly. Once you have all information, "
"call save_mother_profile to store it, then call finish_task with the structured profile."
),
description="Onboards a new mother by collecting gestational age, risk factors, and location.",
tools=[save_mother_profile],
)
monitoring_agent = Agent(
name="monitoring",
mode="task",
model="gemini-3-flash-preview",
output_schema=SymptomReport,
output_key="symptom_report",
instruction=(
"You are a maternal health monitoring agent. Perform a symptom check-in.\n"
"Ask about: bleeding, headaches, swelling, fever, abdominal pain, "
"baby movement, and any other concerns.\n\n"
"Be warm and reassuring. After collecting symptoms, call record_symptoms "
"to store them, then call finish_task with the structured report."
),
description="Performs periodic symptom check-ins and tracks symptoms over time.",
tools=[record_symptoms, get_mother_profile],
)
And here is the full maternity_companion/tools.py:
"""Tool functions for the maternity companion agent system."""
from .danger_signs import WHO_DANGER_SIGNS
def save_mother_profile(
gestational_age_weeks: int,
risk_factors: str,
location: str,
gravida: int,
para: int,
) -> dict:
"""Save the mother's profile to the session for future reference.
Args:
gestational_age_weeks: How many weeks pregnant the mother is.
risk_factors: Comma-separated list of risk factors.
location: Mother's location or nearest town.
gravida: Number of pregnancies including current.
para: Number of previous deliveries.
"""
profile = {
"gestational_age_weeks": gestational_age_weeks,
"risk_factors": [r.strip() for r in risk_factors.split(",")],
"location": location,
"gravida": gravida,
"para": para,
}
return {"status": "saved", "profile": profile}
def get_mother_profile() -> dict:
"""Retrieve the mother's stored profile from the session."""
return {"status": "retrieved", "message": "Profile available in session state under mother_profile key."}
def record_symptoms(symptoms: str, severity: str) -> dict:
"""Record the mother's reported symptoms for this check-in.
Args:
symptoms: Comma-separated list of symptoms reported.
severity: Overall severity assessment: mild, moderate, or severe.
"""
symptom_list = [s.strip().lower() for s in symptoms.split(",")]
return {
"status": "recorded",
"symptoms": symptom_list,
"severity": severity,
"count": len(symptom_list),
}
And maternity_companion/danger_signs.py:
"""WHO IMPAC maternal danger signs for pregnancy risk assessment."""
WHO_DANGER_SIGNS = []
Your first two agents are defined and their tools are working. Next up, you will build the Risk Triage and Escalation agents that score symptoms against WHO danger signs and alert health workers when red flags appear.
Build Risk Triage and Escalation Agents
Your Intake and Monitoring agents can now collect mother profiles and track symptoms. But collecting symptoms is only useful if the system can identify when those symptoms are dangerous.
In this step, you will build the safety layer. A Risk Triage agent will score symptoms against WHO danger-sign protocols, and an Escalation agent will alert a health worker when something is urgent.
In this step, get ready to:
- Define the WHO danger signs dataset for keyword-based symptom matching.
- Build a Triage agent that classifies risk as green, yellow, or red.
- Build an Escalation agent that alerts health workers on red flags.
Create the WHO danger signs dataset
The Triage agent needs a reference list of clinically dangerous symptoms to match against. The WHO IMPAC guidelines define specific danger signs during pregnancy that require immediate action.
Each entry in the dataset includes a name, keywords for matching, a severity level, and a recommended clinical action.
- Open maternity_companion/danger_signs.py and add the following WHO danger signs data:
"""WHO IMPAC maternal danger signs for pregnancy risk assessment."""
WHO_DANGER_SIGNS = [
{
"name": "Vaginal bleeding",
"keywords": ["bleeding", "blood", "spotting", "hemorrhage"],
"severity": "critical",
"action": "Immediate referral to EmONC facility",
},
{
"name": "Severe headache with blurred vision",
"keywords": ["headache", "blurred vision", "visual disturbance", "seeing spots"],
"severity": "critical",
"action": "Check blood pressure, assess for pre-eclampsia",
},
{
"name": "Convulsions",
"keywords": ["convulsion", "seizure", "fit", "fits", "shaking"],
"severity": "critical",
"action": "Eclampsia protocol, immediate magnesium sulfate",
},
{
"name": "Severe abdominal pain",
"keywords": ["severe pain", "abdominal pain", "stomach pain", "cramping"],
"severity": "critical",
"action": "Assess for placental abruption or ruptured uterus",
},
{
"name": "High fever",
"keywords": ["fever", "high temperature", "chills", "shivering"],
"severity": "high",
"action": "Assess for infection, malaria, or sepsis",
},
{
"name": "Swelling of face and hands",
"keywords": ["swelling", "swollen", "edema", "puffy face", "swollen hands"],
"severity": "high",
"action": "Check blood pressure, assess for pre-eclampsia",
},
{
"name": "Reduced or absent fetal movement",
"keywords": ["not moving", "no movement", "less movement", "reduced movement", "baby quiet"],
"severity": "high",
"action": "Fetal monitoring, count movements over 2 hours",
},
{
"name": "Premature rupture of membranes",
"keywords": ["water broke", "water breaking", "leaking fluid", "membranes", "waters"],
"severity": "critical",
"action": "Assess gestational age, risk of preterm labor",
},
{
"name": "Difficulty breathing",
"keywords": ["breathing", "breathless", "shortness of breath", "cannot breathe"],
"severity": "high",
"action": "Assess for cardiac or respiratory complications",
},
]
What does this code do?
This creates a structured lookup table of 9 WHO-recognized danger signs for pregnancy. Each entry has:
- name: the clinical name of the danger sign.
- keywords: words a mother might use when reporting the symptom.
- severity: either critical (immediate referral) or high (urgent monitoring).
- action: the recommended clinical response.
Why keyword matching instead of LLM classification?
For safety-critical decisions like danger-sign detection, a deterministic rules engine is more reliable than letting the LLM decide. Keywords ensure consistent, auditable triage regardless of how the model interprets language.
The LLM orchestrates the flow (deciding when to triage), but the actual scoring logic is hard-coded.
- Save danger_signs.py.
You won't see a visible change yet. The next chunk uses this data in a tool function that the Triage agent will call.
✔️ Awesome, I've got everything!
Great. Make sure you saved the file before continuing.
ⓧ I'd like to double check the full code
"""WHO IMPAC maternal danger signs for pregnancy risk assessment."""
WHO_DANGER_SIGNS = [
{
"name": "Vaginal bleeding",
"keywords": ["bleeding", "blood", "spotting", "hemorrhage"],
"severity": "critical",
"action": "Immediate referral to EmONC facility",
},
{
"name": "Severe headache with blurred vision",
"keywords": ["headache", "blurred vision", "visual disturbance", "seeing spots"],
"severity": "critical",
"action": "Check blood pressure, assess for pre-eclampsia",
},
{
"name": "Convulsions",
"keywords": ["convulsion", "seizure", "fit", "fits", "shaking"],
"severity": "critical",
"action": "Eclampsia protocol, immediate magnesium sulfate",
},
{
"name": "Severe abdominal pain",
"keywords": ["severe pain", "abdominal pain", "stomach pain", "cramping"],
"severity": "critical",
"action": "Assess for placental abruption or ruptured uterus",
},
{
"name": "High fever",
"keywords": ["fever", "high temperature", "chills", "shivering"],
"severity": "high",
"action": "Assess for infection, malaria, or sepsis",
},
{
"name": "Swelling of face and hands",
"keywords": ["swelling", "swollen", "edema", "puffy face", "swollen hands"],
"severity": "high",
"action": "Check blood pressure, assess for pre-eclampsia",
},
{
"name": "Reduced or absent fetal movement",
"keywords": ["not moving", "no movement", "less movement", "reduced movement", "baby quiet"],
"severity": "high",
"action": "Fetal monitoring, count movements over 2 hours",
},
{
"name": "Premature rupture of membranes",
"keywords": ["water broke", "water breaking", "leaking fluid", "membranes", "waters"],
"severity": "critical",
"action": "Assess gestational age, risk of preterm labor",
},
{
"name": "Difficulty breathing",
"keywords": ["breathing", "breathless", "shortness of breath", "cannot breathe"],
"severity": "high",
"action": "Assess for cardiac or respiratory complications",
},
]
Build the Risk Triage agent
The Triage agent receives symptom data from the Monitoring agent and scores it against the WHO danger signs. It does not interact with the user. It runs autonomously, produces a risk classification, and hands the result back to the coordinator.
- In maternity_companion/tools.py, add the assess_danger_signs function after the existing record_symptoms function:
def assess_danger_signs(symptoms: str) -> dict:
"""Assess reported symptoms against WHO maternal danger signs protocol.
Args:
symptoms: Comma-separated list of symptoms to assess.
"""
symptom_list = [s.strip().lower() for s in symptoms.split(",")]
detected = []
for symptom in symptom_list:
for sign in WHO_DANGER_SIGNS:
if any(keyword in symptom for keyword in sign["keywords"]):
detected.append(sign["name"])
break
if detected:
risk_level = "red"
recommendation = (
f"URGENT: {len(detected)} WHO danger sign(s) detected: "
f"{', '.join(detected)}. Immediate referral to health facility required."
)
elif any(word in " ".join(symptom_list) for word in ["mild", "occasional", "slight"]):
risk_level = "green"
recommendation = "No danger signs detected. Continue routine antenatal care."
else:
risk_level = "yellow"
recommendation = (
"No WHO danger signs confirmed, but symptoms warrant increased monitoring. "
"Schedule follow-up within 48 hours."
)
return {
"risk_level": risk_level,
"danger_signs_detected": detected,
"recommendation": recommendation,
}
What does this code do?
This function takes a comma-separated symptoms string and checks each symptom against every entry in the WHO_DANGER_SIGNS list using keyword matching.
- If any keywords match, the risk level is red and the function returns an urgent referral recommendation.
- If no keywords match but the symptoms contain words like "mild" or "slight", the risk is green (routine care).
- Everything else defaults to yellow (increased monitoring, follow-up within 48 hours).
- Save tools.py.
- Verify the function works by running this command in your terminal:
python -c "from maternity_companion.tools import assess_danger_signs; print(assess_danger_signs('severe headache, blurred vision'))"
You should see output containing 'risk_level': 'red' and 'Severe headache with blurred vision' in the detected danger signs list.
Seeing an ImportError?
- Make sure you saved danger_signs.py first. The tools.py file imports WHO_DANGER_SIGNS from it. If danger_signs.py is empty, the import fails.
- Check that you are running the command from the project root (the folder containing the maternity_companion directory).
Why is my assess_danger_signs function throwing an error?
Now that the tool function is ready, you can define the Triage agent that uses it.
- In maternity_companion/agent.py, find the import block at the top of the file:
from .tools import (
save_mother_profile,
get_mother_profile,
record_symptoms,
)
- Add assess_danger_signs to the import so it looks like this:
from .tools import (
save_mother_profile,
get_mother_profile,
record_symptoms,
assess_danger_signs,
)
- Next, add the TriageResult schema and triage_agent definition after the monitoring_agent block:
class TriageResult(BaseModel):
risk_level: str = Field(description="Risk level: green, yellow, red")
danger_signs_detected: list[str] = Field(description="WHO danger signs found")
recommendation: str = Field(description="Clinical recommendation")
triage_agent = Agent(
name="triage",
mode="single_turn",
model="gemini-3-flash-preview",
output_schema=TriageResult,
output_key="triage_result",
instruction=(
"You are a risk triage agent for maternal health. Analyze the symptom report "
"provided and score it against WHO danger signs.\n\n"
"Call assess_danger_signs with the symptoms to get a clinical assessment.\n"
"Based on the result, classify risk as:\n"
"- GREEN: no danger signs, routine care\n"
"- YELLOW: warning signs, increased monitoring needed\n"
"- RED: danger signs present, immediate escalation required\n\n"
"Call finish_task with the triage result."
),
description="Scores symptoms against WHO danger-sign protocols and classifies risk level.",
tools=[assess_danger_signs],
)
Why single_turn mode for triage?
The mode='single_turn' setting means this agent runs autonomously without any user interaction. It receives input, calls its tool, produces a result, and finishes.
This is the right choice for triage because scoring symptoms is a deterministic step. The agent should never ask the user clarifying questions during risk classification.
- Save agent.py.
You won't see a change yet. The Triage agent will be wired into the coordinator in the next step.
Build the Escalation agent
When the Triage agent returns a red risk level, someone needs to act. The Escalation agent finds the nearest health facility and sends an emergency alert to a community health worker.
- In maternity_companion/tools.py, add the lookup_nearest_facility function after assess_danger_signs:
def lookup_nearest_facility(location: str) -> dict:
"""Look up the nearest health facility based on the mother's location.
Args:
location: The mother's location or nearest town.
"""
# Simulated facility database (swappable with a real API later)
facilities = {
"default": {
"name": "District General Hospital",
"distance_km": 5,
"phone": "+254-700-000-000",
"services": ["EmONC", "blood bank", "surgery"],
}
}
facility = facilities.get(location.lower(), facilities["default"])
facility["location_queried"] = location
return facility
What does this code do?
This function simulates a facility lookup. In a production system, this would query a real database or MCP integration. For now, it returns a default hospital with phone number and available services.
The function signature (location string in, facility dict out) is designed to be swappable with a real implementation later without changing the agent's code.
- Save tools.py and add the send_escalation_alert function below lookup_nearest_facility:
def send_escalation_alert(
danger_signs: str,
facility_name: str,
mother_location: str,
gestational_age_weeks: int,
) -> dict:
"""Send an emergency alert to the nearest health facility and community health worker.
Args:
danger_signs: Comma-separated list of detected danger signs.
facility_name: Name of the facility to alert.
mother_location: Mother's current location.
gestational_age_weeks: Current gestational age in weeks.
"""
# Format the emergency alert message
alert_message = (
f"MATERNAL EMERGENCY ALERT\n"
f"Location: {mother_location}\n"
f"Gestational age: {gestational_age_weeks} weeks\n"
f"Danger signs: {danger_signs}\n"
f"Nearest facility: {facility_name}\n"
f"Action required: Immediate assessment and possible referral."
)
return {
"alert_sent": True,
"message": alert_message,
"channel": "simulated_sms",
"recipient": "community_health_worker",
}
What does this code do?
This function formats and "sends" an emergency alert. It takes the detected danger signs, facility information, and mother's details to construct a structured alert message.
The channel field is set to simulated_sms for now. In production, you would swap this for a real SMS API or messaging integration.
- Save tools.py.
Now define the Escalation agent that uses these two tools.
- In maternity_companion/agent.py, find the import block at the top. Update it to include the two new escalation tools:
from .tools import (
save_mother_profile,
get_mother_profile,
record_symptoms,
assess_danger_signs,
send_escalation_alert,
lookup_nearest_facility,
)
- Add the EscalationResult schema and escalation_agent definition after the triage_agent block:
class EscalationResult(BaseModel):
alert_sent: bool = Field(description="Whether an alert was successfully sent")
facility_name: str = Field(description="Name of nearest facility")
message: str = Field(description="Alert message sent to health worker")
escalation_agent = Agent(
name="escalation",
mode="single_turn",
model="gemini-3-flash-preview",
output_schema=EscalationResult,
output_key="escalation_result",
instruction=(
"You are an escalation agent. A RED risk level has been detected.\n"
"1. Call lookup_nearest_facility with the mother's location to find the closest health facility.\n"
"2. Call send_escalation_alert with the danger signs, facility info, and mother profile.\n"
"3. Call finish_task confirming the alert was sent.\n\n"
"Act immediately. Do not engage in conversation."
),
description="On red flags, alerts a human health worker and identifies nearest facility.",
tools=[send_escalation_alert, lookup_nearest_facility],
)
What does this code do?
The Escalation agent uses mode='single_turn' just like the Triage agent. It fires immediately when invoked, looks up the nearest facility, sends an alert, and finishes.
Its instruction is deliberately terse ("Act immediately. Do not engage in conversation.") to prevent the LLM from adding unnecessary dialogue before acting on an emergency.
- Save agent.py.
- Verify that the full tools module imports cleanly by running:
python -c "from maternity_companion.tools import assess_danger_signs, lookup_nearest_facility, send_escalation_alert; print('All tools OK')"
You should see All tools OK printed with no errors.
Seeing a ModuleNotFoundError?
- Make sure you are running the command from the project root directory (the folder that contains the maternity_companion directory), not from inside maternity_companion/ itself.
- Confirm that __init__.py exists inside maternity_companion/ and contains from . import agent.
- Check for typos in the function names in your import statement at the top of agent.py.
Help me fix this ModuleNotFoundError when importing my maternity_companion tools.
✔️ Awesome, I've got everything!
Great. Double check you have saved both tools.py and agent.py before continuing.
ⓧ I'd like to double check the full code
"""Tool functions for the maternity companion agent system."""
from .danger_signs import WHO_DANGER_SIGNS
def save_mother_profile(
gestational_age_weeks: int,
risk_factors: str,
location: str,
gravida: int,
para: int,
) -> dict:
"""Save the mother's profile to the session for future reference.
Args:
gestational_age_weeks: How many weeks pregnant the mother is.
risk_factors: Comma-separated list of risk factors.
location: Mother's location or nearest town.
gravida: Number of pregnancies including current.
para: Number of previous deliveries.
"""
profile = {
"gestational_age_weeks": gestational_age_weeks,
"risk_factors": [r.strip() for r in risk_factors.split(",")],
"location": location,
"gravida": gravida,
"para": para,
}
return {"status": "saved", "profile": profile}
def get_mother_profile() -> dict:
"""Retrieve the mother's stored profile from the session."""
return {"status": "retrieved", "message": "Profile available in session state under mother_profile key."}
def record_symptoms(symptoms: str, severity: str) -> dict:
"""Record the mother's reported symptoms for this check-in.
Args:
symptoms: Comma-separated list of symptoms reported.
severity: Overall severity assessment: mild, moderate, or severe.
"""
symptom_list = [s.strip().lower() for s in symptoms.split(",")]
return {
"status": "recorded",
"symptoms": symptom_list,
"severity": severity,
"count": len(symptom_list),
}
def assess_danger_signs(symptoms: str) -> dict:
"""Assess reported symptoms against WHO maternal danger signs protocol.
Args:
symptoms: Comma-separated list of symptoms to assess.
"""
symptom_list = [s.strip().lower() for s in symptoms.split(",")]
detected = []
for symptom in symptom_list:
for sign in WHO_DANGER_SIGNS:
if any(keyword in symptom for keyword in sign["keywords"]):
detected.append(sign["name"])
break
if detected:
risk_level = "red"
recommendation = (
f"URGENT: {len(detected)} WHO danger sign(s) detected: "
f"{', '.join(detected)}. Immediate referral to health facility required."
)
elif any(word in " ".join(symptom_list) for word in ["mild", "occasional", "slight"]):
risk_level = "green"
recommendation = "No danger signs detected. Continue routine antenatal care."
else:
risk_level = "yellow"
recommendation = (
"No WHO danger signs confirmed, but symptoms warrant increased monitoring. "
"Schedule follow-up within 48 hours."
)
return {
"risk_level": risk_level,
"danger_signs_detected": detected,
"recommendation": recommendation,
}
def lookup_nearest_facility(location: str) -> dict:
"""Look up the nearest health facility based on the mother's location.
Args:
location: The mother's location or nearest town.
"""
facilities = {
"default": {
"name": "District General Hospital",
"distance_km": 5,
"phone": "+254-700-000-000",
"services": ["EmONC", "blood bank", "surgery"],
}
}
facility = facilities.get(location.lower(), facilities["default"])
facility["location_queried"] = location
return facility
def send_escalation_alert(
danger_signs: str,
facility_name: str,
mother_location: str,
gestational_age_weeks: int,
) -> dict:
"""Send an emergency alert to the nearest health facility and community health worker.
Args:
danger_signs: Comma-separated list of detected danger signs.
facility_name: Name of the facility to alert.
mother_location: Mother's current location.
gestational_age_weeks: Current gestational age in weeks.
"""
alert_message = (
f"MATERNAL EMERGENCY ALERT\n"
f"Location: {mother_location}\n"
f"Gestational age: {gestational_age_weeks} weeks\n"
f"Danger signs: {danger_signs}\n"
f"Nearest facility: {facility_name}\n"
f"Action required: Immediate assessment and possible referral."
)
return {
"alert_sent": True,
"message": alert_message,
"channel": "simulated_sms",
"recipient": "community_health_worker",
}
"""Maternity Companion Agent - ADK 2.0 Collaborative Workflow."""
from google.adk import Agent
from pydantic import BaseModel, Field
from .tools import (
save_mother_profile,
get_mother_profile,
record_symptoms,
assess_danger_signs,
send_escalation_alert,
lookup_nearest_facility,
)
# --- Pydantic Schemas ---
class MotherProfile(BaseModel):
gestational_age_weeks: int = Field(description="Gestational age in weeks")
risk_factors: list[str] = Field(description="List of identified risk factors")
location: str = Field(description="Mother's location or nearest town")
gravida: int = Field(description="Number of pregnancies including current")
para: int = Field(description="Number of previous deliveries")
class SymptomReport(BaseModel):
symptoms: list[str] = Field(description="List of reported symptoms")
severity: str = Field(description="Overall severity: mild, moderate, severe")
gestational_age_weeks: int = Field(description="Current gestational age")
class TriageResult(BaseModel):
risk_level: str = Field(description="Risk level: green, yellow, red")
danger_signs_detected: list[str] = Field(description="WHO danger signs found")
recommendation: str = Field(description="Clinical recommendation")
class EscalationResult(BaseModel):
alert_sent: bool = Field(description="Whether an alert was successfully sent")
facility_name: str = Field(description="Name of nearest facility")
message: str = Field(description="Alert message sent to health worker")
# --- Sub-Agents ---
intake_agent = Agent(
name="intake",
mode="task",
model="gemini-3-flash-preview",
output_schema=MotherProfile,
output_key="mother_profile",
instruction=(
"You are a maternal health intake agent. Collect the following from the mother:\n"
"1. Gestational age (how many weeks pregnant)\n"
"2. Risk factors (previous complications, chronic conditions, age under 18 or over 35)\n"
"3. Location (town or area for facility referral)\n"
"4. Gravida (number of pregnancies) and Para (number of deliveries)\n\n"
"Ask questions conversationally and warmly. Once you have all information, "
"call save_mother_profile to store it, then call finish_task with the structured profile."
),
description="Onboards a new mother by collecting gestational age, risk factors, and location.",
tools=[save_mother_profile],
)
monitoring_agent = Agent(
name="monitoring",
mode="task",
model="gemini-3-flash-preview",
output_schema=SymptomReport,
output_key="symptom_report",
instruction=(
"You are a maternal health monitoring agent. Perform a symptom check-in.\n"
"Ask about: bleeding, headaches, swelling, fever, abdominal pain, "
"baby movement, and any other concerns.\n\n"
"Be warm and reassuring. After collecting symptoms, call record_symptoms "
"to store them, then call finish_task with the structured report."
),
description="Performs periodic symptom check-ins and tracks symptoms over time.",
tools=[record_symptoms, get_mother_profile],
)
triage_agent = Agent(
name="triage",
mode="single_turn",
model="gemini-3-flash-preview",
output_schema=TriageResult,
output_key="triage_result",
instruction=(
"You are a risk triage agent for maternal health. Analyze the symptom report "
"provided and score it against WHO danger signs.\n\n"
"Call assess_danger_signs with the symptoms to get a clinical assessment.\n"
"Based on the result, classify risk as:\n"
"- GREEN: no danger signs, routine care\n"
"- YELLOW: warning signs, increased monitoring needed\n"
"- RED: danger signs present, immediate escalation required\n\n"
"Call finish_task with the triage result."
),
description="Scores symptoms against WHO danger-sign protocols and classifies risk level.",
tools=[assess_danger_signs],
)
escalation_agent = Agent(
name="escalation",
mode="single_turn",
model="gemini-3-flash-preview",
output_schema=EscalationResult,
output_key="escalation_result",
instruction=(
"You are an escalation agent. A RED risk level has been detected.\n"
"1. Call lookup_nearest_facility with the mother's location to find the closest health facility.\n"
"2. Call send_escalation_alert with the danger signs, facility info, and mother profile.\n"
"3. Call finish_task confirming the alert was sent.\n\n"
"Act immediately. Do not engage in conversation."
),
description="On red flags, alerts a human health worker and identifies nearest facility.",
tools=[send_escalation_alert, lookup_nearest_facility],
)
Your safety layer is complete. You now have four specialized agents: Intake and Monitoring for conversation, Triage and Escalation for autonomous action. Next up, you will wire them all together under a single Coordinator agent and test the full flow.
Wire the Coordinator and Test Locally
You now have four specialized agents that each know how to handle one part of the maternity care workflow. The problem is that they have no way to talk to each other or decide who should respond to a given message.
In this step, you will build the Coordinator agent that ties everything together. The Coordinator uses ADK 2.0's collaborative workflow pattern: you list sub-agents, and ADK auto-generates delegation tools like request_task_intake that the Coordinator's LLM can call to route conversations to the right specialist.
In this step, get ready to:
- Define the Coordinator agent with routing rules for all four sub-agents.
- Test the full multi-agent flow in the ADK dev UI.
- Verify agent routing with the ADK CLI in the terminal.
Build the Coordinator agent
The Coordinator is the single entry point for all user messages. Its instruction tells the LLM how to route: new mothers go to intake, symptom reports go to monitoring then triage, and red risk levels trigger escalation.
- In maternity_companion/agent.py, scroll to the very bottom of the file (after escalation_agent).
- Add the Coordinator agent by pasting the following code at the end of the file:
# --- Coordinator Agent ---
root_agent = Agent(
name="maternity_coordinator",
model="gemini-3-flash-preview",
sub_agents=[intake_agent, monitoring_agent, triage_agent, escalation_agent],
instruction=(
"You are the Maternity Companion coordinator. You manage a team of specialist agents "
"to support pregnant mothers throughout their pregnancy.\n\n"
"Routing rules:\n"
"- If the mother is new or has not been onboarded, delegate to intake "
"using request_task_intake.\n"
"- If the mother reports symptoms or it is time for a check-in, delegate to monitoring "
"using request_task_monitoring.\n"
"- After monitoring returns a symptom report, ALWAYS delegate to triage "
"using request_task_triage to assess risk.\n"
"- If triage returns risk_level red, IMMEDIATELY delegate to escalation "
"using request_task_escalation.\n"
"- If triage returns green or yellow, reassure the mother and provide guidance.\n\n"
"Always be warm, supportive, and culturally sensitive. Address the mother by name "
"if known. Never provide specific medical diagnoses. Your role is to connect "
"mothers with appropriate care."
),
description="Central coordinator for the maternity companion system.",
)
What does this code do?
The root_agent variable is what ADK looks for when it loads your agent directory. This is the discovery contract defined in __init__.py.
The sub_agents list is the key piece. ADK automatically generates delegation tools named request_task_intake, request_task_monitoring, request_task_triage, and request_task_escalation based on each sub-agent's name field. The Coordinator's LLM uses the instruction to decide which tool to call for each incoming message.
Why a collaborative workflow instead of a graph?
ADK 2.0 offers two multi-agent patterns: graph-based Workflows (fixed execution paths) and collaborative workflows (LLM-driven routing). A maternity companion needs dynamic routing because the next agent depends on conversational context, not a predetermined sequence.
The collaborative pattern lets the Coordinator's LLM reason about which specialist to call. A graph workflow would force a rigid path that cannot adapt when a mother changes topic mid-conversation.
- Save maternity_companion/agent.py.
Seeing an import error?
Make sure the root_agent definition is placed after all four sub-agent definitions. Python reads top to bottom, so the sub-agents must exist before you reference them in the sub_agents list.
Also confirm that your __init__.py still contains from . import agent. ADK needs this to discover your agent module.
help me debug an import error in my ADK agent
✔️ Awesome, I've got everything!
Great. Double check you have saved agent.py before moving on to testing.
ⓧ I'd like to double check the full code
Here is the complete maternity_companion/agent.py file with all schemas, sub-agents, and the Coordinator:
"""Maternity Companion Agent - ADK 2.0 Collaborative Workflow."""
from google.adk import Agent
from pydantic import BaseModel, Field
from .tools import (
save_mother_profile,
get_mother_profile,
record_symptoms,
assess_danger_signs,
send_escalation_alert,
lookup_nearest_facility,
)
# --- Pydantic Schemas ---
class MotherProfile(BaseModel):
gestational_age_weeks: int = Field(description="Gestational age in weeks")
risk_factors: list[str] = Field(description="List of identified risk factors")
location: str = Field(description="Mother's location or nearest town")
gravida: int = Field(description="Number of pregnancies including current")
para: int = Field(description="Number of previous deliveries")
class SymptomReport(BaseModel):
symptoms: list[str] = Field(description="List of reported symptoms")
severity: str = Field(description="Overall severity: mild, moderate, severe")
gestational_age_weeks: int = Field(description="Current gestational age")
class TriageResult(BaseModel):
risk_level: str = Field(description="Risk level: green, yellow, red")
danger_signs_detected: list[str] = Field(description="WHO danger signs found")
recommendation: str = Field(description="Clinical recommendation")
class EscalationResult(BaseModel):
alert_sent: bool = Field(description="Whether an alert was successfully sent")
facility_name: str = Field(description="Name of nearest facility")
message: str = Field(description="Alert message sent to health worker")
# --- Sub-Agents ---
intake_agent = Agent(
name="intake",
mode="task",
model="gemini-3-flash-preview",
output_schema=MotherProfile,
output_key="mother_profile",
instruction=(
"You are a maternal health intake agent. Collect the following from the mother:\n"
"1. Gestational age (how many weeks pregnant)\n"
"2. Risk factors (previous complications, chronic conditions, age under 18 or over 35)\n"
"3. Location (town or area for facility referral)\n"
"4. Gravida (number of pregnancies) and Para (number of deliveries)\n\n"
"Ask questions conversationally and warmly. Once you have all information, "
"call save_mother_profile to store it, then call finish_task with the structured profile."
),
description="Onboards a new mother by collecting gestational age, risk factors, and location.",
tools=[save_mother_profile],
)
monitoring_agent = Agent(
name="monitoring",
mode="task",
model="gemini-3-flash-preview",
output_schema=SymptomReport,
output_key="symptom_report",
instruction=(
"You are a maternal health monitoring agent. Perform a symptom check-in.\n"
"Ask about: bleeding, headaches, swelling, fever, abdominal pain, "
"baby movement, and any other concerns.\n\n"
"Be warm and reassuring. After collecting symptoms, call record_symptoms "
"to store them, then call finish_task with the structured report."
),
description="Performs periodic symptom check-ins and tracks symptoms over time.",
tools=[record_symptoms, get_mother_profile],
)
triage_agent = Agent(
name="triage",
mode="single_turn",
model="gemini-3-flash-preview",
output_schema=TriageResult,
output_key="triage_result",
instruction=(
"You are a risk triage agent for maternal health. Analyze the symptom report "
"provided and score it against WHO danger signs.\n\n"
"Call assess_danger_signs with the symptoms to get a clinical assessment.\n"
"Based on the result, classify risk as:\n"
"- GREEN: no danger signs, routine care\n"
"- YELLOW: warning signs, increased monitoring needed\n"
"- RED: danger signs present, immediate escalation required\n\n"
"Call finish_task with the triage result."
),
description="Scores symptoms against WHO danger-sign protocols and classifies risk level.",
tools=[assess_danger_signs],
)
escalation_agent = Agent(
name="escalation",
mode="single_turn",
model="gemini-3-flash-preview",
output_schema=EscalationResult,
output_key="escalation_result",
instruction=(
"You are an escalation agent. A RED risk level has been detected.\n"
"1. Call lookup_nearest_facility with the mother's location to find the closest health facility.\n"
"2. Call send_escalation_alert with the danger signs, facility info, and mother profile.\n"
"3. Call finish_task confirming the alert was sent.\n\n"
"Act immediately. Do not engage in conversation."
),
description="On red flags, alerts a human health worker and identifies nearest facility.",
tools=[send_escalation_alert, lookup_nearest_facility],
)
# --- Coordinator Agent ---
root_agent = Agent(
name="maternity_coordinator",
model="gemini-3-flash-preview",
sub_agents=[intake_agent, monitoring_agent, triage_agent, escalation_agent],
instruction=(
"You are the Maternity Companion coordinator. You manage a team of specialist agents "
"to support pregnant mothers throughout their pregnancy.\n\n"
"Routing rules:\n"
"- If the mother is new or has not been onboarded, delegate to intake "
"using request_task_intake.\n"
"- If the mother reports symptoms or it is time for a check-in, delegate to monitoring "
"using request_task_monitoring.\n"
"- After monitoring returns a symptom report, ALWAYS delegate to triage "
"using request_task_triage to assess risk.\n"
"- If triage returns risk_level red, IMMEDIATELY delegate to escalation "
"using request_task_escalation.\n"
"- If triage returns green or yellow, reassure the mother and provide guidance.\n\n"
"Always be warm, supportive, and culturally sensitive. Address the mother by name "
"if known. Never provide specific medical diagnoses. Your role is to connect "
"mothers with appropriate care."
),
description="Central coordinator for the maternity companion system.",
)
Test the full agent in the dev UI
ADK ships with a browser-based dev UI that lets you interact with your agent visually. This is the fastest way to confirm the Coordinator routes messages correctly.
- Launch the dev UI by running this command from your project root (the folder that contains the maternity_companion directory):
adk web maternity_companion
What does this command do?
The adk web command starts a local web server at localhost:8000 with a chat interface for your agent. It hot-reloads when you save changes to your agent files.
- Open your browser to localhost:8000. You should see the ADK dev UI with a chat input ready.
Now test the three core routing paths. Each one exercises a different branch of the Coordinator's instruction.
- Test intake routing: type a message like Hello, I am 28 weeks pregnant and press Enter.
- The Coordinator should delegate to the Intake agent, which will start asking questions about your risk factors and location.
- Answer the intake questions (any values are fine for testing). Once intake completes, report a dangerous symptom like I have a severe headache and seeing spots.
- The Coordinator should delegate to Monitoring, then Triage, then Escalation (because severe headache with blurred vision is a WHO danger sign). You should see an alert message in the response.
- Start a new session and test with mild symptoms like slight back pain. The Coordinator should route to Monitoring, then Triage, then reassure you (green risk level).
What should I see?
In the dev UI, you can see which sub-agent the Coordinator delegated to by watching the agent name in the response. When escalation fires, you should see a formatted alert message with the facility name and danger signs detected.
For the mild symptom test, the response should reassure the mother and recommend continued routine care. No escalation alert should appear.
Agent not responding or timing out?
Confirm your .env file contains a valid GOOGLE_API_KEY. ADK loads this automatically, but an invalid key will cause silent failures.
If the Coordinator responds but does not delegate to sub-agents, double check that each sub-agent has a description field. ADK uses the description to generate the delegation tool's documentation, which the Coordinator's LLM reads to decide when to call it.
help me debug why my ADK coordinator is not delegating to sub-agents
Test with the ADK CLI
The terminal-based CLI gives you a raw text interaction without the web UI overhead. This is useful for quickly verifying that routing logic and tool calls work end to end.
- Stop the dev UI with Ctrl+C in your terminal.
- Launch the CLI agent by running:
adk run maternity_companion
- Type a greeting like Hi, I'm new here and 32 weeks pregnant to trigger the intake flow.
- Answer the intake questions, then type something like I have been having severe headaches and my vision is blurry to trigger the full triage and escalation chain.
You should see the Coordinator delegate to Monitoring (collects symptoms), then Triage (scores against WHO danger signs), then Escalation (generates the alert with facility information).
- Verify you see the escalation alert message containing the danger signs detected and the nearest facility name.
- Exit the CLI by pressing Ctrl+C.
Triage not detecting danger signs?
The assess_danger_signs tool matches keywords from danger_signs.py against the symptom text. Use keywords that appear in the dataset (like "headache" or "blurred vision") rather than vague descriptions.
If the Monitoring agent summarizes symptoms in different words than what you typed, the keyword match may miss. Try phrasing your symptoms closer to the WHO danger sign keywords.
help me debug why my triage agent is not detecting WHO danger signs
Your multi-agent system is working locally with full coordinator routing. Next up, you will deploy it securely to Cloud Run so it can run as a production service with proper secrets management and authenticated access.
Deploy Securely to Cloud Run
Your multi-agent system works perfectly on your local machine. The next challenge is making it accessible in production without exposing your API key to the outside world.
In this step, you will store your API key in Google Cloud Secret Manager, deploy to Cloud Run with authenticated access only, and verify that unauthenticated requests are rejected.
In this step, get ready to:
- Store your API key securely in Secret Manager.
- Deploy the maternity companion to Cloud Run with authentication enabled.
- Verify the deployment accepts authenticated requests and rejects unauthenticated ones.
Store your API key in Secret Manager
Right now your API key lives in a local .env file. For production, you need a secure vault that your Cloud Run service can read from at runtime without the key ever appearing in code or environment variables visible to the LLM.
What is Secret Manager?
Google Cloud Secret Manager is a service that stores sensitive data like API keys, passwords, and certificates. Your application accesses the secret at runtime through IAM-controlled permissions. The secret value never needs to be committed to source control or baked into a container image.
- First, find your project's number by running this command (you will need it for the service account email):
- Replace the project ID placeholder with your own Google Cloud project ID.
gcloud projects describe [[GCP_PROJECT_ID="your-gcp-project-id"]] --format="value(projectNumber)"
Where do I find my project ID?
Your project ID is visible in the Google Cloud Console at the top of the page, next to the project name. It is a short hyphenated string like my-maternity-project.
- Note the number that is printed. Your default Compute Engine service account email follows the pattern PROJECT_NUMBER-compute@developer.gserviceaccount.com.
- Create the secret and pipe in your API key by running this command:
echo -n "your-api-key-here" | gcloud secrets create GOOGLE_API_KEY --project=[[GCP_PROJECT_ID="your-gcp-project-id"]] --data-file=-
What does this command do?
The echo -n prints your key without a trailing newline. The pipe sends it to gcloud secrets create, which reads from stdin via --data-file=-. This creates a secret named GOOGLE_API_KEY in your project with the key value stored as version 1.
Replace the placeholder
Replace your-api-key-here in the command with your actual Google AI Studio API key. Do not include quotes around the key value itself.
- Grant your Compute Engine service account permission to read the secret by running:
gcloud secrets add-iam-policy-binding GOOGLE_API_KEY --member="serviceAccount:[[SERVICE_ACCOUNT="PROJECT_NUMBER-compute@developer.gserviceaccount.com"]]" --role="roles/secretmanager.secretAccessor" --project=[[GCP_PROJECT_ID="your-gcp-project-id"]]
Why grant access to the compute service account?
Cloud Run services run under the project's default Compute Engine service account. By granting roles/secretmanager.secretAccessor on just this one secret, you follow the principle of least privilege. The service can read the API key at runtime, but no other secret.
- Verify the secret was created by running:
gcloud secrets describe GOOGLE_API_KEY --project=[[GCP_PROJECT_ID="your-gcp-project-id"]]
- You should see output showing the secret name, creation time, and replication policy.
Getting a permission denied error?
Make sure the Secret Manager API is enabled on your project. You can enable it by running gcloud services enable secretmanager.googleapis.com. Also confirm you are authenticated with an account that has Owner or Editor permissions on the project.
help me fix a Secret Manager permission error in my Google Cloud project
Add deployment configuration and deploy
Agents CLI can scaffold all the Cloud Run deployment infrastructure for you. This adds a Dockerfile, deployment manifest, and the necessary configuration to your project.
- Add Cloud Run deployment infrastructure to your project by running:
agents-cli scaffold enhance --deployment-target cloud_run
What does this command do?
This command inspects your existing project and adds the files needed for a Cloud Run deployment. It generates a Dockerfile, deployment configuration, and any required build scripts. Your agent code stays untouched.
- Deploy the service by running:
agents-cli deploy
What happens during deployment?
The agents-cli deploy command builds a container image from your project, pushes it to Google Cloud, and creates a Cloud Run service. By default, the service deploys with --no-allow-unauthenticated, meaning every request must include a valid identity token.
This deployment may take 2-3 minutes as it builds the container and provisions the service.
- When the deploy finishes, it prints a service URL. Copy this URL and save it as an environment variable by running:
export SERVICE_URL=[[SERVICE_URL="https://maternity-companion-service-xxxxx-uc.a.run.app"]]
- You should see output confirming the service was deployed successfully with the service URL printed at the end.
Deployment failed?
Confirm you are in the maternity-companion project directory (not a subdirectory). Also check that Docker or Cloud Build is accessible in your environment. If you see a billing error, verify that billing is enabled on your Google Cloud project.
help me debug a failed Cloud Run deployment with agents-cli
Verify the secure deployment
A secure deployment means two things: authenticated requests get a valid response, and unauthenticated requests are blocked.
- Get an identity token for authentication by running:
export TOKEN=$(gcloud auth print-identity-token)
- Send an authenticated request to your deployed service by running:
curl -X POST $SERVICE_URL -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"message": "Hello, I am 28 weeks pregnant"}'
- You should see a JSON response from the agent. The coordinator will recognize this as a new mother and begin the intake flow, asking for details like risk factors and location.
What does this verify?
The Authorization: Bearer $TOKEN header proves your identity to Cloud Run. The service reads the API key from Secret Manager at runtime, calls the Gemini API, and returns the agent's response. Your API key never appears in the request or response.
- Now test that unauthenticated requests are rejected by running:
curl -X POST $SERVICE_URL -H "Content-Type: application/json" -d '{"message": "Hello"}'
- You should see a 403 Forbidden response. This confirms that your service is protected and only authorized users can interact with the maternity companion.
Getting a 403 on the authenticated request too?
Your identity token may have expired. Tokens are valid for about one hour. Run export TOKEN=$(gcloud auth print-identity-token) again to refresh it. Also confirm that the account you authenticated with has the Cloud Run Invoker role on the service.
help me troubleshoot a 403 error on my authenticated Cloud Run request
Secret mission
Add the Reminder Agent
Your maternity companion can onboard mothers, monitor symptoms, triage risk, and escalate emergencies. But what about the routine stuff? ANC visits, iron supplements, vaccinations. Can you add a sixth agent that keeps mothers on track with their pregnancy schedule?
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. Both Cloud Run and Secret Manager fall within Google Cloud's free tier for this project, so there are no ongoing costs while the service is idle.
Resources you used:
- Cloud Run service (maternity-companion-service in us-central1).
- Secret Manager secret (GOOGLE_API_KEY).
- Local project directory (maternity-companion/).
- Installed Python package (google-adk).
Keep everything running
No action needed. Choose this if you plan to keep developing your maternity companion agent.
- Your Cloud Run service scales to zero automatically when idle. You will not be charged while it is not receiving requests.
- Secret Manager is free for the first 6 active secret versions. Your single secret stays well within this limit.
- Your local project files remain on disk, ready for editing.
Pause - I'll come back to this later
Cloud Run already scales to zero when idle, so there is nothing to pause. Your service will not incur charges while it is not handling requests.
- No action required for the Cloud Run service or Secret Manager secret.
- Your local files remain on disk. You can resume development at any time by navigating back into the maternity-companion directory.
Delete - I don't want to use this again
Remove all project resources and start fresh. This is irreversible.
- Delete the Cloud Run service by running:
gcloud run services delete maternity-companion-service --region=us-central1
- Delete the Secret Manager secret by running:
gcloud secrets delete GOOGLE_API_KEY --project=[[PROJECT_ID="your-google-cloud-project-id"]]
- Remove the local project directory. On macOS or Linux, run:
cd ~/Desktop
rm -rf maternity-companion
- On Windows, run:
cd ~\Desktop
rmdir /s /q maternity-companion
- Uninstall the google-adk package by running:
pip uninstall google-adk
Well Done!
Well Done!
Nice work! You just built and deployed a production-grade multi-agent maternity companion system using ADK 2.0's collaborative workflow pattern.
You've learned how to:
- Build a multi-agent system with a Coordinator that dynamically delegates to specialized sub-agents using auto-generated request_task_{name} tools.
- Implement WHO danger-sign triage with keyword-based matching, risk classification (green/yellow/red), and automatic escalation to health workers on red flags.
- Deploy securely to Cloud Run with Secret Manager for credentials and authenticated access, ensuring secrets are never exposed to the LLM context window.
- Secret Mission: Added a Reminder Agent with ANC visit schedules, iron/folic acid supplement tracking, and vaccination reminders based on gestational age.
Ready to quiz yourself?