Skip to content
Letta Platform Letta Platform Letta Docs
Sign up
Tutorials
Multi-agent patterns

The supervisor-worker pattern

Coordinate multiple specialized agents under a single supervisor agent with shared memory and archival storage.

Coordinate multiple specialized agents under a single supervisor. Workers maintain memory across sessions and share knowledge through common resources. The supervisor autonomously routes tasks to workers based on their expertise and tags.

The supervisor-worker pattern works well when you need to:

  • Distribute work across specialized agents (technical research, market analysis, content creation)
  • Keep workers coordinated with shared guidelines or priorities
  • Scale task execution with parallel worker agents
  • Maintain shared context and knowledge across a team

A research supervisor coordinates specialized workers (technical, market, and academic) to gather comprehensive information on a topic. The supervisor intelligently routes tasks to specific workers based on their expertise - sending technical questions to the technical worker, market analysis to the market worker. Each worker maintains context across research sessions, stores findings in a shared archive, and follows guidelines set by the supervisor.

The pattern uses three key components:

  1. Shared archival memory: Workers contribute findings to a large searchable knowledge base.
  2. Shared memory blocks: Memory blocks make the small coordination state visible to all agents (guidelines, priorities, status).
  3. Agent tagging: Workers are tagged (for example, ["worker", "research", "technical"]) for organization and discovery by the supervisor.

The supervisor autonomously:

  • Analyzes tasks and decides which workers to use
  • Routes tasks to specific workers using tag-based filtering (match_all and match_some)
  • Receives responses from targeted workers
  • Synthesizes results from worker contributions

Step 1: Get the supervisor coordination tool

Section titled “Step 1: Get the supervisor coordination tool”

The supervisor needs the send_message_to_agents_matching_tags tool to coordinate workers:

from letta_client import Letta
import os
client = Letta(api_key=os.getenv("LETTA_API_KEY"))
# Get the broadcast coordination tool
tools = client.tools.list(name="send_message_to_agents_matching_tags")
broadcast_tool = tools.items[0]

This tool allows the supervisor to send messages to agents matching specific tag criteria. It supports two filtering modes:

  • match_all: Tags that agents MUST have (required tags)
  • match_some: Tags where agents need at least one (optional specialization tags)

Create the shared memory block and archive:

# Shared memory block for guidelines
guidelines_block = client.blocks.create(
label="research_guidelines",
description="Research guidelines and priorities set by supervisor",
value="Focus on accurate, well-sourced information from 2024-2025."
)
# Shared archive for research findings
findings_archive = client.archives.create(
name="research_findings",
description="Collaborative archive for all research findings"
)

Create the supervisor with the broadcast tool and shared resources:

supervisor = client.agents.create(
name="research_supervisor",
model="anthropic/claude-sonnet-4-5-20250929",
memory_blocks=[{
"label": "persona",
"value": """I am a research supervisor that coordinates specialized workers.
Available workers and their tags:
- technical worker (tags: worker, research, technical) - Reviews code, APIs, implementation details
- market worker (tags: worker, research, market) - Analyzes market trends, competitors, industry
I route tasks to appropriate workers using send_message_to_agents_matching_tags:
- For technical questions: match_all=["worker"], match_some=["technical"]
- For market questions: match_all=["worker"], match_some=["market"]
- For broad questions: match_all=["worker", "research"]
I delegate to workers rather than answering directly."""
}],
block_ids=[guidelines_block.id],
tags=["supervisor", "research"],
tool_ids=[broadcast_tool.id]
)
client.agents.archives.attach(findings_archive.id, agent_id=supervisor.id)

Create specialized workers, each with their own expertise:

