When you need this. Your documentation assistant now has several jobs: draft an announcement, check its claims, prepare upgrade steps and ask for approval. Before adding agents, decide which steps depend on each other and who chooses what happens next.
This lesson compares seven workflow patterns using the course’s docuctl documentation tools. You will choose a control structure from the dependencies and failure policy. An optional inspection script lets you compare the actual course graphs and agent trees without calling a model.
The three workflow types
A workflow type says where control lives. A pattern describes how work is arranged. Several patterns can use the same workflow type.
- Graph. A
Workflowdeclares nodes andedges. Code fixes the available paths; a router selects a named branch. A model can supply a router’s input, so a fixed graph can still take different paths. - Dynamic. A
@nodefunction starts children withctx.run_node. Python controls loops, fan-out and error handling when the structure depends on the input. - Collaborative. A coordinator agent delegates through
sub_agentsorAgentTool. The model chooses which specialist to call and what to ask it.
A deprecated template agent. In the pinned SDK, constructing SequentialAgent, LoopAgent or ParallelAgent emits a deprecation warning in favor of Workflow. Use the workflow implementations linked below when adapting an older example. The offline suite exercises those warnings.
The seven patterns
Coordinator and dispatcher
A dispatcher selects a specialist. Use a function router when the decision follows a rule, such as sending a broken-link report to triage. Use a coordinator Agent when interpreting the request is the task; its model proposes a delegation tool call, and ADK invokes the selected specialist. Compare conditional edges with collaborative delegation. A coordinator Agent can choose the wrong specialist; a rule needs an explicit fallback for unexpected input.
Sequential pipeline
Each step needs the previous step’s result. For example, parse a git log, generate grouped release notes, then validate the result. Build a graph workflow implements that sequence. A failed node stops later work; it does not undo earlier effects.
Parallel fan-out and gather
Independent branches read the same input and produce separate outputs. A join collects them. Use this for highlights, upgrade steps and a link report that can be prepared independently. Parallel fan-out and gather compares a graph that requires every branch with a dynamic parent that keeps results when an optional check fails.
Hierarchical task decomposition
A planner delegates a section, and the section writer delegates research. Each call returns its result to its caller. Use this when subtasks need their own instructions and context. Hierarchical task decomposition builds the tree with AgentTool. Depth and repeated delegation both increase work, so the tree needs a depth limit and a shared call budget.
Generate and review
A writer produces a draft; a reviewer compares it with source facts; code accepts or rejects the result. Use it when a draft needs a dedicated check before the caller uses it. Generate and review returns a release announcement or reasons for rejection. There is no automatic revision loop, and a model reviewer can miss an unsupported claim.
Iterative refinement
A failed check returns feedback to a reviser, then the check runs again. Use it when the check can identify a repairable problem. Iterative refinement checks a changelog line’s format and required names. The loop must also stop when its revision limit is reached; more attempts cannot fix contradictory requirements.
Human-in-the-loop
A run pauses for a person’s decision and resumes with the reply. Use it when an action requires a human decision. Pause a workflow for human approval uses RequestInput before a schema migration. The workflow stays paused until a reply arrives, and the reply must be validated before the action continues.
Compare the trade-offs
| Requirement | Start with | Decision to make |
|---|---|---|
| Known order and required steps | Sequential graph | Which failures stop later nodes? |
| A rule selects the next step | Graph router | What catches an unmatched route? |
| Independent outputs | Fan-out and join | Must every branch succeed? |
| A draft needs a dedicated check | Generate and review | What source facts does the reviewer receive? |
| A check can guide repairs | Refinement loop | What bounds revisions? |
| The model must discover subtasks | Collaborative tree | What bounds depth and total calls? |
| A person must decide | Human input | What reply is valid, and how is it resumed? |
Choose from the dependency structure first. A fixed sequence does not need a planner. Independent branches do not need to delegate to one another. Keep decisions in code when the rule is already known.
Print every pattern’s graph
This optional inspection needs Python 3.12+ and the pinned ADK dependencies. Download code contains the inspector, its expected map and tests. The ZIP includes the lesson modules and their documentation under patterns/. Extract it and open a terminal in adk-workflow-patterns/:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements-lock.txt
python -W ignore pattern_map.pypattern_map.py constructs each lesson’s graph or tree, prints its shape and writes pattern-map.json. It sends no user turn and needs no API key. Keep the bundled patterns/ directory beside the inspector.
"""Print the structure of every workflow pattern this course builds, without calling a model.
The bundled lesson modules construct the actual course structures without model calls.
"""
import importlib.util
import json
from pathlib import Path
from google.adk import Agent, Workflow
from google.adk.tools import AgentTool
EXAMPLES = Path(__file__).resolve().parent / "patterns"
# Pattern name on adk.dev/workflows/patterns -> (lesson folder, module, builder, workflow type)
PATTERNS = {
"Sequential pipeline": ("adk-graph-workflow", "release_notes", "build_workflow", "graph"),
"Coordinator and dispatcher": ("adk-graph-routes", "build_router", "build_workflow", "graph"),
"Human-in-the-loop": ("adk-human-approval", "migration_gate", "build_workflow", "graph"),
"Generate and review": ("adk-generate-review", "review_gate", "build_workflow", "graph"),
"Iterative refinement": ("adk-iterative-refinement", "refine_entry", "build_workflow", "graph"),
"Parallel fan-out and gather": ("adk-parallel-gather", "release_prep", "build_graph", "graph"),
"Parallel fan-out, partial results": ("adk-parallel-gather", "release_prep", "build_partial",
"dynamic"),
"Coordinator with sub-agents": ("adk-sub-agents", "review_team", "build_team", "collaborative"),
"Hierarchical task decomposition": ("adk-task-decomposition", "upgrade_guide", "build_tree",
"collaborative"),
}
def load(folder: str, module: str):
"""Import a lesson's module under a unique name; every lesson folder is self-contained."""
spec = importlib.util.spec_from_file_location(f"{folder}.{module}",
EXAMPLES / folder / f"{module}.py")
loaded = importlib.util.module_from_spec(spec)
spec.loader.exec_module(loaded)
return loaded
def edges(workflow: Workflow) -> list[str]:
"""The graph ADK validated at construction: one line per edge, with its route."""
lines = []
for edge in workflow.graph.edges:
route = f" [{edge.route}]" if edge.route is not None else ""
lines.append(f"{edge.from_node.name} -> {edge.to_node.name}{route}")
return lines
def tree(agent: Agent, depth: int = 1, via: str = "") -> list[str]:
"""An agent hierarchy: sub-agents with their mode, and agents wrapped in AgentTool.
ADK 2.12 also gives a coordinator one AgentTool subclass per single_turn sub-agent;
those are the sub-agents, so they are listed once."""
lines = [f"{' ' * (depth - 1)}{agent.name} (depth {depth}{via})"]
for sub in agent.sub_agents:
lines += tree(sub, depth + 1, f", sub-agent, mode={sub.mode}")
subs = {sub.name for sub in agent.sub_agents}
for tool in agent.tools:
if isinstance(tool, AgentTool) and tool.agent.name not in subs:
lines += tree(tool.agent, depth + 1, ", AgentTool")
return lines
def describe(name: str) -> dict:
folder, module, builder, kind = PATTERNS[name]
built = getattr(load(folder, module), builder)("gemini-3.8-flash")
shape = edges(built) if isinstance(built, Workflow) else tree(built)
return {"pattern": name, "type": kind, "lesson_code": f"{folder}/{module}.py", "shape": shape}
if __name__ == "__main__":
described = [describe(name) for name in PATTERNS]
for entry in described:
print(f"{entry['pattern']} ({entry['type']}, {entry['lesson_code']})")
print("\n".join(f" {line}" for line in entry["shape"]) + "\n")
Path(__file__).with_name("pattern-map.json").write_text(json.dumps(described, indent=2) + "\n")Compare the generate-and-review entry with this saved structure:
Recorded example. The archived “Choose an ADK workflow pattern” demonstration retains the earlier shipctl scenario; the current lab uses docuctl and does not claim a new model result.
{
"pattern": "Generate and review",
"type": "graph",
"lesson_code": "adk-generate-review/review_gate.py",
"shape": [
"__START__ -> read_changelog",
"read_changelog -> write_announcement",
"write_announcement -> review_announcement",
"review_announcement -> gate",
"gate -> publish [approve]",
"gate -> send_back [reject]"
]
}The refinement graph has a back-edge from revise_entry to check_entry. The parallel graph has several edges into gather. The hierarchical entry is an agent tree rather than a list of graph edges. These shapes tell you how control can flow; they do not tell you whether generated text is useful.
A node that no edge reaches. Constructing an invalid Workflow raises a Pydantic ValidationError that identifies unreachable nodes.
Test the map
python -W ignore -m unittest -v test_pattern_map.pyThe tests reconstruct the real lessons and compare them with the saved map. They also exercise invalid graph construction and the deprecated template warnings. They check topology, not generated answers.
Choose before you implement
Suppose the release page needs highlights and upgrade steps, but the link report is optional. Choose a pattern and write down its failure policy before reading on. A dynamic fan-out parent fits: both writing branches are required, while a failed link check becomes an explicit warning. In this course’s plain join example, an uncaught link-check error fails the whole run. A graph could instead catch that error inside the optional branch and return a warning for the join.
Continue with Generate and review to build a writer, a reviewer and a code gate.
Read the explanation
Choose the control structure from dependencies and failure policy before adding a model decision.
References
- ADK workflow patterns: pattern definitions and examples.
- ADK graph workflows: declared nodes and edges.
- ADK graph routes: branches, joins and back-edges.
- ADK dynamic workflows: child execution controlled by Python.