Design and implement multi-agent LLM architectures using the orchestrator-subagent pattern...
Multi-agent systems introduce overhead. Every additional agent represents another potential point of failure, another set of prompts to maintain, and another source of unexpected behavior.
Multi-agent systems use 3-10x more tokens than single-agent approaches due to:
A well-designed single agent with appropriate tools can accomplish far more than expected. Use single agent when:
Use when subtasks generate large context but only summary is needed for main task.
Example: Customer Support
class OrderLookupAgent:
def lookup_order(self, order_id: str) -> dict:
messages = [{"role": "user", "content": f"Get essential details for order {order_id}"}]
response = client.messages.create(
model="claude-sonnet-4-5", max_tokens=1024,
messages=messages, tools=[get_order_details_tool]
)
return extract_summary(response) # Returns 50-100 tokens, not 2000+
class SupportAgent:
def handle_issue(self, user_message: str):
if needs_order_info(user_message):
order_id = extract_order_id(user_message)
order_summary = OrderLookupAgent().lookup_order(order_id)
context = f"Order {order_id}: {order_summary['status']}, purchased {order_summary['date']}"
# Main agent gets clean context
Best when:
Use when exploring larger search space or independent research facets.
import asyncio
from anthropic import AsyncAnthropic
client = AsyncAnthropic()
async def research_topic(query: str) -> dict:
facets = await lead_agent.decompose_query(query)
tasks = [research_subagent(facet) for facet in facets]
results = await asyncio.gather(*tasks)
return await lead_agent.synthesize(results)
async def research_subagent(facet: str) -> dict:
messages = [{"role": "user", "content": f"Research: {facet}"}]
response = await client.messages.create(
model="claude-sonnet-4-5", max_tokens=4096,
messages=messages, tools=[web_search, read_document]
)
return extract_findings(response)
Benefit: Thoroughness, not speed. Covers more ground at higher token cost.
Split by domain when agent has 20+ tools, shows domain confusion, or degraded performance.
Signs you need specialization:
Different tasks require conflicting behavioral modes:
Deep domain context that would overwhelm a generalist:
class CRMAgent:
system_prompt = """You are a CRM specialist. You manage contacts,
opportunities, and account records. Always verify record ownership
before updates and maintain data integrity across related records."""
tools = [crm_get_contacts, crm_create_opportunity] # 8-10 CRM tools
class MarketingAgent:
system_prompt = """You are a marketing automation specialist. You
manage campaigns, lead scoring, and email sequences."""
tools = [marketing_get_campaigns, marketing_create_lead] # 8-10 tools
class OrchestratorAgent:
def execute(self, user_request: str):
response = client.messages.create(
model="claude-sonnet-4-5", max_tokens=1024,
system="""Route to appropriate specialist:
- CRM: Contacts, opportunities, accounts, sales pipeline
- Marketing: Campaigns, lead nurturing, email sequences""",
messages=[{"role": "user", "content": user_request}],
tools=[delegate_to_crm, delegate_to_marketing]
)
return response
Problem-centric (counterproductive): Split by work type (writer, tester, reviewer) - creates coordination overhead, context loss at handoffs.
Context-centric (effective): Agent handling a feature also handles its tests - already has necessary context.
Dedicated agent for testing/validating main agent's work. Succeeds because verification requires minimal context transfer.
class CodingAgent:
def implement_feature(self, requirements: str) -> dict:
response = client.messages.create(
model="claude-sonnet-4-5", max_tokens=4096,
messages=[{"role": "user", "content": f"Implement: {requirements}"}],
tools=[read_file, write_file, list_directory]
)
return {"code": response.content, "files_changed": extract_files(response)}
class VerificationAgent:
def verify_implementation(self, requirements: str, files_changed: list) -> dict:
messages = [{"role": "user", "content": f"""
Requirements: {requirements}
Files changed: {files_changed}
Run the complete test suite and verify:
1. All existing tests pass
2. New functionality works as specified
3. No obvious errors or security issues
You MUST run: pytest --verbose
Only mark as PASSED if ALL tests pass with no failures.
"""}]
response = client.messages.create(
model="claude-sonnet-4-5", max_tokens=4096,
messages=messages, tools=[run_tests, execute_code, read_file]
)
return {"passed": extract_pass_fail(response), "issues": extract_issues(response)}
Verifier marks passing without thorough testing. Prevention:
Before adding multi-agent complexity:
Start with simplest approach that works. Add complexity only when evidence supports it.