DEV Community

Julio Quinteros P.
Julio Quinteros P.

Posted on

Comparing Orchestration Patterns in Google ADK: Multi-Agent vs. Workflow-Based Loops

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):

  1. Dynamic Multi-Agent Routing: A hierarchical setup where LLM agents dynamically decide who to transfer control to using injected tools.
  2. 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.

Multi-agent architecture diagram

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
Enter fullscreen mode Exit fullscreen mode

The Control Transfer Flow

  • When steering routes 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."
Enter fullscreen mode Exit fullscreen mode
  • 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 (addersubstractermultiplierdivider), passing state updates via a shared key.

Workflow-agent architecture diagram

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
Enter fullscreen mode Exit fullscreen mode

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"}
Enter fullscreen mode Exit fullscreen mode

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.

A use case: the almighty division by zero

graph TD
    Divider[divider] -->|transfer_to_agent| Guard[zero_division_guard]
    Guard -->|Validation Failed| Error[Abort & Exit]
    Guard -->|Validation Passed| Divider
Enter fullscreen mode Exit fullscreen mode

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 divider to 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.
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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)