Google ADK

Google's agentic framework for Gemini and Vertex AI.

Introduction

Google ADK (Agent Development Kit) is Google's first-party framework for building agentic AI systems natively on Gemini and Google Cloud, released in 2025. Where LangGraph is a community-built graph engine that works with any LLM provider, ADK is Google's opinionated, Gemini-native orchestration layer withWorkflow, Context-based state management, event streaming, and a direct path to Vertex AI Agent Engine deployment built in.

The core model is straightforward: Agent objects wrap a Gemini model, a system instruction, and an output_key that names where their output lands in shared state. Workflow objects compose agents with Edge definitions, enabling linear sequences and conditional retry loops. A Runner ties everything together with a session service and streams events asynchronously.

Quick Navigation

1. When to Use

GCP, Vertex AI, Gemini-native requirements

2. LangGraph vs ADK

Side-by-side comparison across 8 dimensions

3. Project Setup

Multi-Agent Blogger project walkthrough

4. Agent Architecture

Agent, Workflow, routing functions, Runner

5. Running the Project

ADK dev UI and programmatic runner

When to Use Google ADK

Deep GCP / Vertex AI Integration

If your infrastructure runs on Google Cloud (Vertex AI, Cloud Run, Cloud Storage, BigQuery), ADK plugs in natively. Deployment to Vertex AI Agent Engine requires zero extra wiring, and IAM, logging, and monitoring all use your existing GCP setup.

Native Gemini Features

ADK is built around Gemini. Multimodal inputs, grounding with Google Search, and native function calling are first-class features, not adapter layers bolted on after the fact. If you need Gemini-specific capabilities, ADK is the intended path.

Dynamic Agentic Task Automation

ADK's Workflow class supports retry loops, conditional routing via plain Python functions wrapped with node(), and Edge back-edges out of the box. Modeling multi-step tasks with self-correction is the primary use case.

Enterprise Google Cloud Environments

Teams already using Workspace, Cloud Identity, and GCP billing will find ADK integrates with their existing security, observability, and cost management tooling without additional connectors or configuration.

Avoid ADK when...
  • Your stack runs on AWS or Azure with no plans to adopt Vertex AI
  • You need to call non-Gemini models - ADK is designed for Gemini; other providers require custom adapters
  • You need the broad LangChain integration ecosystem: 100+ LLM providers, vector stores, and community tools
  • Your team is already productive with LangGraph and there is no concrete GCP migration on the roadmap

LangGraph SDK vs Google ADK

Both frameworks orchestrate multi-step, stateful agentic workflows, but from different philosophies. LangGraph is a general-purpose graph engine from the open-source community; ADK is a vertically integrated, first-party Google product optimized for Gemini and GCP.

DimensionLangGraphGoogle ADK
ProviderOpen-source (LangChain Inc)First-party Google Cloud
Graph ModelDirected graph with explicit nodes and conditional edgesWorkflow with Edge definitions and node() routing
State ManagementExplicit TypedDict schema passed between nodesctx.state dict on the Context object
Control FlowConditional edges via Python functions returning node namesEdge(from_node=..., route="...") matched against routing function return values
Tool Integration@tool decorator and bind_tools() on the modelNative Gemini function calling inside Agent
Model FlexibilityAny LLM: Anthropic, OpenAI, Gemini, Ollama, and moreOptimized for Gemini; other providers require custom adapters
DebuggingLangSmith tracing, in-memory state inspectionADK event streaming via runner.run_async(), Vertex AI logs
Use CaseAny LLM provider, complex graph topologies, open ecosystemGCP-native teams, Gemini-first workflows, Vertex AI deployment
They Are Not Mutually Exclusive

If your team is already productive with LangGraph, ADK does not replace it. Choose ADK when Vertex AI deployment, native Gemini features, or enterprise GCP integration are hard requirements, not simply because you are building agentic systems. The two can even coexist in the same organization, with different teams choosing based on their infrastructure context.

Project Setup: Multi-Agent Blogger

The example project is a Multi-Agent Blogger: a pipeline that takes a topic, plans an outline with retry logic, writes the full article with retry logic, then generates alternative titles and a social media hook. It demonstrates ADK's core patterns: Agent, Workflow, routing functions wrapped with node(), and the Runner + session model.

Project Structure

agents-google-adk/
├── .env                 # GOOGLE_API_KEY=your_key_here
├── requirements.txt
└── blogger/
    ├── __init__.py
    ├── agent.py         # All agents, routing functions, root_agent (Workflow)
    └── main.py          # Runner, session service, async entry point

requirements.txt

google-adk==2.2.0
python-dotenv>=1.0.0

Installation

pip install -r requirements.txt
API Key

Set GOOGLE_API_KEY=your_key_here in your .env file. Get a free key at Google AI Studio. Gemini Flash is free-tier eligible with generous rate limits for development and testing.

Agent Architecture

