Supervisor Agent
The SupervisorAgent
is an advanced orchestration component that enables sophisticated multi-agent coordination within the Multi-Agent Orchestrator framework.
It implements a unique “agent-as-tools” architecture where team members are exposed to a supervisor agent as invocable tools, enabling parallel processing and contextual communication.
The diagram below illustrates the SupervisorAgent architecture, featuring a Lead Agent that coordinates with a team of specialized agents (A, B, and C). Two memory components—User-Supervisor Memory and Supervisor-Team Memory—support the interactions, enabling efficient information flow and conversation history management throughout the system.
Usage Patterns
The SupervisorAgent can be used in two primary ways:
1. Direct Usage
You can use the SupervisorAgent directly, bypassing the classifier, when you want dedicated team coordination for specific tasks:
// Create and configure SupervisorAgentconst supervisorAgent = new SupervisorAgent({ name: "SupervisorAgent", description: "You are a supervisor agent that manages the team of agents for travel purposes", leadAgent: new BedrockLLMAgent({ name: "Support Team Lead", description: "Coordinates support inquiries" }), team: [ new LexBotAgent({ name: "Booking Agent", description: "Handles travel bookings", botId: "travel-bot-id", botAliasId: "alias-id", localeId: "en_US" }), new AmazonBedrockAgent({ name: "Payment Support", description: "Handles payment issues", agentId: "payment-agent-id", agentAliasId: "alias-id" }) ]});
// Use directlyconst response = await supervisorAgent.processRequest( "I need to modify my flight and check my refund status", "user123", "session456");
# Create and configure SupervisorAgentsupervisor_agent = SupervisorAgent(SupervisorAgentOptions( name: "SupervisorAgent", description: "You are a supervisor agent that manages the team of agents for travel purposes", lead_agent=BedrockLLMAgent(BedrockLLMAgentOptions( name="Support Team Lead", description="Coordinates support inquiries" )), team=[ LexBotAgent(LexBotAgentOptions( name="Booking Agent", description="Handles travel bookings", bot_id="travel-bot-id", bot_alias_id="alias-id", locale_id="en_US" )), BedrockAgent(BedrockAgentOptions( name="Payment Support", description="Handles payment issues", agent_id="payment-agent-id", agent_alias_id="alias-id" )) ]))
# Use directlyresponse = await supervisor_agent.process_request( "I need to modify my flight and check my refund status", "user123", "session456")
Here’s a diagram illustrating the code implementation above, showing how the BedrockLLMAgent (Lead Agent) processes the user’s flight modification request by coordinating with LexBotAgent and Amazon BedrockAgent, supported by dual memory systems for maintaining conversation context.
2. As Part of Classifier-Based Architecture
The SupervisorAgent can also be integrated into a larger system using the classifier, enabling complex hierarchical architectures:
const orchestrator = new MultiAgentOrchestrator();
// Add individual agentsorchestrator.addAgent(new BedrockLLMAgent({ name: "General Assistant", description: "Handles general inquiries"}));
// Add a SupervisorAgent for complex support tasksorchestrator.addAgent(new SupervisorAgent({ name: "SupervisorAgent", description: "You are a supervisor agent that manages the team of agents for product development purposes", leadAgent: new BedrockLLMAgent({ name: "Support Team", description: "Coordinates support inquiries requiring multiple specialists" }), team: [techAgent, billingAgent, lexBookingBot]}));
// Add another SupervisorAgent for product developmentorchestrator.addAgent(new SupervisorAgent({ leadAgent: new AnthropicAgent({ name: "Product Team", description: "Coordinates product development and feature requests" }), team: [designAgent, engineeringAgent, productManagerAgent]}));
// Process through classifierconst response = await orchestrator.routeRequest( userInput, userId, sessionId);
orchestrator = MultiAgentOrchestrator()
# Add individual agentsorchestrator.add_agent(BedrockLLMAgent(BedrockLLMAgentOptions( name="General Assistant", description="Handles general inquiries")))
# Add a SupervisorAgent for complex support tasksorchestrator.add_agent(SupervisorAgent(SupervisorAgentOptions( name: "SupervisorAgent", description: "You are a supervisor agent that manages the team of agents for product development purposes", lead_agent=BedrockLLMAgent(BedrockLLMAgentOptions( name="Support Team", description="Coordinates support inquiries requiring multiple specialists" )), team=[tech_agent, billing_agent, lex_booking_bot])))
# Add another SupervisorAgent for product developmentorchestrator.add_agent(SupervisorAgent(SupervisorAgentOptions( lead_agent=AnthropicAgent(AnthropicAgentOptions( name="Product Team", description="Coordinates product development and feature requests" )), team=[design_agent, engineering_agent, product_manager_agent])))
# Process through classifierresponse = await orchestrator.route_request( user_input, user_id, session_id)
Here’s a diagram illustrating the code implementation above, showing a Classifier that routes user requests to appropriate teams. Three specialized units are shown: a General Assistant, a Support Team (handling tech, billing, and booking), and a Product Team (comprising design, engineering, and product management agents). Each team uses different agent types (BedrockLLMAgent, LexBotAgent, AnthropicAgent, AmazonBedrockAgent) based on their specific functions.
This flexibility allows you to:
- Use SupervisorAgent directly for dedicated team coordination
- Integrate it into classifier-based systems for dynamic routing
- Create hierarchical structures with multiple specialized teams
- Mix different types of agents (LexBot, Bedrock, Anthropic, etc.) in teams
- Scale and adapt the architecture as needs evolve
Core Components
1. Supervisor (Lead Agent)
- Must be either a BedrockLLMAgent or AnthropicAgent
- Acts as the central coordinator
- Communicates with team members through a tool interface
- Maintains conversation context with both user and team members
2. Team Members
- Collection of agents - each agent is wrapped as a tool for the supervisor
- Can be any agent type supported by the framework
- Operate independently and in parallel when possible
Memory Architecture
The SupervisorAgent implements a sophisticated three-tier memory system to maintain context across conversations:
1. User-Supervisor Memory
This is like the main conversation between a customer and the team leader:
User: I'm having trouble with my billing and the mobile app isn't workingAssistant: I understand you're having two issues. Let me help you with both your billing and app problems.User: Yes, the app crashes when I try to view my billAssistant: I'll look into both issues. Let me check with our technical and billing teams.
2. Supervisor-Team Memory
Each team member maintains a private conversation with the supervisor:
# Tech Support ConversationSupervisor: User is experiencing app crashes when viewing bills. Can you investigate?Tech Support: Based on the symptoms, this might be a cache issue. I'll provide steps to clear it.
# Billing Team ConversationSupervisor: Please check the user's billing statusBilling Team: Account is active, last payment received Jan 15, next due Feb 15
3. Combined Memory
The supervisor keeps track of all important information in an organized way:
<agents_memory>User: I'm having trouble with the mobile app, what should I do?Assistant: [Tech Support] The app crash is likely due to corrupted cache. Please ask the user to clear the app cache.User: What is the status of my bills?Assistant: [Billing Team] Account status is good. Last payment: Jan 15, Next due: Feb 15</agents_memory>
Memory Processing Flow
Here’s a complete interaction showing how memory and communication work together:
- Initial Request
User: I'm having trouble with my bill and the mobile app
- Parallel Processing
# Supervisor communicates simultaneously with both teamsSupervisor → Tech Support: What could cause app issues when viewing bills?Supervisor → Billing: Please verify account status
- Team Responses
Tech Support → Supervisor: Likely a cache issue. Common after recent updates.Billing → Supervisor: Account in good standing, no payment issues.
- Unified Response
Supervisor → User: I've checked both issues. Your billing account is in good standing. For the app problem, it appears to be a cache issue. Would you like me to guide you through clearing your app's cache?
Configuration
Configuration Options
interface SupervisorAgentOptions extends AgentOptions { leadAgent: BedrockLLMAgent | AnthropicAgent; // The agent that leads the team coordination team: Agent[]; // Team of agents to coordinate storage?: ChatStorage; // Memory storage implementation trace?: boolean; // Enable detailed logging extraTools?: AgentTools | AgentTool[]; // Additional tools for supervisor}
@dataclassclass SupervisorAgentOptions(AgentOptions): lead_agent: Agent # The agent that leads the team coordination team: list[Agent] # Team of agents that can help in resolving tasks storage: Optional[ChatStorage] # Memory storage for the team trace: Optional[bool] # Enable tracing/logging extra_tools: Optional[Union[AgentTools, list[AgentTool]]] # Additional tools for supervisor
Required Parameters
leadAgent
/lead_agent
: Must be either a BedrockLLMAgent or AnthropicAgent instanceteam
: List of agents that will be coordinated by the supervisor
Optional Parameters
storage
: Custom storage implementation for conversation history (defaults to InMemoryChatStorage)trace
: Enable detailed logging of agent interactionsextraTools
/extra_tools
: Additional tools to be made available to the supervisor
Built-in Tools
send_messages Tool
The SupervisorAgent includes a built-in tool for parallel message processing:
{ "name": "send_messages", "description": "Send messages to multiple agents in parallel.", "properties": { "messages": { "type": "array", "items": { "type": "object", "properties": { "recipient": { "type": "string", "description": "Agent name to send message to." }, "content": { "type": "string", "description": "Message content." } }, "required": ["recipient", "content"] }, "description": "Array of messages for different agents.", "minItems": 1 } }}
Adding Custom Tools
const customTools = [ new AgentTool({ name: "analyze_sentiment", description: "Analyze message sentiment", properties: { text: { type: "string", description: "Text to analyze" } }, required: ["text"], func: analyzeSentiment })];
const supervisorAgent = new SupervisorAgent({ leadAgent: supervisor, team: [techAgent, billingAgent], extraTools: customTools});
custom_tools = [ AgentTool( name="analyze_sentiment", description="Analyze message sentiment", properties={ "text": { "type": "string", "description": "Text to analyze" } }, required=["text"], func=analyze_sentiment )]
supervisor_agent = SupervisorAgent(SupervisorAgentOptions( lead_agent=supervisor, team=[tech_agent, billing_agent], extra_tools=custom_tools))
Communication Guidelines
-
Response Handling
- Aggregates responses from all relevant agents
- Maintains original agent responses without summarization
- Provides final answers only when all necessary responses are received
-
Agent Interaction
- Optimizes for parallel processing when possible
- Maintains agent isolation (agents are unaware of each other)
- Keeps inter-agent communications concise
-
Context Management
- Provides full context when necessary
- Reuses previous responses when appropriate
- Maintains efficient conversation history
-
Input Processing
- Forwards simple inputs directly to relevant agents
- Extracts all relevant data before creating action plans
- Never assumes parameter values
Best Practices
-
Agent Team Composition
- Choose specialized agents with clear, distinct roles
- Ensure agent descriptions are detailed and non-overlapping
- Consider communication patterns when selecting team size
-
Storage Configuration
- Use persistent storage (e.g., DynamoDBChatStorage) for production
- Consider memory usage with large conversation histories
- Implement appropriate cleanup strategies
-
Tool Management
- Add custom tools through extraTools/extra_tools parameter
- Keep tool functions focused and well-documented
- Consider performance impact of tool complexity
-
Performance Optimization
-
Performance Optimization
- Enable parallel processing where appropriate
- Monitor and adjust team size based on requirements
- Use tracing to identify bottlenecks
- Configure memory storage based on expected conversation volumes
Complete Example
Here’s a complete example showing how to use the SupervisorAgent in a typical scenario:
import { MultiAgentOrchestrator, BedrockLLMAgent, SupervisorAgent, DynamoDBChatStorage, AgentTool, AgentTools} from 'multi-agent-orchestrator';
// Function to analyze sentiment (implementation would go here)async function analyzeSentiment(text: string): Promise<{ sentiment: string; score: number }> { return { sentiment: "positive", score: 0.8 };}
async function main() { // Create orchestrator const orchestrator = new MultiAgentOrchestrator();
// Create supervisor (lead agent) const supervisor = new BedrockLLMAgent({ name: "Team Lead", description: "Coordinates specialized team members", modelId: "anthropic.claude-3-sonnet-20240229-v1:0" });
// Create team members const techAgent = new BedrockLLMAgent({ name: "Tech Support", description: "Handles technical issues", modelId: "anthropic.claude-3-sonnet-20240229-v1:0" });
const billingAgent = new BedrockLLMAgent({ name: "Billing Expert", description: "Handles billing and payment queries", modelId: "anthropic.claude-3-sonnet-20240229-v1:0" });
// Create custom tools const customTools = [ new AgentTool({ name: "analyze_sentiment", description: "Analyze message sentiment", properties: { text: { type: "string", description: "Text to analyze" } }, required: ["text"], func: analyzeSentiment }) ];
// Create SupervisorAgent const supervisorAgent = new SupervisorAgent({ leadAgent: supervisor, team: [techAgent, billingAgent], storage: new DynamoDBChatStorage("conversation-table", "us-east-1"), trace: true, extraTools: new AgentTools(customTools) });
// Add supervisor agent to orchestrator orchestrator.addAgent(supervisorAgent);
try { // Process request const response = await orchestrator.routeRequest( "I'm having issues with my bill and the mobile app", "user123", "session456" );
// Handle the response (streaming or non-streaming) if (response.streaming) { console.log("\n** STREAMING RESPONSE **"); console.log(`Agent: ${response.metadata.agentName}`);
// Handle streaming response for await (const chunk of response.output) { process.stdout.write(chunk); } } else { console.log("\n** RESPONSE **"); console.log(`Agent: ${response.metadata.agentName}`); console.log(`Response: ${response.output}`); } } catch (error) { console.error("Error processing request:", error); }}
// Run the examplemain().catch(console.error);
from multi_agent_orchestrator.orchestrator import MultiAgentOrchestratorfrom multi_agent_orchestrator.agents import ( SupervisorAgent, BedrockLLMAgent, SupervisorAgentOptions, BedrockLLMAgentOptions)from multi_agent_orchestrator.storage import DynamoDBChatStoragefrom multi_agent_orchestrator.utils import AgentTool, AgentTools
# Create orchestratororchestrator = MultiAgentOrchestrator()
# Create supervisor and teamsupervisor = BedrockLLMAgent(BedrockLLMAgentOptions( name="Team Lead", description="Coordinates specialized team members"))
tech_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name="Tech Support", description="Handles technical issues"))
billing_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name="Billing Expert", description="Handles billing and payment queries"))
# Create custom toolscustom_tools = [ AgentTool( name="analyze_sentiment", description="Analyze message sentiment", properties={ "text": { "type": "string", "description": "Text to analyze" } }, required=["text"], func=analyze_sentiment )]
# Create and add supervisor agentsupervisor_agent = SupervisorAgent(SupervisorAgentOptions( lead_agent=supervisor, team=[tech_agent, billing_agent], storage=DynamoDBChatStorage(), trace=True, extra_tools=custom_tools))
orchestrator.add_agent(supervisor_agent)
# Process requestasync def main(): response = await orchestrator.route_request( "I'm having issues with my bill and the mobile app", "user123", "session456" )
# Handle response based on whether it's streaming or not if response.streaming: print("\n** STREAMING RESPONSE **") print(f"Agent: {response.metadata.agent_name}") async for chunk in response.output: print(chunk, end='', flush=True) else: print("\n** RESPONSE **") print(f"Agent: {response.metadata.agent_name}") print(f"Response: {response.output}")
# Run the exampleif __name__ == "__main__": import asyncio asyncio.run(main())
Limitations
- LeadAgent must be either BedrockLLMAgent or AnthropicAgent
- May require significant memory for large conversation histories
- Performance depends on slowest agent in parallel operations
By leveraging the SupervisorAgent, you can create sophisticated multi-agent systems with coordinated responses, maintained context, and efficient parallel processing. The agent’s flexible architecture allows for customization while providing robust built-in capabilities for common coordination tasks.