Skip to main content

Google ADK Agent Example

Learn how to run Google Agent Development Kit (ADK) agents in production with OmniDaemon using the Supervisor pattern.
πŸ’‘ Working code: examples/google_adk_with_supervisor/

The Challenge

When you build AI agents with Google ADK (or any framework), running them in production means dealing with:
  • ❌ Agent crashes - Memory errors, API failures, unexpected exceptions
  • ❌ Resource leaks - Connections that don’t close, memory that doesn’t free
  • ❌ No fault isolation - One agent’s problem affects your entire system
  • ❌ Manual recovery - You have to restart everything manually
Google ADK gives you great tools for building agents, but it doesn’t solve these operational challenges. That’s where OmniDaemon’s supervisors come in.

The Supervisor Solution

Supervisors run your Google ADK agent in an isolated process - like putting it in its own container.

What This Means for Your Google ADK Agent

Without Supervisor:
Single Process
β”œβ”€β”€ Event Bus
β”œβ”€β”€ Google ADK Agent (with sessions, MCP tools)
└── Another Agent
    ↓
If Google ADK crashes β†’ Everything dies ❌
With Supervisor:
Main Process
β”œβ”€β”€ Event Bus
└── Supervisor β†’ Google ADK Process (PID: 5678)
                 β”œβ”€β”€ Agent
                 β”œβ”€β”€ Sessions
                 └── MCP Tools
    ↓
If Google ADK crashes β†’ Supervisor restarts it βœ…
Other agents keep running βœ…

Why This Matters for Google ADK

Google ADK agents often:
  • Maintain session state for conversations
  • Connect to MCP servers for tools (filesystem, GitHub, etc.)
  • Make external API calls to Gemini or other LLMs
  • Hold persistent connections
All of these can fail. The Supervisor ensures:
  • βœ… Failures are isolated
  • βœ… Sessions can be recovered
  • βœ… MCP connections restart cleanly
  • βœ… No manual intervention needed
πŸ“š How supervisors work: Agent Supervisor Architecture explains the technical implementation details.

How It Works

❌ Simple Pattern (Not Production-Ready)

Running Google ADK directly in your main process:
# Google ADK agent in main process - risky
from google.adk.agents import LlmAgent

agent = LlmAgent(...)  # In main process
runner = Runner(agent=agent, ...)

async def callback(message: dict):
    # Run agent directly
    async for event in runner.run_async(...):
        if event.is_final_response():
            return event.content

await sdk.register_agent(callback=callback)  # No isolation!
Problems:
  • Agent crash kills main process
  • No automatic recovery
  • Sessions lost on crash

βœ… Supervisor Pattern (Production-Ready)

Running Google ADK in an isolated process:
# Supervisor manages Google ADK in separate process
supervisor = await create_supervisor_from_directory(
    agent_name="google_adk_agent",
    agent_dir="./google_adk_with_supervisor",  # Agent code here
    callback_function="callback.call_google_adk_agent"
)

await sdk.register_agent(
    callback=supervisor.handle_event  # Supervisor handles everything!
)
Benefits:
  • Process isolation βœ…
  • Auto-restart on crash βœ…
  • Session state preserved βœ…
  • MCP connections managed βœ…
The supervisor spawns a new Python process, loads your Google ADK agent, and manages its entire lifecycle.
πŸ“š Complete flow: Agent Process Flow shows exactly how events flow from OmniDaemon β†’ Supervisor β†’ Google ADK β†’ Response.

Implementation

Now that you understand why supervisors matter for Google ADK, let’s build it.

Prerequisites

# 1. Install OmniDaemon
pip install omnidaemon

# 2. Install Google ADK
pip install google-adk

# 3. Install LiteLLM (for non-Gemini models)
pip install litellm

# 4. Start Redis
docker run -d -p 6379:6379 --name redis redis:latest

# 5. Set API keys
export GOOGLE_API_KEY=your_key  # For Gemini
# Or
export OPENAI_API_KEY=your_key  # For OpenAI via LiteLLM