The Blogger pipeline has three layers. Leaf Agent objects call Gemini and write results to a named output_key in shared ctx.state. Workflow objects group leaf agents into retry loops with conditional routing. The root Workflow sequences the three stages in order.

1. Leaf Agents

Agent is the fundamental unit. Each one has a model (a Gemini model string), an instruction (its system prompt), and an output_key. When an agent runs, it calls Gemini and writes the response text to ctx.state[output_key]. Downstream agents see that value because Gemini receives the full state as context when building the next prompt.

from datetime import date

from dotenv import load_dotenv
from google.adk.agents import Agent, Context
from google.adk.workflow import Edge, START, Workflow, node

load_dotenv()
MODEL = "gemini-flash-latest"

blog_planner = Agent(
    name="BlogPlanner",
    description="Creates a practical, skimmable outline in Markdown",
    model=MODEL,
    output_key="blog_outline",
    instruction="""
As a technical content strategist, produce a clear Markdown outline with:
- Title
- Short intro
- 2 main sections.
- Conclusion

If `codebase_context` exists in state, weave in specific sections/snippets.
Return only the outline in Markdown.
""",
)

outline_validator = Agent(
    name="OutlineValidator",
    description="Validates the outline.",
    model=MODEL,
    instruction="""
Check the outline (`blog_outline`) in state. If it has a title, intro,
2 sections and a conclusion, respond "OK", otherwise return "RETRY" and
list missing pieces.""",
    output_key="validation_result",
)

2. Routing Functions

Routing functions are plain Python functions that receive a Context, readctx.state, and return a string. That string is matched againstroute= values in Edge definitions to decide the next node. Wrap the function with node() to make it composable inside a Workflow. The tries >= 3 guard is essential: it ensures the loop exits even if the validator never returns "OK".

def _route_outline(ctx: Context) -> str:
    result = ctx.state.get("validation_result", "")
    tries = ctx.state.get("planner_tries", 0) + 1
    ctx.state["planner_tries"] = tries
    return "OK" if result.strip().upper().startswith("OK") or tries >= 3 else "RETRY"

route_outline = node(_route_outline, name="RouteOutline")

3. Workflow with Retry Loop

Workflow is ADK's orchestrator. A tuple (START, a, b, c) means a linear sequence. Edge(from_node=route_outline, to_node=blog_planner, route="RETRY") adds a conditional back-edge: if route_outline returns "RETRY", execution loops back to blog_planner for another attempt.

resilient_planner = Workflow(
    name="ResilientPlanner",
    description="Generates and validates a blog outline, retrying up to 3 times",
    edges=[
        (START, blog_planner, outline_validator, route_outline),
        Edge(from_node=route_outline, to_node=blog_planner, route="RETRY"),
    ],
)

4. The Writer Stage

The second stage is the first one again with different nouns, which is the point: once you have produce, validate, route, you reuse the shape. BlogWriter turns the outline into an article, BlogPostValidator judges it, androute_post loops back on "RETRY" with its own independent writer_tries counter. Note that both stages write their verdict to the same validation_result key, which is safe only because the stages run in sequence and each routing function reads it immediately after its own validator.

# The writer stage mirrors the planner stage exactly: an agent that
# produces, a validator that judges, and a routing function that loops.

def _route_post(ctx: Context) -> str:
    result = ctx.state.get("validation_result", "")
    tries = ctx.state.get("writer_tries", 0) + 1
    ctx.state["writer_tries"] = tries
    return "OK" if result.strip().upper().startswith("OK") or tries >= 3 else "RETRY"

route_post = node(_route_post, name="RoutePost")

blog_writer = Agent(
    name="BlogWriter",
    description="Blog Writer Agent",
    instruction="""
Write a complete Markdown article from the outline in `blog_outline`
Guidelines:
- Audience: Software Engineers
- Explain both the `how` and `why`
- Include concise code snippets when helpful
- Output only the final article in Markdown (no fence around the whole post)""",
    model=MODEL,
    output_key="blog_post",
)

post_validator = Agent(
    name="BlogPostValidator",
    description="Validates the final post.",
    model=MODEL,
    instruction="""
Check the final post (`blog_post`) in state. If it has a title, intro,
clear sections matching the outline, conclusion and is technically accurate
respond "OK", otherwise return "RETRY" with the specific fixes.""",
    output_key="validation_result",
)

resilient_writer = Workflow(
    name="ResilientWriter",
    description="Generates and validates a blog post, retrying up to 3 times",
    edges=[
        (START, blog_writer, post_validator, route_post),
        Edge(from_node=route_post, to_node=blog_writer, route="RETRY"),
    ],
)

final_agent = Agent(
    name="FinalAgent",
    description="Generates alternative titles and tweet-length hooks",
    model=MODEL,
    instruction=f"""
Based on the blog post in `blog_post` state, provide:
- 2 alternative titles
- 2 tweet-length hooks

Date: {date.today().isoformat()}
""",
)

5. Root Workflow

