AI Agent Orchestration Patterns: Designing Workflows
The patterns and practices for orchestrating complex AI agent workflows.
Orchestrating AI agents is like conducting an orchestra β knowing when each agent should play, how loud, and when to stop. This guide covers the design patterns for effective AI agent orchestration.
Pattern Catalog
1. Simple Sequential (Pipeline)
Task β Agent 1 β Agent 2 β Agent 3 β Result
Best for: Linear processes like content creation
workflow = SequentialPipeline([
("research", research_agent),
("outline", outline_agent),
("write", writer_agent),
("edit", editor_agent),
("publish", publisher_agent),
])
2. Parallel Scatter-Gather
ββ Agent A β
Task ββ Agent B ββ Merge β Result
ββ Agent C β
Best for: Getting multiple perspectives
3. Conditional Routing
Task β Classifier β ββ Path A (if billing)
ββ Path B (if technical)
ββ Path C (if general)
Best for: Customer support, ticket routing
workflow = ConditionalRouter({
"billing": billing_agent,
"technical": tech_agent,
"general": general_agent,
}, classifier=intent_classifier)
4. Loop Until Satisfied
Task β Agent β Quality Check β ββ Output (if quality > threshold)
ββ Agent (if quality < threshold)
Best for: Iterative improvement
async def loop_until_satisfied(task, max_iterations=3):
result = await agent.process(task)
for _ in range(max_iterations):
quality = await quality_checker.evaluate(result)
if quality.score > 0.85:
return result
result = await improver_agent.improve(result, quality.feedback)
return result
5. Human-in-the-Loop
Task β Agent β Human Review β ββ Approve β Output
ββ Reject β Agent (revise)
Best for: High-stakes decisions
async def human_in_loop(task):
result = await agent.process(task)
review = await request_human_review(result)
if review.approved:
return result
else:
# Agent revises based on feedback
return await agent.process(f"{task}\n\nFeedback: {review.feedback}")
6. Competitive Selection
Task β ββ Agent A β Result A β
ββ Agent B β Result B ββ Best Selector β Output
ββ Agent C β Result C β
Best for: When quality matters more than cost
async def competitive(task):
results = await asyncio.gather(
agent_a.process(task),
agent_b.process(task),
agent_c.process(task),
)
# Evaluate each result
scored = [(await scorer.evaluate(r), r) for r in results]
# Return best
return max(scored, key=lambda x: x[0].score)[1]
7. Map-Reduce
Task β Split into chunks β ββ Agent processes chunk 1 β
ββ Agent processes chunk 2 ββ Combine
ββ Agent processes chunk N β
Best for: Processing large datasets
async def map_reduce(data, chunk_size=100):
chunks = split_into_chunks(data, chunk_size)
# Map: process each chunk
results = await asyncio.gather(*[
agent.process(chunk) for chunk in chunks
])
# Reduce: combine results
return await combiner_agent.process(results)
State Management
class WorkflowState:
"""Manages state across workflow steps."""
def __init__(self):
self.data = {}
self.history = []
def set(self, key, value):
self.data[key] = value
self.history.append({
"key": key,
"value": value,
"timestamp": datetime.utcnow(),
})
def get(self, key, default=None):
return self.data.get(key, default)
def checkpoint(self):
"""Save state for recovery."""
return copy.deepcopy(self.data)
def restore(self, checkpoint):
"""Restore from checkpoint."""
self.data = checkpoint
Error Handling Patterns
Retry with Backoff
async def with_retry(agent, task, max_retries=3):
for attempt in range(max_retries):
try:
return await agent.process(task)
except (TimeoutError, RateLimitError) as e:
if attempt == max_retries - 1:
raise
wait = 2 ** attempt # Exponential backoff
await asyncio.sleep(wait)
Fallback Chain
async def with_fallback(task, agent_chain):
"""Try agents in order until one succeeds."""
for agent in agent_chain:
try:
return await agent.process(task)
except AgentError:
continue
raise AllAgentsFailedError()
Circuit Breaker
class CircuitBreaker:
def __init__(self, threshold=5, reset_timeout=60):
self.failures = 0
self.threshold = threshold
self.reset_timeout = reset_timeout
self.last_failure = None
async def call(self, func, *args):
if self.is_open():
raise CircuitOpenError("Circuit breaker is open")
try:
result = await func(*args)
self.failures = 0 # Reset on success
return result
except Exception:
self.failures += 1
self.last_failure = datetime.utcnow()
raise
Cost-Aware Orchestration
class CostAwareOrchestrator:
def __init__(self, budget):
self.budget = budget
self.spent = 0
async def execute(self, workflow):
for step in workflow.steps:
# Check budget
estimated_cost = self.estimate_cost(step)
if self.spent + estimated_cost > self.budget:
# Use cheaper alternative
step = self.downgrade_step(step)
result = await step.agent.process(step.input)
self.spent += result.cost
return result
def downgrade_step(self, step):
"""Use a cheaper model/agent for this step."""
if step.agent.model == "gpt-4o":
step.agent = Agent(model="gpt-4o-mini")
return step
Conclusion
Choosing the right orchestration pattern dramatically affects the quality, speed, and cost of your AI agent workflows. Start simple (sequential), add complexity only when needed (parallel, conditional, loops), and always plan for failures and budget constraints.
Learn More
- Multi-Agent Systems Design
- AI Agent Orchestration Tools
- AI Workflow Automation Examples
- AI Agent Cost Optimization
Find orchestration tools on SkillExchange.