Quickstart: Google ADK Durable Workflows
This quickstart is coming soon. Stay tuned!
Run Google ADK agents with durable execution using Dapr Workflows. Each tool call becomes a checkpoint — if your agent crashes, it resumes exactly where it left off without re-executing completed work.
What You'll Build
A Google ADK agent that:
- Survives crashes — Workflow state is checkpointed after each tool execution
- Retries on failure — Failed LLM calls and tools retry with exponential backoff
- Resumes from checkpoint — Restart the app and pick up where you left off
Why Durable Agent Execution?
Long-running agents are fragile. Network errors, API rate limits, or process restarts can lose all context and data. Dapr Workflows make your agents production-ready:
- Automatic checkpointing — Every tool call is persisted before execution
- Crash recovery — On restart, the workflow replays from the last checkpoint
- Built-in retries — Configurable retry policies handle transient failures
- Observability — Track workflow state and debug failures
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Dapr Workflow Runtime │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Google ADK Agent Workflow │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ LLM Call │────▶│Tool Exec │────▶│ LLM Call │ │ │
│ │ │(Activity)│ │(Activity)│ │(Activity)│ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ ▼ │ │
│ │ [Checkpoint] [Checkpoint] [Checkpoint] │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Each LLM call and tool execution runs as a workflow activity. If the process crashes after any checkpoint, the workflow resumes from that point — previous activities won't re-execute.
Prerequisites
- Dapr CLI installed and initialized (
dapr init) - Python 3.11+
- A Google API key for Gemini
Setup
Create Project Structure
Create a new project directory:
mkdir adk-durable && cd adk-durable
mkdir -p components
Your project structure:
adk-durable/
├── components/
│ └── statestore.yaml
├── agent.py
└── requirements.txt
Configure Dapr State Store
Create components/statestore.yaml:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
- name: actorStateStore
value: "true"
This uses Redis for workflow state persistence. Dapr initializes Redis automatically with dapr init.
Install Dependencies
Create requirements.txt:
dapr-ext-adk>=0.1.0
google-adk>=0.1.0
Set up a virtual environment and install:
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
Set Your Google API Key
export GOOGLE_API_KEY="your-api-key-here"
Get your API key from Google AI Studio.
How It Works
The DaprWorkflowAgentRunner wraps your Google ADK agent in a Dapr workflow:
- Agent configuration is serialized and stored in workflow state
- Each LLM call (to Gemini) runs as a workflow activity with checkpointing
- Each tool execution runs as a separate activity
- Tools execute through Google ADK's full context system
This means:
- LLM API calls that succeeded won't be repeated
- Tool executions that completed won't re-run
- You only pay for the work that actually needs to be done
Build the Agent
Create the Durable Agent
Create agent.py:
import asyncio
from google.adk.agents import LlmAgent
from google.adk.tools import FunctionTool
from dapr.ext.adk import DaprWorkflowAgentRunner
# Define tools as regular Python functions
def get_weather(city: str) -> str:
"""Get the current weather for a city.
Args:
city: The name of the city to get weather for.
Returns:
A string describing the weather.
"""
# In production, call a real weather API
weather_data = {
"tokyo": "Sunny, 22°C",
"london": "Cloudy, 15°C",
"new york": "Rainy, 18°C",
"paris": "Partly cloudy, 20°C",
}
return weather_data.get(city.lower(), f"Weather data not available for {city}")
def get_time(timezone: str) -> str:
"""Get the current time in a timezone.
Args:
timezone: The timezone (e.g., 'JST', 'UTC', 'EST').
Returns:
A string with the current time.
"""
from datetime import datetime, timedelta
offsets = {"jst": 9, "utc": 0, "est": -5, "pst": -8, "cet": 1}
offset = offsets.get(timezone.lower(), 0)
time = datetime.utcnow() + timedelta(hours=offset)
return f"Current time in {timezone.upper()}: {time.strftime('%H:%M:%S')}"
def calculate(expression: str) -> str:
"""Evaluate a mathematical expression.
Args:
expression: A mathematical expression to evaluate (e.g., '2 + 2').
Returns:
The result of the calculation.
"""
try:
allowed_chars = set("0123456789+-*/(). ")
if not all(c in allowed_chars for c in expression):
return "Error: Invalid characters in expression"
result = eval(expression)
return f"{expression} = {result}"
except Exception as e:
return f"Error: {e}"
async def main():
print("=" * 60)
print("Google ADK Agent with Dapr Workflow - Durable Execution")
print("=" * 60)
# Create the ADK agent with tools
agent = LlmAgent(
name="helpful_assistant",
model="gemini-2.0-flash",
instruction="""You are a helpful assistant that can check weather,
time, and do calculations. Be concise in your responses.""",
tools=[
FunctionTool(get_weather),
FunctionTool(get_time),
FunctionTool(calculate),
],
)
print(f"\nAgent: {agent.name}")
print(f"Model: gemini-2.0-flash")
print(f"Tools: get_weather, get_time, calculate")
print("-" * 60)
# Create the Dapr Workflow runner
runner = DaprWorkflowAgentRunner(
agent=agent,
max_iterations=10,
)
# Start the workflow runtime
print("\nStarting Dapr Workflow runtime...")
runner.start()
try:
# Example conversation
user_message = "What's the weather in Tokyo and what time is it there (JST)?"
print(f"\nUser: {user_message}")
print("-" * 60)
# Run the agent
async for event in runner.run_async(
user_message=user_message,
session_id="example-session-001",
user_id="example-user",
app_name="adk-example",
):
event_type = event["type"]
if event_type == "workflow_started":
print(f"Workflow started: {event.get('workflow_id')}")
elif event_type == "workflow_status_changed":
print(f"Status: {event.get('status')}")
elif event_type == "workflow_completed":
print(f"\nWorkflow Completed!")
print(f"Iterations: {event.get('iterations')}")
if event.get('final_response'):
print(f"\nAssistant: {event.get('final_response')}")
elif event_type == "workflow_failed":
print(f"Workflow FAILED: {event.get('error')}")
print("\n" + "=" * 60)
print("Workflow execution complete!")
print("=" * 60)
finally:
print("\nShutting down...")
runner.shutdown()
if __name__ == "__main__":
asyncio.run(main())
Run the Agent
Start with Dapr
Run the agent with Dapr:
dapr run --app-id adk-agent \
--dapr-grpc-port 50001 \
--resources-path ./components \
-- python agent.py
You'll see:
- Dapr initializing the workflow runtime
- The agent executing tool calls (each as a durable activity)
- The final response from Gemini
Test Crash Recovery
To see durability in action:
- Start the agent
- While it's running, press
Ctrl+Cto simulate a crash - Restart with the same command
- The workflow resumes from the last checkpoint
Key Concepts
| Concept | Description |
|---|---|
| Workflow | A durable orchestration that survives restarts |
| Activity | A single unit of work (LLM call or tool execution) that can be retried |
| Checkpoint | Automatic state persistence after each activity completes |
| Retry Policy | Built-in retry with exponential backoff (3 attempts, 1s to 30s) |
Next Steps
- Add session management for persistent agent memory
- Deploy to Kubernetes with Catalyst
- Explore other agent frameworks for different use cases
Clean Up
Stop the Dapr application:
dapr stop --app-id adk-agent