# Technical research worker
tech_worker = client.agents.create(
name="technical_worker",
model="anthropic/claude-sonnet-4-5-20250929",
memory_blocks=[{
"label": "persona",
"value": "I analyze technical documentation, APIs, and implementation details."
}],
block_ids=[guidelines_block.id],
tags=["worker", "research", "technical"],
tools=["archival_memory_insert", "archival_memory_search"]
)
client.agents.archives.attach(findings_archive.id, agent_id=tech_worker.id)
# Market research worker
market_worker = client.agents.create(
name="market_worker",
model="anthropic/claude-sonnet-4-5-20250929",
memory_blocks=[{
"label": "persona",
"value": "I gather market trends, competitor information, and industry insights."
}],
block_ids=[guidelines_block.id],
tags=["worker", "research", "market"],
tools=["archival_memory_insert", "archival_memory_search"]
)
client.agents.archives.attach(findings_archive.id, agent_id=market_worker.id)

Key features:

  • Workers automatically persist memory across sessions
  • Workers are tagged with ["worker", "research"] plus their specialty
  • All share the research_findings archive
  • All share the research_guidelines memory block

Step 5: Supervisor routes tasks intelligently

Section titled “Step 5: Supervisor routes tasks intelligently”

The supervisor autonomously analyzes tasks and routes them to appropriate workers:

# Technical task - supervisor routes to technical worker
response = client.agents.messages.create(
agent_id=supervisor.id,
messages=[{
"role": "user",
"content": "Analyze the API documentation for Letta's agent creation endpoint."
}]
)
# Supervisor autonomously uses: match_all=["worker"], match_some=["technical"]
# Market task - supervisor routes to market worker
response = client.agents.messages.create(
agent_id=supervisor.id,
messages=[{
"role": "user",
"content": "Research current trends in the AI agent framework market."
}]
)
# Supervisor autonomously uses: match_all=["worker"], match_some=["market"]
# Comprehensive task - supervisor broadcasts to all workers
response = client.agents.messages.create(
agent_id=supervisor.id,
messages=[{
"role": "user",
"content": "Conduct a comprehensive analysis of Letta from all perspectives."
}]
)
# Supervisor autonomously uses: match_all=["worker", "research"]
from letta_client import Letta
import os
client = Letta(api_key=os.getenv("LETTA_API_KEY"))
# Get broadcast tool
tools = client.tools.list(name="send_message_to_agents_matching_tags")
broadcast_tool = tools.items[0]
# Create shared resources
guidelines = client.blocks.create(
label="guidelines",
value="Focus on quality sources."
)
archive = client.archives.create(name="findings")
# Create supervisor with broadcast tool
supervisor = client.agents.create(
model="anthropic/claude-sonnet-4-5-20250929",
memory_blocks=[{
"label": "persona",
"value": """I am a research supervisor that coordinates specialized workers.
Available workers and their tags:
- technical worker (tags: worker, technical) - Reviews code, APIs
I route tasks using send_message_to_agents_matching_tags:
- For technical questions: match_all=["worker"], match_some=["technical"]
I delegate to workers rather than answering directly."""
}],
block_ids=[guidelines.id],
tags=["supervisor"],
tool_ids=[broadcast_tool.id]
)
client.agents.archives.attach(archive.id, agent_id=supervisor.id)
# Create technical worker
tech_worker = client.agents.create(
model="anthropic/claude-sonnet-4-5-20250929",
memory_blocks=[{
"label": "persona",
"value": "I am a technical researcher."
}],
block_ids=[guidelines.id],
tags=["worker", "technical"],
tools=["archival_memory_insert", "archival_memory_search"]
)
client.agents.archives.attach(archive.id, agent_id=tech_worker.id)
# Supervisor autonomously routes to technical worker
response = client.agents.messages.create(
agent_id=supervisor.id,
messages=[{
"role": "user",
"content": "Review this Python code for potential issues:\n\ndef process_data(items):\n return [item * 2 for item in items]"
}]
)
# Cleanup
client.agents.delete(tech_worker.id)
client.agents.delete(supervisor.id)
client.archives.delete(archive.id)
client.blocks.delete(guidelines.id)
  • Use descriptive tags: Tag workers by role, specialty, and team for flexible coordination
  • Keep shared memory focused: Use shared memory blocks for guidelines and coordination, not large data
  • Use archival memory for data: Store research findings, data, and historical context in archives
  • Design clear personas: Give each worker a specific specialty and role definition
  • Monitor worker state: Check worker memory and archive contributions to track progress