| name | adk-python |
| description | Google Agent Development Kit (ADK) for Python — verified API patterns for building production-grade multi-agent systems. Covers Workflow graphs (ADK 2.0 Beta), LlmAgent, SequentialAgent, LoopAgent, CustomAgent (BaseAgent), MCP integration via McpToolset, FunctionTool, ToolContext state, session management, context compaction, callbacks (before/after agent/model/tool), human input via RequestInput, and the App wrapper. Use when: ADK agent, workflow graph, multi-agent, MCP toolset, HITL, context compaction, session state, agent pipeline. |
| risk | low |
| source | adk.dev official docs (verified May 2026) |
| date_added | 2026-05-11 |
Google ADK Python — Verified API Skill
Source of truth: https://adk.dev/ (llms.txt index)
ADK 2.0 Beta docs: https://adk.dev/workflows/graph-routes/index.md
Install: pip install "google-adk[extensions]" --pre (for 2.0 Beta features)
Core Imports (Verified)
from google.adk.agents import LlmAgent
from google.adk.agents import BaseAgent
from google.adk.agents import SequentialAgent
from google.adk.agents import LoopAgent
from google.adk.agents import ParallelAgent
from google.adk import Workflow
from google.adk import Event
from google.adk.events import RequestInput
from google.adk.workflow import JoinNode
from google.adk.tools.function_tool import FunctionTool
from google.adk.tools.mcp_tool import McpToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
from google.adk.sessions import InMemorySessionService
from google.adk.runners import Runner
from google.adk.apps.app import App, EventsCompactionConfig
from google.adk.apps.llm_event_summarizer import LlmEventSummarizer
from google.adk.models import Gemini
Agent Types
LlmAgent (Primary)
root_agent = LlmAgent(
model='gemini-flash-latest',
name='my_agent',
instruction='Help the user with their task.',
tools=[my_function_tool, my_mcp_toolset],
)
SequentialAgent (Stable)
Runs sub-agents one after another. No LLM needed.
pipeline = SequentialAgent(
name="my_pipeline",
sub_agents=[agent_a, agent_b, agent_c]
)
LoopAgent (Stable)
Repeats sub-agents until max iterations or an agent signals exit.
quality_loop = LoopAgent(
name="quality_gate",
sub_agents=[drafter_agent, critic_agent],
max_iterations=3
)
ADK 2.0 Beta: Graph-Based Workflows (Workflow)
⚠️ ADK 2.0 Beta. Do not use in production.
The Workflow class defines deterministic directed graphs using an edges array. There is NO WorkflowAgent, add_node(), or add_edge() method. The graph is fully declarative.
Basic Sequential Graph
from google.adk import Workflow
root_agent = Workflow(
name="sequential_workflow",
edges=[("START", task_A_node, task_B_node)],
)
Conditional Routing
from google.adk import Workflow, Event
def router(node_input: str):
"""Return an Event with a route value to branch."""
if "urgent" in node_input:
return Event(route="URGENT_PATH")
return Event(route="NORMAL_PATH")
root_agent = Workflow(
name="routing_workflow",
edges=[
("START", task_A, router),
(router, {
"URGENT_PATH": urgent_handler,
"NORMAL_PATH": normal_handler,
}),
],
)
Human-in-the-Loop (HITL)
from google.adk.events import RequestInput
from google.adk import Workflow
def ask_user():
"""Pause execution and request human input."""
yield RequestInput(message="Select branch (1, 2, or 3):")
def process_input(node_input):
"""Process the human's response."""
return Event(output=f"User chose: {node_input}")
root_agent = Workflow(
name="hitl_workflow",
edges=[("START", ask_user, process_input)],
)
RequestInput Options
yield RequestInput(
message="Enter your choice:",
payload=some_data_dict,
response_schema=MyPydanticModel,
)
Parallel Fan-Out + Join
from google.adk.workflow import JoinNode
my_join = JoinNode(name="my_join")
root_agent = Workflow(
name="parallel_workflow",
edges=[
("START", task_A, my_join),
("START", task_B, my_join),
("START", task_C, my_join),
(my_join, final_task),
],
)
Nested Workflows
root_agent = Workflow(
name="parent_workflow",
edges=[
("START", task_A, router),
(router, {
"RUN_WORKFLOW_B": child_workflow_B,
"RUN_WORKFLOW_C": child_workflow_C,
}),
],
)
Tools
FunctionTool (Plain Python Function)
def get_weather(city: str) -> dict:
"""Get weather for a city."""
return {"city": city, "temp": "72°F"}
agent = LlmAgent(
model='gemini-flash-latest',
name='weather_agent',
tools=[get_weather],
)
ToolContext (State Access Inside Tools)
from google.adk.tools import ToolContext
def my_tool(query: str, tool_context: ToolContext) -> dict:
"""Tool with access to session state."""
count = tool_context.state.get('call_count', 0)
tool_context.state['call_count'] = count + 1
return {"result": query, "calls": count + 1}
McpToolset (Connect to MCP Servers)
from google.adk.tools.mcp_tool import McpToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
from mcp import StdioServerParameters
agent = LlmAgent(
model='gemini-flash-latest',
name='mcp_agent',
tools=[
McpToolset(
connection_params=StdioConnectionParams(
server_params=StdioServerParameters(
command='node',
args=['path/to/mcp-server.js'],
),
),
)
],
)
For remote MCP servers (HTTP/SSE):
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
McpToolset(
connection_params=StreamableHTTPConnectionParams(
url="https://example.com/mcp",
headers={"Authorization": "Bearer ..."}
)
)
Sessions & State
State Prefixes (Scope)
| Prefix | Scope | Persisted Across |
|---|
state["key"] | Session-scoped | Current session only |
state["app:key"] | App-scoped | All sessions in app |
state["user:key"] | User-scoped | All sessions for user |
state["temp:key"] | Temporary | Current invocation only |
Templating in Instructions
agent = LlmAgent(
name="greeter",
instruction="Hello {user_name}, you have {task_count} tasks.",
)
Runner (Execution)
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
session_service = InMemorySessionService()
session = await session_service.create_session(
state={}, app_name='my_app', user_id='user1'
)
runner = Runner(
app_name='my_app',
agent=root_agent,
session_service=session_service,
)
events = runner.run_async(
session_id=session.id,
user_id=session.user_id,
new_message=content,
)
async for event in events:
print(event)
Context Compaction (Anti-Amnesia)
Prevents context explosion during long sessions via sliding-window summarization.
from google.adk.apps.app import App, EventsCompactionConfig
from google.adk.apps.llm_event_summarizer import LlmEventSummarizer
from google.adk.models import Gemini
app = App(
name='my-agent',
root_agent=root_agent,
events_compaction_config=EventsCompactionConfig(
compaction_interval=3,
overlap_size=1,
summarizer=LlmEventSummarizer(
llm=Gemini(model="gemini-flash-latest")
),
),
)
Callbacks (Before/After Hooks)
Six callback types for observability and control:
| Callback | Signature | Blocking Behavior |
|---|
before_agent_callback | (callback_context) -> Optional[Content] | Returning Content bypasses entire agent run |
after_agent_callback | (callback_context) -> Optional[Content] | Appends additional response content |
before_model_callback | (callback_context) -> Optional[LlmRequest] | Modify the prompt before LLM call |
after_model_callback | (callback_context) -> Optional[LlmResponse] | Modify or replace LLM response |
before_tool_callback | (tool, args, tool_context) -> Optional[dict] | Returning a dict skips tool execution and uses that dict as result |
after_tool_callback | (tool, args, tool_context, result) -> Optional[dict] | Returning a dict replaces the original tool output |
Callbacks are set on the LlmAgent:
def policy_guard(tool, args, tool_context):
"""Block tool if kill-list word detected."""
if "DROP TABLE" in str(args):
return {"error": "BLOCKED by policy"}
return None
agent = LlmAgent(
model='gemini-flash-latest',
name='guarded_agent',
before_tool_callback=policy_guard,
after_tool_callback=my_after_tool_fn,
)
Tool Confirmation Flow
For high-stakes tools, ADK supports manual approval:
⚠️ Server-side tools (GoogleSearchTool, VertexAiSearchTool) do NOT trigger
before_tool_callback or after_tool_callback — they execute inside the Gemini
service. This is a critical blindspot for audit logging and security guardrails.
Custom Agents (BaseAgent)
For arbitrary orchestration logic beyond Sequential/Loop/Parallel:
from google.adk.agents import BaseAgent
class MyCustomAgent(BaseAgent):
my_sub_agent: LlmAgent
async def _run_async_impl(self, ctx):
"""Override this to define custom execution logic."""
async for event in self.my_sub_agent.run_async(ctx):
yield event
Key rules:
- Must inherit from
BaseAgent
- Must implement
_run_async_impl as an async generator
- Pass sub-agents to
super().__init__(sub_agents=[...]) for framework discovery
Sharp Edges (from Deep Research)
| Issue | Impact | Workaround |
|---|
| MCP stdio 5-second hardcoded timeout (v1.2.x) | Long-running tool calls fail | Custom session manager to extend timeout |
Workflow inherits BaseNode, not BaseAgent | after_agent_callback silently no-ops on Workflow roots | Use node-level callbacks or custom wrapper |
| Server-side tools skip callbacks | GoogleSearchTool/VertexAiSearchTool bypass before_tool_callback | Implement audit at model callback level |
| JoinNode hangs if upstream fails | Workflow stuck indefinitely | Ensure every fan-out node has error fallback output |
| Session state Pickle→JSON migration (v1.22) | Old Pickle-format DBs incompatible | Re-create sessions or migrate schema |
--pre flag doesn't override 1.x | pip install google-adk --pre fails if 1.x installed | Use pip install google-adk --pre --force |
| Workflow incompatible with live streaming | Graph workflows can't use real-time session modes | Use LlmAgent for live streaming use cases |
PolicyEvaluator (Governance)
ADK integrates with governance engines via the PolicyEvaluator protocol. The ADKPolicyEvaluator is a YAML-configurable engine wired into before_tool_callback.
Key principle: Monotonic Narrowing — when a parent agent delegates to a sub-agent, the sub-agent's permissions (tools, max iterations) are always a subset of the parent's. A sub-agent can never escalate privileges.
policies:
- name: "kill_list"
scope: ["*"]
action: "reject"
condition: "output contains banned_word"
- name: "api_key_leak"
scope: ["*"]
action: "reject"
condition: "output matches AIzaSy.*"
Migration: ADK 1.x → 2.0
- Python 3.10+ required (1.x supported 3.9)
- Install:
pip install google-adk --pre --force
Workflow replaces nested SequentialAgent/LoopAgent — but they can coexist (1.x agents work as nodes in 2.0 graphs)
- Session DB schema changed from Pickle blobs to queryable JSON
Optional parameters removed from builders for type safety
- Security fix in v2.0.1 — upgrade is mandatory for enterprise
CLI Commands
adk web
adk web --no-reload
adk run <agent_dir>
adk api_server
Related Skills
Works with: mcp-builder, gemini-api-dev, gemini-embedding-2, ai-agents-architect
Deep Research Source
Full 41-source technical reference: forge-references/agentic/ADK-2.0-Technical-Reference-Guide.md