The root Workflow chains the three stages. resilient_planner runs first and writes blog_outline to state. resilient_writer runs second, readsblog_outline (via Gemini's context), and writes blog_post.final_agent runs last to generate the titles and tweet hook.

root_agent = Workflow(
    name="Blogger",
    description="Plans and writes a complete blog post end-to-end.",
    edges=[
        (START, resilient_planner, resilient_writer, final_agent),
    ],
)

Architecture Diagram

Multi-Agent Blogger Architecture

BloggerRoot WorkflowResilientPlannerWorkflowResilientWriterWorkflowFinalAgentAgentBlogPlanneroutput_key: blog_outlineOutlineValidatoroutput_key: validation_resultRouteOutlineOK / RETRYBlogWriteroutput_key: blog_postBlogPostValidatoroutput_key: validation_resultRoutePostOK / RETRYRETRYRETRY

Figure 1: Three-stage pipeline: plan with retry, write with retry, then finalize. Dashed arrows are conditional back-edges.

How Agents Share Data via output_key

BlogPlanner writes its output to ctx.state["blog_outline"] viaoutput_key="blog_outline". OutlineValidator does not receive blog_outline as an argument: it sees it because ADK passes the fullctx.state as context to Gemini when building the next prompt. Downstream agents reference state keys in their instruction text (e.g. "check the outline (blog_outline) in state"), and Gemini resolves them. This is the key ADK data-passing pattern, no explicit argument threading required.

Running the Project

First, Check That It Assembles

Every run below costs Gemini calls, and a typo in an edge or a renamed ADK symbol will not surface until the graph is already executing. Importing the module builds everyAgent and Workflow object without calling a model, so this catches structural mistakes for free:

# smoke_test.py - check the pipeline assembles before spending an API call.
#
# Importing agent.py builds every Agent and Workflow object. If a name is
# wrong, an edge points at a node that does not exist, or the installed
# google-adk has moved on, this fails here rather than three Gemini calls
# into a run.

from blogger.agent import resilient_planner, resilient_writer, root_agent

for wf in (resilient_planner, resilient_writer, root_agent):
    print(f"{wf.name:18s} {len(wf.edges)} edge group(s)")

print("\nroot pipeline:", root_agent.description)
Expected Output:
ResilientPlanner   2 edge group(s)
ResilientWriter    2 edge group(s)
Blogger            1 edge group(s)

root pipeline: Plans and writes a complete blog post end-to-end.

Two edge groups per resilient stage: the forward sequence and the "RETRY" back-edge. One for the root, which is a plain three-step sequence with no loop of its own.

ADK Dev Web UI

The fastest way to try the project is the ADK development interface. From the project root:

adk web blogger/

Opens the ADK dev interface at http://localhost:8000. Send a topic and watch each agent step execute in real time.

Programmatic Runner

For scripted or automated use, run via the module entry point:

python -m blogger.main "The future of agentic AI systems"
import asyncio
import sys

from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types

from .agent import root_agent

async def run(topic: str) -> None:
    session_service = InMemorySessionService()
    await session_service.create_session(
        app_name="blogger", user_id="local", session_id="run-1"
    )
    runner = Runner(
        node=root_agent,
        app_name="blogger",
        session_service=session_service,
    )
    async for event in runner.run_async(
        user_id="local",
        session_id="run-1",
        new_message=types.Content(
            role="user",
            parts=[types.Part(text=topic)],
        ),
    ):
        if event.content and event.content.parts:
            for part in event.content.parts:
                if part.text:
                    print(part.text, end="", flush=True)
    print()


if __name__ == "__main__":
    TOPIC = " ".join(sys.argv[1:]) or "Introduction to Google ADK multi-agent systems"
    asyncio.run(run(TOPIC))
Streaming Events

runner.run_async() is an async generator that yields an ADK event for every agent step. The loop filters for text parts and prints them incrementally, so you see the article being written as each agent completes rather than waiting for the full pipeline to finish.

Key Takeaways

  • Agent is the leaf unit - model, instruction, and output_key are the three required pieces; agents communicate via named state keys, not explicit argument passing
  • Workflow is the orchestrator - (START, a, b, c) defines a linear sequence; Edge(from_node=x, to_node=y, route="R") adds a conditional back-edge for retry loops
  • Routing functions are plain Python - wrap with node(), receiveContext, read ctx.state, return a route string matched against Edge.route values
  • output_key is how agents hand data forward - downstream agents see prior output because Gemini receives the full state as context when building each prompt
  • Runner + InMemorySessionService form the runtime - one session per run, events streamed asynchronously via runner.run_async()
  • Choose ADK for Gemini-native GCP workflows - choose LangGraph for multi-provider flexibility, complex graph topologies, and the open-source ecosystem
  • Always guard retry loops - a tries >= N condition in every routing function prevents infinite loops when a validator never returns "OK"
  • Smoke-test the graph before running it - importing the module builds every agent and workflow without calling Gemini, so a bad edge or a renamed symbol fails instantly instead of several paid calls into a run