Suyi Zhang
Suyi Zhang
Published on
5 min read

Simplifying Multi-Agent Systems: Reducing Root Agent Complexity

AI agentsGoogle ADKmulti agent systemsprompt engineeringperformance optimization

Simplifying Multi-Agent Systems: Reducing Root Agent Complexity

The Complex Root Agent I Started With

A while ago, my ADK root orchestrator agent was a 150-line prompt that tried to handle too much complexity. It had intricate conditional logic, state management, and transfer rules that spanned multiple nested if-else statements. The system was slower than needed and sometimes confused users with incorrect agent transfers.

# The pattern I was using - too complex
system_prompt = """
You are a workflow coordinator that must understand user intent deeply.

If user asks about requirements but they're not complete:
    Check if they're asking new requirements or clarifying existing ones
    If new requirements: transfer to requirement_agent
    If clarifying: handle directly

elif user has seen search results:
    Analyze if they want more results, want to select, or have questions
    If they want to "tell me more": transfer to product_agent
    If they want to "compare": handle directly
    If they want new search: transfer to search_agent

# ... 30+ more lines of conditional logic
"""

root_agent = LlmAgent(
    model=constants.ORCHESTRATOR_MODEL,
    name="coordinator",
    description="Complex workflow coordinator with deep intent analysis",
    instruction=system_prompt,
    tools=[
        FunctionTool(complex_state_checker),
        FunctionTool(intent_analyzer),
        FunctionTool(workflow_manager)
    ]
)

The result? Slower response times and a system that was harder to maintain. Adding new features meant understanding complex conditional branches.

Radical Simplification

The solution wasn't more logic - it was dramatically less. I reduced my root agent's prompt from 150 lines to 20 lines by making it truly dumb and letting specialists be smart.

The New Root Agent: Simple and Focused

Here's the pattern I use now:

from google.adk.agents import LlmAgent
from .shared_libraries import constants

# Simplified fictional agents for illustration
requirement_agent = LlmAgent(model=constants.PRO_MODEL, name="requirement_agent")
search_agent = LlmAgent(model=constants.PRO_MODEL, name="search_agent")
selection_agent = LlmAgent(model=constants.PRO_MODEL, name="selection_agent")
specs_agent = LlmAgent(model=constants.PRO_MODEL, name="specs_agent")
quote_agent = LlmAgent(model=constants.PRO_MODEL, name="quote_agent")

# Simplified root agent with clean workflow logic
system_prompt = """
You are a workflow coordinator.
Your role is simple: manage workflow progression and let specialists handle everything else.

**WORKFLOW STATES:**
1. Requirements → 2. Search → 3. Selection → 4. Specifications → 5. Quotes

**REQUIRED TRANSFERS (Only These):**
- No requirements → requirement_agent
- Requirements complete + no search → search_agent
- Selections complete → specs_agent (automatic)
- Specs complete + user wants quotes → quote_agent

**NEVER TRANSFER:**
- If a specialist agent can handle the user's request
- More than twice per conversation turn

**YOUR SIMPLE JOB:**
1. Check session state workflow progress
2. Transfer only for required workflow progression
3. Let specialist agents handle all user questions
4. Provide workflow summaries when complete
"""

root_agent = LlmAgent(
    model=constants.ORCHESTRATOR_MODEL,
    name="coordinator",
    description="Simple workflow coordinator that manages progression and delegates to specialists",
    instruction=system_prompt,
    sub_agents=[
        requirement_agent,
        search_agent,
        selection_agent,
        specs_agent,
        quote_agent,
    ]
)

That's the entire orchestrator logic. 87% less code than the previous version. All the complex conditional logic is gone.

What Actually Changed: The Architecture Shift

Before (Complex Root Agent):

  • 150 lines of conditional logic
  • Complex state checking with nested if-else statements
  • Root agent tried to understand user intent deeply
  • Transfer rules spanned dozens of conditions
  • High cognitive load on the orchestrator

