A practical study on structured vs. dynamic agent routing, based on Google Skills Lab GENAI106.
Introduction
As agentic AI matures, developers face a critical design decision: How should agents orchestrate tasks?
In this article, we explore the two primary orchestration architectures provided by the Google Agent Development Kit (ADK):
- Dynamic Multi-Agent Routing: A hierarchical setup where LLM agents dynamically decide who to transfer control to using injected tools.
-
Sequential Workflow Loops (
LoopAgent): A structured setup where execution flows in a pre-defined sequence of nodes using a shared session state.
To compare these patterns, we built a mathematical expression parser. Solving an expression like ((12 + 8) * 5) - (10 / 2) + (4 * 6) - (50 / 5) requires coordinating four specialists: adder, substracter, multiplier, and divider.
This study is adapted and extended from the code lab **GENAI106: Build Multi-Agent Systems with ADK* on the skills.google platform.*
Architecture 1: Dynamic Multi-Agent Routing (parent_and_subagents)
In the dynamic multi-agent pattern, a central steering coordinator receives the user input and delegates operations to specialist sub-agents based on mathematical precedence.
graph TD
User([User Expression]) --> Steering[steering coordinator]
Steering -->|transfer_to_agent| Adder[adder specialist]
Steering -->|transfer_to_agent| Substracter[substracter specialist]
Steering -->|transfer_to_agent| Multiplier[multiplier specialist]
Steering -->|transfer_to_agent| Divider[divider specialist]
Adder -->|transfer_to_agent| Steering
Substracter -->|transfer_to_agent| Steering
Multiplier -->|transfer_to_agent| Steering
Divider -->|transfer_to_agent| Steering
The Control Transfer Flow
- When
steeringroutes an expression to a specialist (e.g.,adder), control is transferred. - When the specialist finishes simplifying its part of the expression, it transfers control back to its parent:
# adk_multiagent_systems/parent_and_subagents/agent.py
# adder instruction:
# "Once you have performed the additions, transfer control back to the 'steering' agent using the 'transfer_to_agent' tool."
- Pros: Highly flexible. The LLM dynamically decides the best path and delegates recursively.
- Cons: High latency due to constant peer-to-peer routing calls.
Architecture 2: Structured Workflow Loops (workflow_agents)
In the workflow pattern, a LoopAgent coordinates execution sequentially in a fixed, predefined loop (adder ➔ substracter ➔ multiplier ➔ divider), passing state updates via a shared key.
graph TD
User([User Expression]) --> WSteering[workflow_steering coordinator]
WSteering -->|transfer_to_agent| Loop[calculator_loop LoopAgent]
subgraph calculator_loop
Adder[adder] --> Substracter[substracter]
Substracter --> Multiplier[multiplier]
Multiplier --> Divider[divider]
Divider -->|Loops back| Adder
end
calculator_loop -.->|exit_loop| WSteering
The Shared Session State
Instead of returning values directly, the workflow agents read and update a shared key (expression) in the session state using a custom tool:
def update_expression(tool_context: ToolContext, expression: str):
tool_context.state['expression'] = expression
return {"status": "success"}
The Loop terminates when a specialist detects that the expression has been reduced to a single number and calls the ADK built-in tool exit_loop.
Technical Challenges & Hard-Won Lessons
When transitioning the calculator to a LoopAgent workflow, we encountered three main challenges:
1. The Tool Loop Recurrence Issue
The Problem: LLM agents generate tool calls in a loop within a single turn until they output text. Because the system instruction containing { expression? } is static for the duration of the turn, the LLM kept calling update_expression recursively with the same values.
The Solution: We modified the instructions to enforce turn termination immediately after updating the state:
"- After calling the 'update_expression' tool, output the updated expression as text and end your turn. Do NOT call the tool again."
2. Operator Precedence in Fixed Sequences
The Problem: A loop agent runs specialists in a strict order. If adder runs before substracter, an expression like 100 - 5 + 24 - 10 would incorrectly evaluate 5 + 24 = 29 first, leading to 100 - 29 - 10 = 61 (instead of 109).
The Solution: We instructed adder to respect order of operations:
"- Do NOT perform additions if they are immediately preceded by a subtraction (e.g., in '100 - 5 + 24', you must NOT add 5 + 24 because the minus sign belongs to 5)."
3. Graceful Loop Termination
The Problem: Calling exit_loop() sets skip_summarization = True, terminating the run immediately. If the agent doesn't print the result first, the session closes silently.
The Solution: Instruct the resolving agent to output the final result before calling exit_loop:
"- If the expression is reduced to a single number, output the final result clearly (e.g. 'El resultado es X') and call the 'exit_loop' tool."
Showcasing Hierarchical Nested Sub-Agents: zero_division_guard
To demonstrate deep agent hierarchies and error boundary handling, we added a nested validation guard (zero_division_guard) under the divider specialist.
graph TD
Divider[divider] -->|transfer_to_agent| Guard[zero_division_guard]
Guard -->|Validation Failed| Error[Abort & Exit]
Guard -->|Validation Passed| Divider
Before performing any division, divider delegates to zero_division_guard using transfer_to_agent. The guard checks the divisor (denominator):
- If the divisor evaluates to zero (e.g.,
10 / 0), it aborts execution and outputs a warning. - If it is safe, it transfers control back to
dividerto complete the calculation.
Integration Test Trace (Multi-Agent):
Running: .venv/bin/adk run adk_multiagent_systems/parent_and_subagents "Please evaluate 10 / 0"
[zero_division_guard]: Error: Division by zero detected. Operation cancelled.
[divider]: Error: Division by zero detected. Operation cancelled.
[steering]: Error: Division by zero detected. Operation cancelled.
Integration Test Trace (Workflow):
Running: .venv/bin/adk run adk_multiagent_systems/workflow_agents "Please evaluate 10 / 0"
[zero_division_guard]: Error: Division by zero detected. Operation cancelled.
Conclusion
Google ADK provides powerful primitives for both dynamic multi-agent architectures and deterministic workflow sequences.
- Use Multi-Agent Routing when tasks are highly dynamic, branch unpredictably, and benefit from conversational flexibility.
- Use Workflow Loops when you have a structured, multi-step pipeline where reliability, fixed sequencing, and state-based progression are paramount.



Top comments (0)