Skip to main content

Quickstart: Google ADK Durable Workflows

Coming Soon

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


Setup

1

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
2

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.

3

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
4

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:

  1. Agent configuration is serialized and stored in workflow state
  2. Each LLM call (to Gemini) runs as a workflow activity with checkpointing
  3. Each tool execution runs as a separate activity
  4. 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

1

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

1

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:

  1. Dapr initializing the workflow runtime
  2. The agent executing tool calls (each as a durable activity)
  3. The final response from Gemini
2

Test Crash Recovery

To see durability in action:

  1. Start the agent
  2. While it's running, press Ctrl+C to simulate a crash
  3. Restart with the same command
  4. The workflow resumes from the last checkpoint

Key Concepts

ConceptDescription
WorkflowA durable orchestration that survives restarts
ActivityA single unit of work (LLM call or tool execution) that can be retried
CheckpointAutomatic state persistence after each activity completes
Retry PolicyBuilt-in retry with exponential backoff (3 attempts, 1s to 30s)

Next Steps


Clean Up

Stop the Dapr application:

dapr stop --app-id adk-agent