After (Simple Root Agent):

  • 20 lines total
  • Simple declarative transfer rules
  • Root agent only checks workflow state
  • Clear "required transfers" vs "never transfer" rules
  • Heavy lifting delegated to specialists

The key insight: The root agent shouldn't try to be smart. It should just know when to pass control to someone who is.

Better Sub-Agent Descriptions

One crucial discovery I made: well-written sub-agent descriptions make the root agent's job much easier. Instead of complex conditional logic, I now focus on writing clear, descriptive sub-agent definitions.

# Before: Generic description
search_agent = LlmAgent(
    model=constants.PRO_MODEL,
    name="search_agent",
    description="Searches for products",
    instruction="Search the catalog and return results"
)

# After: Detailed, proactive description
search_agent = LlmAgent(
    model=constants.PRO_MODEL,
    name="search_agent",
    description="Use PROACTIVELY when user provides search queries, search criteria, specifications, technical details, filters, or search refinement requests. Handles text search, image search, and search refinement.",
    instruction="""
        You are a product search specialist...
        # ... the rest of the agent prompt
        """,
    tools=[
        FunctionTool(product_search_tool),
        FunctionTool(image_search_tool),
        FunctionTool(filter_results_tool)
    ]
)

This descriptive approach means the root agent doesn't need complex logic to decide when to use each specialist - the sub-agents essentially advertise their own use cases.

Specialists Handle the Complexity

With the root agent simplified, the specialist agents became more capable and self-contained. Each specialist is now responsible for its own domain complexity, including handling user questions within that domain.

The Performance Impact: Why Simplicity Wins

What Actually Improved

The architectural simplification delivered better results in practice:

What's Improved:

  • Response Quality: More consistent and appropriate agent behavior
  • System Reliability: Fewer confused responses and transfer loops
  • Development Experience: Much easier to understand and modify the system
  • Code Maintainability: Simpler logic meant fewer bugs and easier debugging

Code Reduction:

  • Root Agent: 150 lines → 20 lines (87% reduction)
  • Complexity: Eliminated nested conditional logic
  • Cognitive Load: Much easier for new developers to understand

Why This Works Better:

  1. Reduced Cognitive Load: The LLM processes fewer tokens and simpler logic
  2. Clearer Decision Paths: No nested conditional branches to evaluate
  3. Better Context Preservation: Fewer unnecessary transfers
  4. Specialist Ownership: Each agent owns its domain completely

What I Learned About Agent Simplification

1. The "Dumb Coordinator" Principle

The root agent shouldn't try to understand user intent deeply. It should only:

  • Check workflow state
  • Follow simple transfer rules
  • Let specialists handle complexity
# Bad: Complex intent analysis
"Analyze user intent based on conversation context..."

# Good: Simple state checking
"Requirements complete? → Search agent needed."

2. Specialist Ownership

Each specialist owns their domain completely:

  • They handle domain-specific questions
  • They determine when their work is done
  • They manage their own tools and logic
  • They only transfer when workflow progression is required

3. Minimal Transfer Logic

Instead of complex conditional branching, use:

  • Clear workflow states
  • Simple transfer triggers
  • Explicit "never transfer" rules

How to Apply This Approach

Simplification Checklist

Before Refactoring:

  • Identify complex conditional logic in root agent
  • Map current transfer patterns

During Refactoring:

  • Move domain logic to specialists
  • Simplify root to state-based transfers only
  • Add explicit "never transfer" rules

After Refactoring:

  • Measure performance improvements
  • Monitor transfer success rates

TL;DR: The Key Takeaway

I reduced my root orchestrator from 150 lines to 20 lines by making it truly dumb and letting specialists handle complexity. This resulted in more consistent behavior, easier maintenance, and a much simpler system to understand and modify. Root agents should coordinate, not comprehend.

What's Next

I'm trying to write up what I learned from building production AI agents with Google ADK, it'll be a series of posts coming up in the next few weeks. They will likely cover architecture, deployment, monitoring, and the lessons learned from running this system in production.