0 / 1200 XP LVL 1 Setup Phase
🏛️
LAUNCH SIMULATOR
DEPLOYED!
You built a 6-agent enterprise pharma intelligence pipeline on Google ADK — deterministic orchestration, 8 live free-API integrations, and a board-ready executive dossier generator. That is systems engineering.
🏛️
Claude Skill Engineering Bootcamp · Certificate of Completion
DRUG LAUNCH
STRATEGY ARCHITECT
This certifies that designed and built an end-to-end, 6-agent Enterprise Drug Launch Strategy Simulator on Google's Agent Development Kit — integrating ClinicalTrials.gov, PubMed, World Bank, WHO GHO, openFDA, and live RSS intelligence feeds into a single deterministic pipeline producing board-ready executive launch dossiers.
0
XP Earned
8
APIs Wired
6
Agents Built
7
Gates Passed
🏛️ Claude Skill Engineering Bootcamp · Season 03

BUILD YOUR ENTERPRISE
DRUG LAUNCH SIMULATOR

From zero to a 6-agent multi-agent system on Google's Agent Development Kit that pulls live market, clinical, competitive, and regulatory intelligence from 8 free public APIs — and synthesizes it into a board-ready Launch / Delay / Gather-Evidence / Do-Not-Launch recommendation. All inside VS Code or Antigravity.

7
Build Phases
6
ADK Agents
8
Free APIs
1200
XP to Earn
WHAT YOU'LL BUILD

A fully operational pharmaceutical commercial-strategy AI system — the same multi-agent architecture pattern used in enterprise decision-intelligence tools, built on Google ADK's deterministic orchestration primitives.

⚙️
Phase 1 — Project Setup
Scaffold the repo, pin every dependency, configure your free Gemini API key. Gate 0 confirms ADK actually imports before you write a single agent.
1 Prompt · 50 XP
🧩
Phase 2 — Config & Schema Contract
Centralize every base URL, indicator code, and rate-limit constant. Build the shared ToolResponse contract every one of the 8 tools must obey.
1 Prompt · 80 XP
🔌
Phase 3 — The 8 Tool Wrappers
Wire ClinicalTrials.gov, PubMed, World Bank, WHO GHO, Google News RSS, FDA RSS, and openFDA — each with real, verified request/response contracts and zero placeholder data.
4 Prompts · 320 XP
🤖
Phase 4 — The 4 Intelligence Agents
Market, Scientific, Competitive, and Regulatory Intelligence — each an LlmAgent that calls real tools and writes a typed report to shared session state.
4 Prompts · 320 XP
👑
Phase 5 — Synthesis & Coordinator
Commercial Strategy and Executive Decision agents (pure reasoning, zero tools) plus the ParallelAgent + SequentialAgent composition that wires all 6 agents into one root_agent pipeline.
3 Prompts · 280 XP
🚀
Phase 6 — Run, Verify, Ship
Run the Gated Build Protocol's smoke tests, trace real facts through the pipeline, and launch the full Executive Launch Dossier via adk run or adk web.
2 Prompts · 150 XP
THE TECH STACK

Every package pinned, every API contract verified live against current documentation. No "latest" — that breaks production systems and silently breaks AI-assisted ones even faster.

Agent Framework
google-adk
google-genai
HTTP & Parsing
requests
feedparser
Config & Schema
python-dotenv
pydantic
Data Sources (all free)
ClinicalTrials.gov v2 · PubMed E-utilities
World Bank v2 · WHO GHO OData
openFDA · Google News & FDA RSS
⚠️ Prerequisites: Python 3.10+, VS Code or Antigravity (or any terminal + coding agent), and a free Gemini API key from Google AI Studio. No Google Cloud project, no billing account, no paid service of any kind anywhere in this build.
SYSTEM ARCHITECTURE

Understand the blueprint before you build. Every prompt in Phase 3-5 maps to one box in this diagram.

┌─────────────────────────────────────────────────────────────────────┐ │ root_agent = SequentialAgent (coordinator_agent.py) │ │ │ │ STAGE 1 ── intelligence_gathering_block = ParallelAgent │ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ ┌───────────┐│ │ │ market_agent │ │scientific_ │ │competitor_ │ │regulatory_││ │ │ (Agent 1) │ │agent (Agent 2)│ │agent (Agent 3)│ │agent (4) ││ │ │ tools: │ │ tools: │ │ tools: │ │ tools: ││ │ │ World Bank │ │ ClinicalTrials│ │ Google News │ │ openFDA ││ │ │ WHO GHO │ │ PubMed │ │ RSS │ │ FDA RSS ││ │ └──────┬────────┘ └──────┬────────┘ └──────┬────────┘ └─────┬─────┘│ │ │ output_key= │ output_key= │ output_key= │ │ │ │ market_ │ scientific_ │ competitive_ │ │ │ │ intelligence_ │ intelligence_ │ intelligence_ │ │ │ │ report │ report │ report ... │ │ │ └──────────────────┴──────────────────┴─────────────────┘ │ │ ▼ (all write to shared session.state) │ │ STAGE 2 ── commercial_agent = LlmAgent (no tools, pure synthesis) │ │ reads {market_intelligence_report} + 3 others via │ │ instruction template variables │ │ writes → commercial_strategy_report │ │ ▼ │ │ STAGE 3 ── executive_agent = LlmAgent (no tools, final synthesis) │ │ reads ALL FIVE upstream reports │ │ writes → executive_launch_dossier (the final output) │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ ▼ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ World Bank │ │ClinicalTrials│ │Google News │ │ openFDA │ │ WHO GHO │ │.gov v2 │ │RSS · FDA RSS│ │ label.json │ │ (no auth) │ │PubMed E-utils│ │ (feedparser)│ │ event.json │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ 8 free public APIs, zero keys required (2 optional for higher limits)
DATA FLOW

How a drug candidate becomes a board recommendation — 6 stages every run travels through.

💊
1. Input
Drug + indication + countries
2. Parallel Fan-out
4 agents, 8 APIs, concurrent
🗂
3. State Write
4 typed reports via output_key
🧮
4. Commercial Synthesis
Pure reasoning over 4 reports
👑
5. Executive Synthesis
Scores + decision rule
📄
6. Dossier
Markdown, saved + printed
🎯 Why ParallelAgent for Stage 1? Market, Scientific, Competitive, and Regulatory Intelligence have zero dependencies on each other — every drug launch decision needs all four, every time. That's exactly the case where deterministic concurrent execution (ParallelAgent) beats an LLM-driven router: no routing logic to get wrong, no risk of an agent being skipped, and the four API-bound calls run at the same time instead of stacking latency.
FILE STRUCTURE

26 files across 6 modules. Each Build-phase prompt below creates one module completely, gated by a smoke test before the next module is allowed to begin.

📁 drug-launch-strategy/26 files · 6 modules
📄 main.pyCLI entry point — collects drug_name, mechanism, indication, countries
📄 requirements.txtExact pinned dependency list
📄 .env.exampleGOOGLE_API_KEY template
📄 README.mdSetup + run instructions
📄 CHANGELOG.mdAny API-drift deviations you had to adapt to
⚙️config/2 files
__init__.py
settings.pyALL base URLs, indicator codes, rate limits
🧩schemas/2 files
__init__.py
tool_response.pyShared {status,data,source,error_message} contract
🔌tools/9 files
__init__.py
clinical_trials.pyClinicalTrials.gov v2 wrapper
pubmed.pyNCBI E-utilities 2-step wrapper
world_bank.pyWorld Bank Indicators v2 wrapper
who_gho.pyWHO GHO OData wrapper
rss_reader.pyGeneric feedparser wrapper (2 agents reuse it)
openfda.pyopenFDA label + event wrapper
crossref_openalex.pyPhase 5 optional enrichment, flag-gated
smoke_test_all.pyGate 1 verification — every tool, PASS/FAIL
🤖agents/7 files
__init__.py
market_agent.pyAgent 1 — Market Intelligence
scientific_agent.pyAgent 2 — Clinical & Scientific Intelligence
competitor_agent.pyAgent 3 — Competitive Intelligence
regulatory_agent.pyAgent 4 — Regulatory Intelligence
commercial_agent.pyAgent 5 — reasoning-only synthesis
executive_agent.pyAgent 6 — final dossier synthesis
coordinator_agent.pyroot_agent — Parallel + Sequential composition
📝prompts/1 file
instruction_fragments.pyShared boilerplate instruction text, DRY across agents
🧪tests/4 files
__init__.py
test_tools.pyGate 1 — per-tool isolation tests
test_agents_isolated.pyGate 2 — per-agent isolation tests
test_end_to_end.pyGate 3/4 — full pipeline runs
BUILD YOUR SIMULATOR