Directory Structure

google_adk_agent/
β”œβ”€β”€ __init__.py
β”œβ”€β”€ adk_agent.py      # Google ADK setup (runs in isolated process)
β”œβ”€β”€ callback.py       # Callback function (entry point)
└── requirements.txt  # Dependencies

Step 1: Google ADK Setup

Create google_adk_agent/adk_agent.py:
from google.adk.agents import LlmAgent
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset
from google.adk.models.lite_llm import LiteLlm
from google.adk.sessions import InMemorySessionService
from google.adk.runners import Runner

# Create session service (stores conversation history)
session_service = InMemorySessionService()

# Create Google ADK agent
agent = LlmAgent(
    # Use Gemini
    model="gemini-2.0-flash",
    
    # Or use OpenAI via LiteLLM
    # model=LiteLlm(model="openai/gpt-4o"),
    
    name="filesystem_agent",
    instruction="Help manage files",
    tools=[
        McpToolset(...)  # MCP filesystem tools
    ]
)

# Create runner
runner = Runner(
    agent=agent,
    app_name="filesystem_agent",
    session_service=session_service
)

Step 2: Callback Function

Create google_adk_agent/callback.py:
from google.genai import types
from .adk_agent import runner, session_service
import logging

logger = logging.getLogger(__name__)

async def call_google_adk_agent(message: dict):
    """
    This runs in the ISOLATED PROCESS managed by the supervisor.
    
    The supervisor:
    1. Spawned this process
    2. Loaded this module
    3. Calls this function when events arrive
    4. Returns results back via stdio
    """
    # Create session for this conversation
    await session_service.create_session(
        app_name="filesystem_agent",
        user_id="user_1",
        session_id="session_001"
    )
    
    query = message.get("content", "")
    logger.info(f"Processing: {query}")
    
    # Create Google ADK content
    content = types.Content(
        role="user",
        parts=[types.Part(text=query)]
    )
    
    # Run Google ADK agent (async streaming)
    async for event in runner.run_async(
        user_id="user_1",
        session_id="session_001",
        new_message=content
    ):
        if event.is_final_response():
            response = event.content.parts[0].text
            logger.info(f"Response: {response}")
            return response
    
    return "No response from agent"
Key Point: This entire callback runs in an isolated process. If it crashes, only this process dies - the supervisor restarts it automatically.

Step 3: Runner with Supervisor

Create agent_runner.py:
import asyncio
import logging
from omnidaemon import OmniDaemonSDK, AgentConfig, SubscriptionConfig
from omnidaemon.agent_runner.agent_supervisor_runner import (
    create_supervisor_from_directory,
    shutdown_all_supervisors,
)

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

sdk = OmniDaemonSDK()
_supervisor = None

async def get_supervisor():
    """Initialize supervisor for Google ADK agent"""
    global _supervisor
    if _supervisor is None:
        _supervisor = await create_supervisor_from_directory(
            agent_name="google_adk_agent",
            agent_dir="./google_adk_agent",
            callback_function="callback.call_google_adk_agent"
        )
    return _supervisor

async def call_supervised_agent(message: dict):
    """Delegate to supervisor"""
    supervisor = await get_supervisor()
    return await supervisor.handle_event(message)

async def main():
    try:
        logger.info("Initializing Google ADK agent with supervisor...")
        await get_supervisor()  # Spawn isolated process
        
        await sdk.register_agent(
            agent_config=AgentConfig(
                name="google_adk_agent",
                topic="file.tasks",
                callback=call_supervised_agent,
                config=SubscriptionConfig(
                    reclaim_idle_ms=6000,
                    dlq_retry_limit=3,
                    consumer_count=3,
                ),
            )
        )
        
        logger.info("Starting OmniDaemon...")
        await sdk.start()
        logger.info("βœ… Google ADK agent running in isolated process.")
        
        await asyncio.Event().wait()
        
    except KeyboardInterrupt:
        logger.info("Shutdown signal received")
    finally:
        logger.info("Shutting down...")
        await sdk.shutdown()
        await shutdown_all_supervisors()  # Clean shutdown of agent process
        logger.info("Shutdown complete")