Copy each prompt, paste it into VS Code (Cursor, Antigravity, Windsurf, or Claude Code), and let the AI build each layer. Complete each phase to unlock the next. The Gates are not decorative — skipping one is exactly how six agents' worth of debugging gets created later.

⚙️Setup
🧩Config
🔌Tools
🤖Agents
👑Synthesis
🔗Coordinator
🚀Deploy
01
PHASE 1 — PROJECT SETUP (Gate 0)
Scaffold the repo, pin every dependency, configure your free Gemini API key, and prove ADK actually imports before writing a single agent.
50 XP
📍 Where to paste: Open VS Code or Antigravity → open your terminal (Ctrl+`) → open a new Claude / Cursor / Antigravity chat window → paste the prompt below. The AI will generate and run the commands.
1
📁 Scaffold the Repo & Verify ADK Imports
Create the 26-file skeleton, install pinned packages, and confirm google-adk is actually importable
+50 XP
PHASE 1 PROMPT — Paste into Claude / Cursor / Antigravity Chat
You are a Principal AI Systems Architect with 14 years building production multi-agent systems on Google Cloud, with deep, current hands-on expertise in Google's Agent Development Kit (ADK) for Python — the stable `google-adk` PyPI line (v1.x), NOT the experimental ADK 2.0 Workflow/graph runtime. TASK: Scaffold the complete project skeleton for an "Enterprise Drug Launch Strategy Simulator" — a 6-agent ADK system. Do NOT write any agent or tool logic yet — this phase is structure only. EXECUTE these shell commands in sequence: 1. Create the project structure: mkdir drug-launch-strategy cd drug-launch-strategy mkdir -p config schemas tools agents prompts tests output 2. Create requirements.txt with EXACTLY these packages (no version pinning needed — we want current stable ADK, but list them in this exact order): google-adk google-genai requests feedparser python-dotenv pydantic 3. Create .env.example with content: GOOGLE_API_KEY=your-gemini-api-key-here NCBI_API_KEY= OPENFDA_API_KEY= USE_CROSSREF_ENRICHMENT=false 4. Create empty __init__.py files for every package: touch config/__init__.py schemas/__init__.py tools/__init__.py agents/__init__.py tests/__init__.py 5. Install all packages: pip install -r requirements.txt 6. CRITICAL VERIFICATION — run this exact command and show me the output: python -c "import google.adk; print('ADK OK:', google.adk.__version__)" 7. Confirm by listing the full directory tree. GATE 0 — DO NOT PROCEED PAST THIS PROMPT UNTIL: - pip install completed with zero errors - The python -c import command printed a real version string, not an ImportError - If the import fails, STOP and show me the exact error — do not guess at a fix by reinstalling blindly; diagnose first. OUTPUT: Show every command, its output, and explicitly confirm Gate 0 PASSED or FAILED.
02
PHASE 2 — CONFIG & SCHEMA CONTRACT
Centralize every base URL, indicator code, and rate-limit constant in one file. Build the shared ToolResponse contract every one of the 8 tools must obey — this single design choice is what lets agent instructions reliably branch on "empty vs error vs success."
80 XP
2
🧩 Build config/settings.py + schemas/tool_response.py
No magic strings anywhere downstream — every URL and code lives here, imported everywhere else
+80 XP
🧱 Why this matters first: If ClinicalTrials.gov or openFDA changes a parameter name next year, the fix should be one line in settings.py — not a hunt through 8 tool files. And if every tool returns a different response shape, every agent instruction has to special-case it. One shared contract removes that entirely.
PHASE 2 PROMPT — Config & Schema (2 files)
You are a Principal AI Systems Architect building the Enterprise Drug Launch Strategy Simulator on Google ADK. TASK: Build the configuration and schema layer. Create 2 files COMPLETELY — no stubs, no placeholders. FILE 1: config/settings.py """Single source of truth for every external API constant used in this system.""" Define these EXACT constants (verified against live API documentation, do not alter): CTGOV_BASE_URL = "https://clinicaltrials.gov/api/v2/studies" CTGOV_TIMEOUT_SECONDS = 15 PUBMED_BASE_URL = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/" PUBMED_MIN_DELAY_NO_KEY = 0.34 # ≤3 req/sec ceiling without an API key PUBMED_MIN_DELAY_WITH_KEY = 0.1 # ≤10 req/sec ceiling with NCBI_API_KEY set WORLD_BANK_BASE_URL = "https://api.worldbank.org/v2/country/{country_codes}/indicator/{indicator}" WB_INDICATOR_GDP = "NY.GDP.MKTP.CD" WB_INDICATOR_GDP_PER_CAPITA = "NY.GDP.PCAP.CD" WB_INDICATOR_POPULATION = "SP.POP.TOTL" WB_INDICATOR_HEALTH_EXP_PCT_GDP = "SH.XPD.CHEX.GD.ZS" WB_INDICATOR_HEALTH_EXP_PER_CAPITA = "SH.XPD.CHEX.PC.CD" WHO_GHO_BASE_URL = "https://ghoapi.azureedge.net/api/{indicator_code}" WHO_GHO_INDICATOR_SEARCH_URL = "https://ghoapi.azureedge.net/api/Indicator" GOOGLE_NEWS_RSS_BASE = "https://news.google.com/rss/search" FDA_PRESS_RELEASE_RSS = "https://www.fda.gov/about-fda/contact-fda/stay-informed/rss-feeds/press-releases/rss.xml" RSS_MIN_DELAY_SECONDS = 2.0 OPENFDA_LABEL_URL = "https://api.fda.gov/drug/label.json" OPENFDA_EVENT_URL = "https://api.fda.gov/drug/event.json" OPENFDA_MIN_DELAY_SECONDS = 0.25 # ≤4 req/sec, safely under the 240/min no-key ceiling CROSSREF_BASE_URL = "https://api.crossref.org/works" OPENALEX_BASE_URL = "https://api.openalex.org/works" Also load from environment (use python-dotenv, load_dotenv() at module top): - GOOGLE_API_KEY (required, raise a clear error at startup if missing) - NCBI_API_KEY (optional, default None) - OPENFDA_API_KEY (optional, default None) - USE_CROSSREF_ENRICHMENT (optional bool, default False, parse from string "true"/"false") A small static ISO3-code → country-name dict for at least these 12 countries (WHO GHO returns ISO3 codes, not names): IND, BRA, IDN, NGA, MEX, VNM, ZAF, EGY, PHL, PAK, BGD, TUR FILE 2: schemas/tool_response.py """The contract every one of the 8 tool functions must return.""" Use a Python dataclass: from dataclasses import dataclass, field from typing import Any, Literal @dataclass class ToolResponse: status: Literal["success", "empty", "error"] data: list[dict[str, Any]] = field(default_factory=list) source: str = "" error_message: str | None = None def to_dict(self) -> dict: return { "status": self.status, "data": self.data, "source": self.source, "error_message": self.error_message, } CRITICAL RULES: - Every tool function built in Phase 3 returns ToolResponse(...).to_dict() — no exceptions, no naked dicts of differing shape. - "empty" means the API call succeeded but returned zero matching records (a VALID, common, meaningful outcome for a novel pre-approval drug — never treat empty as an error). - "error" means the HTTP call itself failed (network, timeout, malformed response) — always include error_message. - NEVER hardcode a base URL, indicator code, or RSS feed URL inside any tool or agent file written in later phases — everything imports from config.settings.
03
PHASE 3 — THE 8 TOOL WRAPPERS (Gate 1)
Build and independently verify every external API call BEFORE any agent touches it. This is the single most important gate in the whole build — debugging a tool bug after it's hidden inside an LLM's reasoning loop is dramatically slower than catching it here.
320 XP
⚠️ Non-negotiable rule for this whole phase: Do NOT let the AI move to Phase 4 until python tools/smoke_test_all.py prints PASS for all 8 tools. If you're tempted to skip ahead because "it'll probably work," that's exactly the instinct this Gate exists to override.
3a
🧬 Build tools/clinical_trials.py + tools/pubmed.py
ClinicalTrials.gov v2 (cursor pagination!) + PubMed's two-step ESearch→ESummary flow
+90 XP
🧬 ClinicalTrials.gov API v2No auth required
GET https://clinicaltrials.gov/api/v2/studies?query.cond=<disease>&query.intr=<drug>&pageSize=50&format=json
🪤 Gotcha: Pagination is cursor-based via nextPageToken, NOT page numbers. An absent nextPageToken means you're on the last page — not an error. An empty studies: [] array is a valid "zero matching trials" result.
🔬 PubMed E-utilities (NCBI Entrez)No auth · 3 req/sec ceiling
GET .../esearch.fcgi?db=pubmed&term=... → GET .../esummary.fcgi?db=pubmed&id=<PMIDs>
🪤 Gotcha: This is a TWO-STEP API. You cannot skip ESearch and call ESummary directly with a search term — PMIDs must be resolved first. Also: esearchresult.count is a STRING, cast it explicitly.
PHASE 3a PROMPT — Clinical & Scientific Data Tools (2 files)
You are a Principal AI Systems Architect. You think in terms of: what are the three ways this API call fails (network, rate-limit, empty-result-set), and does my function return a typed, non-crashing response in all three cases? TASK: Build 2 tool functions used by the Scientific Intelligence agent later. Both import ToolResponse from schemas.tool_response and constants from config.settings. Real, working HTTP calls — zero mocks, zero "# TODO: implement." FILE 1: tools/clinical_trials.py def search_clinical_trials(condition: str, intervention: str, phase: str = "", status: str = "", max_results: int = 50) -> dict: - Google-style docstring (ADK surfaces this to the LLM as the tool's calling contract — be precise, not vague) - Build params dict: query.cond=condition, query.intr=intervention, pageSize=min(max_results,1000), format=json - If phase: add filter.phase. If status: add filter.overallStatus. - GET request to CTGOV_BASE_URL with CTGOV_TIMEOUT_SECONDS timeout, wrapped in try/except requests.exceptions.RequestException - Parse payload["studies"] — if empty list, return ToolResponse(status="empty", source="clinicaltrials.gov").to_dict() - Else extract per study: nctId, briefTitle, overallStatus, phases (list), leadSponsor name, hasResults — from protocolSection.identificationModule / statusModule / designModule / sponsorCollaboratorsModule - Return ToolResponse(status="success", data=records, source="clinicaltrials.gov").to_dict() - On exception: return ToolResponse(status="error", source="clinicaltrials.gov", error_message=str(e)).to_dict() FILE 2: tools/pubmed.py def search_pubmed_literature(query_terms: str, max_results: int = 30) -> dict: - Respect rate limit: time.sleep(PUBMED_MIN_DELAY_WITH_KEY if NCBI_API_KEY else PUBMED_MIN_DELAY_NO_KEY) before EACH of the two calls below - STEP 1 (ESearch): GET PUBMED_BASE_URL + "esearch.fcgi" with db=pubmed, term=query_terms, retmax=max_results, sort=date, retmode=json, api_key=NCBI_API_KEY if set - Parse response["esearchresult"]["idlist"] — cast count via int(response["esearchresult"]["count"]) - If idlist is empty: return ToolResponse(status="empty", source="pubmed").to_dict() — do NOT proceed to step 2 - STEP 2 (ESummary): GET PUBMED_BASE_URL + "esummary.fcgi" with db=pubmed, id=",".join(idlist), retmode=json - Iterate over result["uids"] (NOT result.keys() — "uids" is itself a key you must skip) - For each uid build: {pmid, title, pubdate, journal, first_author} from result[uid] - Return ToolResponse(status="success", data=records, source="pubmed").to_dict() - Wrap BOTH steps in one try/except — any exception anywhere returns ToolResponse(status="error", source="pubmed", error_message=str(e)).to_dict() CRITICAL RULES: - NEVER raise an uncaught exception from either function - NEVER treat an empty result set as an error — it's the EXPECTED case for a novel, not-yet-studied drug candidate - NEVER skip PubMed's ESearch step "to save a call" — ESummary requires PMIDs obtained from ESearch first, this is architecturally required, not optional - Docstrings must be precise enough that an LLM calling this tool knows exactly what arguments to pass
3b
🌍 Build tools/world_bank.py + tools/who_gho.py
World Bank's 2-element array response + WHO GHO's OData indicator search
+80 XP
🌍 World Bank Indicators API v2No auth, ever
GET https://api.worldbank.org/v2/country/IN;BR;ZA/indicator/NY.GDP.PCAP.CD?format=json&mrv=1
🪤 Gotcha: The response is a TOP-LEVEL 2-ELEMENT ARRAY — response[0] is metadata, response[1] is the actual data rows. A naive response["results"] access silently breaks. Also semicolon-join multiple country codes into ONE request — never loop one-country-per-call. "value" can be JSON null — filter it out.
🏥 WHO Global Health Observatory OData APINo auth, ever
GET https://ghoapi.azureedge.net/api/{IndicatorCode}?$filter=SpatialDim eq 'IND'
🪤 Gotcha: You must resolve an indicator code BY NAME SEARCH first (?$filter=contains(IndicatorName,'diabetes')) — never guess codes. SpatialDim returns ISO3 codes (IND, BRA), not country names — use the static lookup dict from Phase 2, don't call yet another API for this.
PHASE 3b PROMPT — Macro & Health Data Tools (2 files)
You are a Principal AI Systems Architect building the Enterprise Drug Launch Strategy Simulator. TASK: Build 2 tool functions for the Market Intelligence agent. Real HTTP calls, ToolResponse contract, full error handling. FILE 1: tools/world_bank.py def get_market_indicators(country_codes: list[str], indicators: list[str] = None) -> dict: - Default indicators to [WB_INDICATOR_GDP, WB_INDICATOR_GDP_PER_CAPITA, WB_INDICATOR_POPULATION, WB_INDICATOR_HEALTH_EXP_PCT_GDP] if None - For EACH indicator (loop indicators, but semicolon-join ALL country_codes into ONE request per indicator — never loop per-country): - url = WORLD_BANK_BASE_URL.format(country_codes=";".join(country_codes), indicator=indicator) - GET with params format=json, mrv=1 (most recent value) - Response is a list: response[0] is metadata, response[1] is the data array (or None if zero results — World Bank can return [metadata, None]) - If response[1] is None or empty: skip this indicator, continue the loop (not an error, just no data for that indicator/country combo) - For each row in response[1] where row["value"] is not None: append {country: row["country"]["value"], country_code: row["countryiso3code"], indicator: row["indicator"]["value"], indicator_code: indicator, year: row["date"], value: row["value"]} - If ALL indicators returned nothing: return ToolResponse(status="empty", source="worldbank.org").to_dict() - Else: return ToolResponse(status="success", data=all_records, source="worldbank.org").to_dict() - Wrap each HTTP call in try/except — on any RequestException, return ToolResponse(status="error", source="worldbank.org", error_message=str(e)).to_dict() immediately (don't silently swallow it and return partial empty success) FILE 2: tools/who_gho.py def get_disease_burden(disease_keyword: str, country_codes_iso3: list[str]) -> dict: - STEP 1: GET WHO_GHO_INDICATOR_SEARCH_URL with $filter=contains(IndicatorName,'{disease_keyword}') to resolve candidate indicator codes - If zero indicators found: return ToolResponse(status="empty", source="who_gho", error_message=f"No GHO indicator found for '{disease_keyword}'").to_dict() - Pick the first matching IndicatorCode (or let the caller pass a more specific keyword if results are too broad) - STEP 2: GET WHO_GHO_BASE_URL.format(indicator_code=resolved_code) with $filter querying SpatialDim in country_codes_iso3 and a recent date range - Parse response["value"] — for each row build {country_iso3: row["SpatialDim"], country_name: ISO3_TO_NAME.get(row["SpatialDim"], row["SpatialDim"]), year: row["TimeDim"], value: row["NumericValue"], indicator: disease_keyword} - If value array is empty: return ToolResponse(status="empty", source="who_gho").to_dict() - Return ToolResponse(status="success", data=records, source="who_gho").to_dict() - On exception: return ToolResponse(status="error", source="who_gho", error_message=str(e)).to_dict() CRITICAL RULES: - NEVER treat World Bank's response[0] (metadata) as the data — always unpack response[1] - NEVER loop one-country-per-World-Bank-call — semicolon-join them into a single request - NEVER guess a WHO GHO indicator code — resolve it by name search first, every time - Filter out null "value" fields before returning records — do not pass nulls downstream
3c
📰 Build tools/rss_reader.py
One generic feedparser wrapper serves BOTH Google News and FDA press releases — never duplicate parsing logic
+50 XP
📰 Google News RSS + FDA Press Release RSSNo auth — public feeds
https://news.google.com/rss/search?q=<query>&hl=en-US&gl=US&ceid=US:en
🪤 Gotcha: feedparser never raises on malformed XML — it sets a .bozo flag instead. Check it, log a warning if set, but STILL use .entries (feedparser is lenient and often extracts usable entries even from slightly malformed feeds). Don't raise on bozo=1.
PHASE 3c PROMPT — Generic RSS Tool (1 file, used by 2 agents)
You are a Principal AI Systems Architect building the Enterprise Drug Launch Strategy Simulator. TASK: Build ONE generic RSS tool function. This single function will be passed into BOTH the Competitive Intelligence agent (Google News) and the Regulatory Intelligence agent (FDA press releases) later — do not write two separate parsers. FILE: tools/rss_reader.py """Generic RSS feed reader using feedparser — serves Google News and FDA RSS alike.""" def fetch_rss(feed_url: str, max_items: int = 15) -> dict: - Import feedparser - time.sleep(RSS_MIN_DELAY_SECONDS) before parsing (self-throttle, these are unofficial-rate-limit public feeds) - parsed = feedparser.parse(feed_url) - Check parsed.bozo: if 1, log a warning (use Python's logging module) but DO NOT raise — continue to use parsed.entries anyway - If not parsed.entries: return ToolResponse(status="empty", source=feed_url).to_dict() - For each entry in parsed.entries[:max_items]: build {title: entry.get("title",""), link: entry.get("link",""), published: entry.get("published", entry.get("updated","")), summary: strip_html_tags(entry.get("summary",""))} - strip_html_tags: a small helper using re.sub(r'<[^>]+>', '', text) to remove HTML tags from the summary field (Google News summaries are HTML-laden) - Return ToolResponse(status="success", data=records, source=feed_url).to_dict() - Wrap the ENTIRE function body in try/except — if feedparser itself throws (rare, but a dead URL or DNS failure can), return ToolResponse(status="error", source=feed_url, error_message=str(e)).to_dict() def build_google_news_query_url(query_terms: str) -> str: - URL-encode query_terms (use urllib.parse.quote) - Return f"{GOOGLE_NEWS_RSS_BASE}?q={encoded}&hl=en-US&gl=US&ceid=US:en" - This helper lets the Competitive Intelligence agent build a query URL from drug/indication terms without hand-assembling URL encoding itself CRITICAL RULES: - ONE function serves both Google News and FDA RSS — the feed_url parameter is what differs, not the parsing logic - NEVER raise on parsed.bozo == 1 — feedparser's leniency is a feature, not a bug, here - The FDA feed URL occasionally gets restructured by FDA — that's exactly why feed_url is a parameter, not hardcoded inside this function
3d
⚖️ Build tools/openfda.py + tools/smoke_test_all.py — GATE 1
The 404-means-empty gotcha, plus the script that proves all 8 tools actually work before Phase 4 begins
+100 XP
⚖️ openFDANo auth · 240/min, 120k/day
GET https://api.fda.gov/drug/label.json?search=openfda.generic_name:"semaglutide"&limit=5
🪤 Gotcha: An HTTP 404 from openFDA means "zero results matched" — this is DOCUMENTED behavior, not a broken endpoint. Catch it specifically and convert to a typed empty result. This is the EXPECTED outcome for a novel, pre-approval drug candidate that has no marketed label yet — which is the exact scenario this whole system is built around.
PHASE 3d PROMPT — Regulatory Tools + Gate 1 Smoke Test (2 files)
You are a Principal AI Systems Architect building the Enterprise Drug Launch Strategy Simulator. TASK: Build the final tool file, THEN build the Gate 1 smoke test script that verifies all 8 tools before any agent is written. FILE 1: tools/openfda.py def search_drug_labels(drug_term: str, max_results: int = 5) -> dict: - GET OPENFDA_LABEL_URL with params: search=f'openfda.generic_name:"{drug_term}"', limit=max_results - time.sleep(OPENFDA_MIN_DELAY_SECONDS) before the call - CATCH requests.exceptions.HTTPError SPECIFICALLY: if e.response.status_code == 404, return ToolResponse(status="empty", source="openfda", error_message="No marketed precedent label found — novel/pre-approval candidate").to_dict() — this is the expected path for new candidates, not a failure - On success: for each result, extract {brand_name: result.get("openfda",{}).get("brand_name",[]), generic_name: result.get("openfda",{}).get("generic_name",[]), boxed_warning: result.get("boxed_warning", []), warnings: result.get("warnings", []), drug_interactions: result.get("drug_interactions", [])} — use .get() everywhere, boxed_warning is often ABSENT, never index directly - Return ToolResponse(status="success", data=records, source="openfda").to_dict() - Any other exception: return ToolResponse(status="error", source="openfda", error_message=str(e)).to_dict() def search_adverse_events(drug_term: str, max_results: int = 10) -> dict: - Same pattern, but GET OPENFDA_EVENT_URL with search=f'patient.drug.medicinalproduct:"{drug_term}"', limit=max_results, sort=receivedate:desc - Same 404-means-empty handling - Extract whatever reaction/seriousness fields are present per result, using .get() defensively FILE 2: tools/smoke_test_all.py — THIS IS GATE 1 """Run every tool function once with a real hardcoded scenario. ALL 8 must print PASS before Phase 4 begins.""" - Import all 8 tool functions - Define ONE hardcoded test scenario at the top: drug="semaglutide", condition="type 2 diabetes", countries=["IN","BR","ID"], news_query="semaglutide launch OR partnership" - For each tool, call it, print f"[{tool_name}] status={result['status']} source={result['source']}" and: - PASS if status is "success" or "empty" (both are valid, non-crashing outcomes) - FAIL if status is "error" (print the error_message) OR if calling the function raised an uncaught exception - At the end, print a summary: "X/8 PASS" — and exit with a non-zero code if any tool FAILed, so this can be used as a real CI gate, not just a visual check GATE 1 — RUN THIS NOW: python tools/smoke_test_all.py DO NOT PROCEED TO PHASE 4 UNTIL ALL 8 TOOLS PRINT PASS. If any tool fails, fix that tool in isolation — do not patch around it with a try/except that hides the real problem, and do not proceed with a "we'll fix it later" placeholder.
🚧 GATE 1 CHECKLIST
☐ All 8 tools return the exact ToolResponse shape
☐ smoke_test_all.py prints 8/8 PASS
☐ No tool raised an uncaught exception during the smoke test
☐ You manually inspected at least 2 "success" results and confirmed real data came back (not an empty success)
04
PHASE 4 — THE 4 INTELLIGENCE AGENTS (Gate 2)
Wire each LlmAgent to its now-verified tools. Build and test each agent IN ISOLATION before composing them — an agent that "runs" but never actually calls its tool is a silent failure that's much harder to catch once it's buried inside a ParallelAgent.
320 XP
🔑 The pattern that repeats 4 times: Every agent below is an LlmAgent with a model, a description (for ADK's own routing/discovery), an instruction string (the actual behavior contract — explicit about which tool to call and how to branch on its status), a tools=[...] list, and an output_key (the exact session-state variable name downstream agents will read).
4a
📊 Build agents/market_agent.py — Agent 1
World Bank + WHO GHO → market size, growth potential, priority country ranking
+80 XP
PHASE 4a PROMPT — Market Intelligence Agent
You are a Principal AI Systems Architect building the Enterprise Drug Launch Strategy Simulator on Google ADK (stable google-adk, LlmAgent class — not the experimental Workflow runtime). TASK: Build agents/market_agent.py — Agent 1 of 6. This agent calls real tools; do not let it answer from background knowledge instead of calling them. FILE: agents/market_agent.py from google.adk.agents import LlmAgent from tools.world_bank import get_market_indicators from tools.who_gho import get_disease_burden market_agent = LlmAgent( name="market_intelligence_agent", model="gemini-2.5-flash", description="Evaluates pharmaceutical market opportunity across candidate launch countries using World Bank economic data and WHO disease burden data.", instruction="""You are a pharmaceutical market intelligence analyst. Write the FULL instruction string with these exact requirements: - Explicitly direct the LLM to call get_market_indicators for the country list, THEN call get_disease_burden for the indication/disease - If a tool returns status "empty": explicitly state no data was available for that country/indicator combination — never invent a plausible-sounding number to fill the gap - If a tool returns status "error": note the data source was temporarily unavailable, proceed with whatever the OTHER tool returned - Require exactly these 4 output sections: (1) Market Size & Growth Potential per country, (2) Disease Burden Summary per country, (3) Priority Market Ranking 1-N with one-sentence rationale per country, (4) Market Attractiveness Score 0-100 per country with the three driving factors named explicitly for the top-ranked country - End the instruction with: "Be explicit and numeric. Never say 'the market looks attractive' without citing the specific GDP, health expenditure, or disease burden figure that justifies the claim." Close the LlmAgent definition with: tools=[get_market_indicators, get_disease_burden], output_key="market_intelligence_report", ) CRITICAL RULES: - output_key must be exactly "market_intelligence_report" — this is the state key 2 downstream agents will reference later, a typo here causes a SILENT template-resolution failure, not a crash - The instruction must contain an explicit, unambiguous directive to call each tool BY NAME — a vague instruction lets the LLM "answer from training knowledge" instead, which defeats the entire point of building this tool
4b
🧬 Build agents/scientific_agent.py — Agent 2
ClinicalTrials.gov + PubMed → trial-phase landscape, publication trends, maturity score
+80 XP
PHASE 4b PROMPT — Scientific Intelligence Agent
You are a Principal AI Systems Architect building the Enterprise Drug Launch Strategy Simulator on Google ADK. TASK: Build agents/scientific_agent.py — Agent 2 of 6. from google.adk.agents import LlmAgent from tools.clinical_trials import search_clinical_trials from tools.pubmed import search_pubmed_literature scientific_agent = LlmAgent( name="scientific_intelligence_agent", model="gemini-2.5-flash", description="Assesses scientific and clinical maturity of a drug candidate using ClinicalTrials.gov and PubMed data.", instruction="""You are a pharmaceutical clinical/scientific intelligence analyst. Write the FULL instruction string requiring: - Call search_clinical_trials using the drug's mechanism/name as intervention and indication as condition, no phase filter first, then optionally a second call filtered to PHASE3 to assess late-stage maturity specifically - Call search_pubmed_literature combining drug name/mechanism and indication as query_terms - If either tool returns "empty": state plainly that no trials/publications were found — frame this as a meaningful finding (early-stage or novel mechanism), not a tool failure to apologize for - Require exactly these 4 output sections: (1) Clinical Trial Landscape — count by phase, sponsors, statuses, (2) Publication Trend Summary — volume, recency, growing-or-shrinking, (3) Scientific Maturity Score 0-100 justified by trial-phase distribution, (4) Innovation Signal — flag if sparse trials AND sparse publications suggests a novel mechanism vs. a crowded, well-studied one Close with: tools=[search_clinical_trials, search_pubmed_literature], output_key="scientific_intelligence_report", ) CRITICAL RULES: - output_key must be exactly "scientific_intelligence_report" - The instruction must make explicit that completed Phase 3 trials score HIGHER maturity than only-Phase-1 trials — give the LLM the actual weighting logic, don't leave it to guess
4c
🏢 Build agents/competitor_agent.py — Agent 3
Google News RSS → competitor launches, partnerships, acquisitions, SWOT positioning
+80 XP
PHASE 4c PROMPT — Competitive Intelligence Agent
You are a Principal AI Systems Architect building the Enterprise Drug Launch Strategy Simulator on Google ADK. TASK: Build agents/competitor_agent.py — Agent 3 of 6. from google.adk.agents import LlmAgent from tools.rss_reader import fetch_rss competitor_agent = LlmAgent( name="competitive_intelligence_agent", model="gemini-2.5-flash", description="Monitors competitor launches, partnerships, and acquisitions via news and press release feeds.", instruction="""You are a pharmaceutical competitive intelligence analyst. Write the FULL instruction string requiring: - Call fetch_rss with a Google News RSS query URL built from the indication area plus terms like "launch", "partnership", "acquisition", "FDA approval" — show the agent the EXACT URL pattern to build: https://news.google.com/rss/search?q=<URL-encoded query>&hl=en-US&gl=US&ceid=US:en - If the tool returns "empty" or "error": state plainly that no recent competitive signal was found via this channel — do NOT fabricate competitor names or deals to fill the gap - Require exactly these 4 output sections: (1) Recent Competitive Activity — headlines with source/date, (2) Competitor Landscape Summary — group companies by what they're doing (launching/partnering/acquired/advancing trials), (3) SWOT positioning based ONLY on retrieved headlines plus market/scientific data passed via state, (4) Market Positioning Recommendation - End the instruction with an explicit honesty requirement: "If the news feed returns sparse or no results, explicitly flag that competitive intelligence from this run is limited and should be supplemented by paid intelligence tools before a final go/no-go decision — do not paper over a thin data return with confident-sounding generic claims." Close with: tools=[fetch_rss], output_key="competitive_intelligence_report", ) CRITICAL RULES: - output_key must be exactly "competitive_intelligence_report" - This agent must build its own query URL using the helper pattern from tools/rss_reader.py — the instruction should reference fetch_rss directly, not a separate "search news" abstraction that doesn't exist
4d
⚖️ Build agents/regulatory_agent.py — Agent 4 + GATE 2
openFDA + FDA RSS → comparable approvals, safety signals, regulatory risk rating
+80 XP
PHASE 4d PROMPT — Regulatory Intelligence Agent + Gate 2 Isolation Tests
You are a Principal AI Systems Architect building the Enterprise Drug Launch Strategy Simulator on Google ADK. TASK: Build agents/regulatory_agent.py — Agent 4 of 6 — THEN build the Gate 2 isolation test for all 4 intelligence agents. from google.adk.agents import LlmAgent from tools.openfda import search_drug_labels, search_adverse_events from tools.rss_reader import fetch_rss regulatory_agent = LlmAgent( name="regulatory_intelligence_agent", model="gemini-2.5-flash", description="Assesses regulatory readiness and safety landscape using openFDA and FDA RSS data.", instruction="""You are a pharmaceutical regulatory intelligence analyst. Write the FULL instruction string requiring: - Call search_drug_labels for the drug name or its drug class to find comparable approved-drug labels - Call search_adverse_events for the same term to check safety signal density in that drug class - Call fetch_rss with FDA_PRESS_RELEASE_RSS to check for recent regulatory actions - CRITICAL explicit instruction: "If search_drug_labels returns 'empty', this is EXPECTED and INFORMATIVE for a novel, not-yet-approved candidate — explicitly state 'no marketed precedent label found; this candidate would be a novel regulatory filing' rather than treating it as a failure." - Require exactly these 4 output sections: (1) Comparable Approved Products — labels found with boxed warnings if present, (2) Safety Signal Summary, (3) Recent Regulatory Activity from the FDA feed, (4) Regulatory Risk Rating Low/Medium/High WITH the specific factors driving the rating named explicitly Close with: tools=[search_drug_labels, search_adverse_events, fetch_rss], output_key="regulatory_intelligence_report", ) FILE 2: tests/test_agents_isolated.py — THIS IS GATE 2 """Run each of the 4 intelligence agents ALONE, print its raw output_key value.""" - Import Runner and InMemorySessionService from google.adk - For EACH of the 4 agents (market, scientific, competitor, regulatory): create an isolated Runner with InMemorySessionService, run ONE hardcoded test scenario (drug="semaglutide", indication="type 2 diabetes", countries=["India","Brazil","Indonesia"]) - Print the agent's final session.state[output_key] value in full - Manually-inspectable PASS criteria printed per agent: "Does this output contain REAL numbers/names traceable to a tool call, or generic LLM prose with no real data in it?" GATE 2 — RUN THIS NOW: python tests/test_agents_isolated.py DO NOT PROCEED TO PHASE 5 UNTIL: each of the 4 agents, run alone, produces non-empty, schema-conformant output that visibly contains real data threaded through from its tool calls — not generic invented prose. If Agent 1 returns text with no actual GDP numbers in it because the tool wasn't called, that is a FAIL even though no exception was thrown.
🚧 GATE 2 CHECKLIST
☐ All 4 agents run alone without exceptions
☐ Each agent's output_key state value contains REAL data traceable to a specific tool call (not generic LLM prose)
☐ "Empty" tool results are narrated honestly in the output, not papered over
☐ output_key strings exactly match: market_intelligence_report, scientific_intelligence_report, competitive_intelligence_report, regulatory_intelligence_report
05
PHASE 5 — SYNTHESIS AGENTS
Agents 5 and 6 are pure reasoning — zero tools. They read the four upstream reports via {state_variable} templating and synthesize them into commercial strategy and, finally, the board-ready Executive Launch Dossier.
180 XP
⚠️ The #1 silent bug in this whole system lives here: If a {state_variable} name in these agents' instructions doesn't EXACTLY match the output_key string from Phase 4, the template silently fails to resolve. No crash, no error — the agent just never "sees" the upstream data. Triple-check spelling against Phase 4 before moving on.
5a
💰 Build agents/commercial_agent.py — Agent 5
Pure synthesis: launch sequencing, positioning, pricing APPROACH (never a fabricated number), risks
+90 XP
🚫 Never let this agent invent a price. "$1,200/month" with no payer-mix data behind it is a more dangerous hallucination than a fabricated headline — it sounds authoritative and board members may act on it directly. This agent recommends a pricing approach (value-based vs. reference pricing), never a specific number.
PHASE 5a PROMPT — Commercial Strategy Agent (no tools)
You are a Principal AI Systems Architect building the Enterprise Drug Launch Strategy Simulator on Google ADK. TASK: Build agents/commercial_agent.py — Agent 5 of 6. This agent has NO tools — pure reasoning over the 4 upstream reports. from google.adk.agents import LlmAgent commercial_agent = LlmAgent( name="commercial_strategy_agent", model="gemini-2.5-flash", description="Synthesizes market, scientific, competitive, and regulatory intelligence into a commercial launch strategy. Uses no external tools — pure reasoning over upstream agent outputs.", instruction="""You are a pharmaceutical commercial strategy lead. You have NO tools. Your job is pure synthesis and judgment over the four intelligence reports already gathered by upstream specialist agents, available to you as: Market Intelligence: {market_intelligence_report} Scientific Intelligence: {scientific_intelligence_report} Competitive Intelligence: {competitive_intelligence_report} Regulatory Intelligence: {regulatory_intelligence_report} Continue the instruction string requiring exactly these 5 output sections: 1. Launch Strategy Overview — sequencing recommendation drawing explicitly on the Market Intelligence ranking 2. Product Positioning — vs. competitors identified in Competitive Intelligence, using clinical evidence strength from Scientific Intelligence 3. Pricing Strategy Direction — explicitly: "do not invent a specific price point in absence of payer data; instead recommend a pricing APPROACH — e.g., value-based vs. reference pricing — justified by the disease burden and health-expenditure data in Market Intelligence" 4. Target Customer / Prescriber Profile 5. Commercial Risks — each one traceable to a specific fact from one of the four upstream reports, never a generic risk with no evidentiary anchor End the instruction with: "NEVER contradict a finding from an upstream report without explicitly flagging the contradiction and your reasoning for resolving it." Close with: tools=[], output_key="commercial_strategy_report", ) CRITICAL RULES: - The four {state_variable} names in the instruction string must be character-for-character identical to the output_key values from Phase 4 — copy-paste them, do not retype - tools=[] is intentional and correct — do NOT add tools to this agent, its entire value is disciplined reasoning over already-gathered evidence
5b
👑 Build agents/executive_agent.py — Agent 6
The final synthesis: SWOT, scores, timeline, risks, and the explicit Launch/Delay/Gather-Evidence/Do-Not-Launch decision rule
+90 XP
PHASE 5b PROMPT — Executive Decision Agent (no tools, final output)
You are a Principal AI Systems Architect building the Enterprise Drug Launch Strategy Simulator on Google ADK. TASK: Build agents/executive_agent.py — Agent 6 of 6, the FINAL agent in the pipeline. No tools. This produces the actual deliverable a board would read. from google.adk.agents import LlmAgent executive_agent = LlmAgent( name="executive_decision_agent", model="gemini-2.5-flash", description="Produces the final Executive Launch Dossier synthesizing all five upstream reports into a board-ready recommendation.", instruction="""You are presenting to the executive committee. You have NO tools. Synthesize ALL FIVE upstream reports, available to you as: Market Intelligence: {market_intelligence_report} Scientific Intelligence: {scientific_intelligence_report} Competitive Intelligence: {competitive_intelligence_report} Regulatory Intelligence: {regulatory_intelligence_report} Commercial Strategy: {commercial_strategy_report} Continue the instruction requiring this EXACT 8-section output structure, in this exact order: 1. Executive Summary (3-5 sentences: drug, headline recommendation, single most important reason why) 2. Market Opportunity Score [0-100] — justified by specific figures from Market Intelligence 3. SWOT Analysis — Strengths/Weaknesses/Opportunities/Threats, 2-4 bullets each, each traceable to a specific upstream finding 4. Launch Readiness Score [0-100] — weighted explicitly: scientific maturity 40%, regulatory risk 30%, competitive position 30%, show the weighted-component math, don't just assert a number 5. Recommended Launch Timeline — phase-gated, tied to clinical/regulatory milestones found upstream; if Phase 3 isn't complete yet, the timeline MUST reflect that realistically, not an aspirational date 6. Key Business Risks — top 5, ranked by severity, each traceable to a specific upstream fact 7. Success Probability [percentage]% — must correlate logically with Launch Readiness Score and Regulatory Risk Rating, with reasoning shown 8. Final Recommendation: [LAUNCH | DELAY | GATHER MORE EVIDENCE | DO NOT LAUNCH] Embed this EXACT decision rule in the instruction, verbatim, for the agent to apply and cite explicitly: - LAUNCH: Launch Readiness Score ≥ 70 AND Regulatory Risk is Low or Medium - GATHER MORE EVIDENCE: Scientific Maturity is low (early-phase trials only, sparse publications) but Market Opportunity is high — recommend specific additional evidence needed - DELAY: Regulatory Risk is High OR there is an unresolved Phase 3 trial outcome pending - DO NOT LAUNCH: Market Opportunity Score is very low AND no clear differentiation found in Competitive Intelligence - "State which rule applied and why, explicitly." Close with: tools=[], output_key="executive_launch_dossier", ) CRITICAL RULES: - All FIVE {state_variable} names must character-match the five output_key values from Phases 4 and 5a exactly - The decision rule must be applied explicitly and shown, not just asserted — a board reading this needs to see WHY a score crossed a threshold, not just the resulting label
06
PHASE 6 — COORDINATOR COMPOSITION (Gate 3)
Compose all 6 agents into one deterministic pipeline: a ParallelAgent for the 4 independent intelligence agents, wrapped by a SequentialAgent that runs synthesis strictly in order. Then prove real facts actually flow end to end.
100 XP
6
🔗 Build agents/coordinator_agent.py — root_agent + GATE 3 trace
ParallelAgent + SequentialAgent composition, named EXACTLY root_agent so ADK's CLI tooling discovers it
+100 XP
⚠️ Naming gotcha: ADK's adk run / adk web CLI tooling looks for a variable named exactly root_agent in the target module. Name it anything else and the CLI simply won't find your pipeline — no helpful error, it just won't discover it.
PHASE 6 PROMPT — root_agent Composition + End-to-End Trace
You are a Principal AI Systems Architect building the Enterprise Drug Launch Strategy Simulator on Google ADK. TASK: Build agents/coordinator_agent.py — the final composition wiring all 6 agents into one pipeline. Then run a real end-to-end trace. from google.adk.agents import SequentialAgent, ParallelAgent from agents.market_agent import market_agent from agents.scientific_agent import scientific_agent from agents.competitor_agent import competitor_agent from agents.regulatory_agent import regulatory_agent from agents.commercial_agent import commercial_agent from agents.executive_agent import executive_agent # Agents 1-4 have no interdependencies on each other's outputs — run them concurrently. intelligence_gathering = ParallelAgent( name="intelligence_gathering_block", sub_agents=[market_agent, scientific_agent, competitor_agent, regulatory_agent], description="Runs the four independent intelligence-gathering specialist agents concurrently.", ) # The full pipeline: gather in parallel, then synthesize strictly in order. root_agent = SequentialAgent( name="drug_launch_strategy_coordinator", sub_agents=[intelligence_gathering, commercial_agent, executive_agent], description="End-to-end Enterprise Drug Launch Strategy Simulator pipeline: parallel intelligence gathering, followed by sequential commercial synthesis and executive decision synthesis.", ) CRITICAL: the variable MUST be named root_agent (not coordinator_agent) — ADK's adk run / adk web CLI discovery specifically looks for this exact name in the module. NEXT — build tests/test_end_to_end.py (Gate 3): - Run the full pipeline via the ADK Runner against ONE complete scenario: drug_name="Pharmara-GLP1X", mechanism="dual GLP-1/GIP receptor agonist", indication="type 2 diabetes and obesity", candidate_countries=["India","Brazil","Indonesia","Mexico","Nigeria"] - Print the final executive_launch_dossier output in full - Then MANUALLY trace at least 3 specific facts in that final output back to a specific tool call's data (e.g., a specific GDP figure, a specific NCT trial ID, a specific competitor headline) — confirm the pipeline isn't silently falling back to the LLM's own unverified background knowledge instead of the real tool data GATE 3 — RUN THIS NOW: adk run agents/coordinator_agent DO NOT PROCEED TO PHASE 7 UNTIL: the full end-to-end run produces a final executive output that visibly contains real numbers/names threaded through from Phase 3's tool calls — not generic LLM-invented content. If you can't trace at least 3 specific facts back to a specific tool call, something upstream is silently failing even though no exception fired.
🚧 GATE 3 CHECKLIST
☐ root_agent is named exactly "root_agent" in coordinator_agent.py
☐ adk run agents/coordinator_agent executes without exceptions
☐ The final dossier contains all 8 required sections from Phase 5b
☐ You traced ≥3 specific facts in the output back to a specific tool call's actual data
07
PHASE 7 — HARDEN, GENERALIZE & SHIP (Gates 4-5)
Prove the system isn't overfit to one example, add documentation and input validation, then — only if everything above is rock-solid — wire the optional CrossRef/OpenAlex enrichment and dashboard.
150 XP
7
🚀 main.py, README.md, second-scenario Gate 4, optional Gate 5
CLI entry point, generalization proof, and the stretch-goal enrichment layer — never before Gate 4 is green
+150 XP
🎯 Why a SECOND test scenario matters: Gate 3 only proves the pipeline works for one example. A system that only works for "semaglutide / type 2 diabetes" might be silently overfit — prompts that quietly assume that exact drug class, or country list, sneak in more often than you'd expect. Gate 4 catches that by forcing a completely different input through the same pipeline.
PHASE 7 PROMPT — CLI, Docs, Generalization Test, Optional Enrichment
You are a Principal AI Systems Architect finishing the Enterprise Drug Launch Strategy Simulator build on Google ADK. TASK: Harden the system, prove it generalizes, then ship it. FILE 1: main.py - CLI entry point that prompts for: drug_name, mechanism, indication, candidate_countries (comma-separated, accept common country names — not just ISO codes) - Reject empty drug_name or indication with a clear error message, re-prompt - Invoke the root_agent pipeline via ADK's Runner with the collected inputs - Print the final executive_launch_dossier to stdout AND save it to output/executive_dossier_<drug_name>_<timestamp>.md FILE 2: README.md - Setup instructions (pip install, .env configuration) - Run instructions: `adk run agents/coordinator_agent` (CLI) or `adk web agents/` (browser UI) or `python main.py` - An architecture diagram (ASCII or mermaid) showing the ParallelAgent → SequentialAgent flow - A note on the single required credential: GOOGLE_API_KEY only FILE 3: CHANGELOG.md - Document any deviation you had to make from the original API contracts during this build (e.g., if ClinicalTrials.gov or openFDA had drifted since this prompt was written) — the exact diff you found and how you adapted, so future maintainers know exactly where to look first GATE 4 — RUN THIS NOW WITH A DIFFERENT SCENARIO: Run the full pipeline again with a SECOND, DIFFERENT test case — different drug, different indication, different countries than Phase 6 used. For example: drug_name="Cardiozyme-7", mechanism="PCSK9 inhibitor", indication="hypercholesterolemia and cardiovascular risk reduction", candidate_countries=["South Africa","Philippines","Vietnam","Egypt","Turkey"] DO NOT PROCEED UNTIL: this second, independent scenario also runs end-to-end successfully and produces a complete, well-formed Executive Launch Dossier — this proves the system generalizes rather than being overfit to the one example used while building it. GATE 5 (OPTIONAL — only attempt if Gates 0-4 are ALL green): - Build tools/crossref_openalex.py behind the USE_CROSSREF_ENRICHMENT flag from config.settings — additive enrichment to scientific_agent only, never required for the core pipeline to function - OPTIONAL stretch: a simple Streamlit or Gradio front-end that calls the same coordinator_agent and renders the 8 dossier sections visually - Never sacrifice Gate 4 stability to rush this — if you're not fully confident in Gates 0-4, skip Gate 5 entirely and ship without it
🚧 GATE 4 CHECKLIST (REQUIRED)
☐ A second, different drug/indication/country scenario runs end-to-end successfully
☐ main.py rejects empty drug_name/indication input
☐ README.md documents the single required credential clearly
☐ CHANGELOG.md exists, even if it just says "no deviations found"

🏛️ ALL 7 PHASES COMPLETE

You've built a deterministic, auditable, 6-agent enterprise system — not a chatbot. Head to "All Prompts" to grab any single phase again, "Knowledge Check" to test your understanding, or "Deploy" for the run-it cheat sheet.

ALL PROMPTS

Every prompt from the build, in one place. Jump to any phase, copy, and paste straight into VS Code or Antigravity — no need to scroll back through "Build It."

1 · Setup
2 · Config
3 · Tools
4 · Agents
5 · Synthesis
6 · Coordinator
7 · Ship
Phase 1 — Project Setup
Phase 2 — Config & Schema Contract
Phase 3a — ClinicalTrials.gov + PubMed
Phase 3b — World Bank + WHO GHO
Phase 3c — Generic RSS Tool
Phase 3d — openFDA + Gate 1 Smoke Test
Phase 4a — Market Intelligence Agent
Phase 4b — Scientific Intelligence Agent
Phase 4c — Competitive Intelligence Agent
Phase 4d — Regulatory Agent + Gate 2
Phase 5a — Commercial Strategy Agent
Phase 5b — Executive Decision Agent
Phase 6 — Coordinator + Gate 3
Phase 7 — Harden, Generalize & Ship
KNOWLEDGE CHECK

10 questions on the architecture decisions and API gotchas that actually matter — the kind of thing that separates "it ran once" from "I understand why it works."

Question 1 of 10
Why does the coordinator use a ParallelAgent for Agents 1-4 instead of an LLM-driven router deciding which agents to call?
All four intelligence agents are needed for EVERY drug launch decision — there's no conditional relevance to route around, so deterministic concurrency is strictly more reliable.
ParallelAgent is faster to write than a router, so it was chosen purely to save development time.
LLM routers aren't supported in Google ADK at all.
Question 2 of 10
A tool function returns status="empty" after calling ClinicalTrials.gov. What does this mean, and what should the agent do?
The API call failed — the agent should retry the request immediately.
Zero matching trials were found — a valid, meaningful outcome (often for a novel candidate) — the agent should state this plainly, not treat it as a failure.
The API key is invalid and needs to be regenerated.
Question 3 of 10
What's the critical gotcha in World Bank's API response shape?
It returns XML by default even when you ask for JSON.
The response is a 2-element array — response[0] is metadata, response[1] is the actual data rows. Naive response["results"] access silently breaks.
It requires an API key for any indicator other than GDP.
Question 4 of 10
Why can't you call PubMed's ESummary endpoint directly with a search term, skipping ESearch?
ESummary requires PMIDs as input, and PMIDs can only be obtained from ESearch first — it's architecturally a two-step API.
You can skip it — it's just a recommended best practice, not a requirement.
ESearch is only needed if you want results sorted by date.
Question 5 of 10
An HTTP 404 comes back from openFDA's /drug/label.json endpoint. What's the correct interpretation?
The openFDA service is down and the request should be retried with exponential backoff.
openFDA documents 404 as meaning zero results matched the search — this should be caught and converted to a typed "empty" result, not treated as an error.
The drug name was misspelled and the request needs fuzzy matching.
Question 6 of 10
What does the output_key parameter on an LlmAgent actually do?
It sets the file name the agent's output gets saved to on disk.
It names the session-state variable the agent's final response is written to, which downstream agents can then reference via {state_variable} templating in their own instructions.
It controls which Gemini model the agent uses.
Question 7 of 10
Why must coordinator_agent.py's final composed pipeline be named exactly "root_agent"?
ADK's adk run / adk web CLI tooling specifically looks for a variable with this exact name in the target module to discover the pipeline.
It's just a naming convention — any name works, "root_agent" is simply recommended in style guides.
Google's terms of service require this exact variable name for billing purposes.
Question 8 of 10
Why does the Commercial Strategy agent recommend a pricing "approach" instead of a specific price point?
Pricing recommendations are illegal for AI systems to generate under FDA regulations.
No payer-mix data feeds this system, so a specific fabricated number would be a confident-sounding hallucination a board could act on directly — more dangerous than other gaps because it looks authoritative.
Gemini models are not capable of generating numeric outputs.
Question 9 of 10
What's the purpose of running a SECOND, different test scenario at Gate 4, after Gate 3 already passed?
It's purely a formality with no real diagnostic value once Gate 3 has passed.
Gate 3 only proves the pipeline works for ONE example — a second, independent input catches prompts that quietly overfit to the first drug class, country list, or data shape used during building.
It's required because Gemini's free tier rate-limits identical requests.
Question 10 of 10
Why does tools/rss_reader.py implement ONE fetch_rss() function instead of separate parsers for Google News and FDA RSS?
FDA RSS and Google News use fundamentally incompatible XML schemas that happen to coincidentally parse the same way.
Both are standard RSS feeds parseable by the same feedparser-based logic — the feed_url parameter is what differs, not the parsing logic, so one function serves both (DRY) and a future FDA URL change is a one-line config fix.
Google ADK requires exactly one RSS tool per agent pipeline as a hard architectural limit.
DEPLOY & RUN

The cheat sheet for actually running your finished simulator — locally, in the browser, or as a one-off CLI report.

▶ Option 1 — ADK's built-in CLI
adk run agents/coordinator_agent

Interactive terminal session. You type the drug/indication/countries, the full 6-agent pipeline runs, and the dossier prints to your terminal.

▶ Option 2 — ADK's browser dev UI
adk web agents/

Opens a local browser UI where you can inspect each agent's individual tool calls and state writes — the best way to debug a specific agent's behavior visually.

▶ Option 3 — Your own CLI entry point
python main.py

Runs your Phase 7 main.py — input validation included — and saves the dossier to output/executive_dossier_<drug_name>_<timestamp>.md automatically.

PRE-FLIGHT CHECKLIST
✅ Before your first real run
☐ .env contains a real GOOGLE_API_KEY (free from Google AI Studio)
python -c "import google.adk" succeeds with no error
python tools/smoke_test_all.py prints 8/8 PASS
☐ All 6 agent output_key strings match exactly across every {state_variable} reference
☐ coordinator_agent.py's pipeline variable is named exactly root_agent
☐ At least 2 different drug/indication scenarios have run end-to-end successfully
⚠️ If a run fails silently (no exception, but generic/empty-feeling output): the most common cause is a {state_variable} name in Phase 5/6 that doesn't character-match a Phase 4 output_key. Check spelling before checking anything else — this is the single most common real-world bug in this exact architecture.