if __name__ == "__main__":
    asyncio.run(main())

Step 4: Run It

python agent_runner.py
Your Google ADK agent is now running in an isolated process, managed by the supervisor!

Using Different LLM Providers

Google ADK works with any LLM via LiteLLM:
# Gemini (native)
model="gemini-2.0-flash"

# OpenAI
model=LiteLlm(model="openai/gpt-4o")

# Anthropic
model=LiteLlm(model="anthropic/claude-3-opus")

# Azure, Mistral, Together AI, etc.
model=LiteLlm(model="azure/gpt-4")
model=LiteLlm(model="mistral/mistral-large")
Just change the model config - the supervisor pattern stays the same.

Session Management

Google ADK uses sessions to maintain conversation history. With supervisors, sessions persist across restarts:
# Multi-user sessions
async def call_google_adk_agent(message: dict):
    # Extract user from message
    user_id = message.get("tenant_id", "default_user")
    session_id = f"session_{user_id}"
    
    # Each user gets their own session
    await session_service.create_session(
        app_name="my_app",
        user_id=user_id,
        session_id=session_id
    )
    
    # Run with user-specific session
    async for event in runner.run_async(
        user_id=user_id,
        session_id=session_id,
        new_message=content
    ):
        ...
If the agent process crashes and restarts, sessions are recreated automatically by the supervisor.

Production Deployment

Environment Variables

# .env
GOOGLE_API_KEY=your_key
REDIS_URL=redis://prod-redis:6379
STORAGE_BACKEND=redis
LOG_LEVEL=INFO

Horizontal Scaling

# Run multiple instances - automatic load balancing
python agent_runner.py &  # Instance 1
python agent_runner.py &  # Instance 2
python agent_runner.py &  # Instance 3
Each instance runs its own isolated Google ADK process. OmniDaemon distributes events across all instances.

Monitoring

# Health check
omnidaemon health

# Metrics
omnidaemon metrics --topic file.tasks

# Failed events
omnidaemon bus dlq --topic file.tasks

Complete Working Example

See the full production implementation: πŸ“ examples/google_adk_with_supervisor/ This example shows:
  • Complete Google ADK setup
  • MCP filesystem tools
  • Session management
  • Process isolation with supervisors
  • Error handling
  • Graceful shutdown

Understanding the Architecture

Process Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Main Process                      β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ AgentSupervisor              β”‚  β”‚
β”‚  β”‚ - Spawns subprocess          β”‚  β”‚
β”‚  β”‚ - Monitors health            β”‚  β”‚
β”‚  β”‚ - stdio communication        β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚             β”‚ JSON over stdio       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              ↓
       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
       β”‚ Google ADK Process           β”‚
       β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
       β”‚ β”‚ callback.py              β”‚ β”‚
       β”‚ β”‚ - Google ADK Agent       β”‚ β”‚
       β”‚ β”‚ - Session Service        β”‚ β”‚
       β”‚ β”‚ - MCP Tools              β”‚ β”‚
       β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Understanding Supervisors: Implementation Details: Other Examples:

Key Takeaways

βœ… Supervisors make Google ADK production-ready - Process isolation prevents cascading failures
βœ… Session state is preserved - Sessions survive process restarts
βœ… Works with any LLM - Gemini, OpenAI, Anthropic via LiteLLM
βœ… Simple integration - create_supervisor_from_directory() handles complexity
βœ… Auto-recovery - Crashed agents restart automatically with sessions intact
Google ADK is excellent for building agents. Supervisors make them production-ready. Together with OmniDaemon, you get fault-tolerant, scalable AI systems.
Next: Check out the content moderation example for a multi-agent pipeline using supervisors.