Lesson 3: Building Multi-Agent Systems 🔗 ↑ TOC

In this lesson, you'll learn how to create sophisticated multi-agent systems where specialized agents work together. We'll build the complete PC Builder Pro system with three agents: Alex (triage), Morgan (sales), and Sam (support).

Table of Contents 🔗 ↑ TOC

  1. Understanding Multi-Agent Architecture
  2. The AgentServer Class
  3. Dynamic Configuration
  4. Agent-to-Agent Transfers
  5. Building the Complete System
  6. Testing Multi-Agent Flows
  7. Production Considerations
  8. Summary

Understanding Multi-Agent Architecture 🔗 ↑ TOC

Multi-agent systems allow you to create specialized agents that handle different aspects of customer interaction. This provides several benefits:

Advantages:

Our System Architecture:

Customer → Triage Agent (Alex) → ┬→ Sales Agent (Morgan)
                                 └→ Support Agent (Sam)

Key Components:

  1. AgentServer: Hosts multiple agents on the same port
  2. Routes: Each agent has a unique endpoint (/, /sales, /support)
  3. Transfer Mechanism: swml_transfer skill for seamless handoffs
  4. Context Preservation: Customer data flows between agents

The AgentServer Class 🔗 ↑ TOC

The AgentServer class allows you to host multiple agents on a single port, each with its own route.

Basic Usage 🔗 ↑ TOC

from signalwire_agents import AgentServer, AgentBase

# Create the server
server = AgentServer(
    host="0.0.0.0",
    port=3001,
    log_level="info"
)

# Create and register agents
triage_agent = TriageAgent()
server.register(triage_agent, "/")

sales_agent = SalesAgent()
server.register(sales_agent, "/sales")

# Run the server
server.run()

Server Features 🔗 ↑ TOC

Automatic Features:

Configuration Options:

server = AgentServer(
    host="0.0.0.0",        # Network interface
    port=3001,             # TCP port
    log_level="debug",     # Logging verbosity
    auth_user="custom",    # Override auto-generated auth
    auth_pass="secret"     # Override auto-generated password
)

Dynamic Configuration 🔗 ↑ TOC

Dynamic configuration allows agents to adapt their behavior based on request parameters. This is crucial for:

How It Works 🔗 ↑ TOC

def configure_transfer_tools(self, query_params, body_params, headers, agent):
    """
    Called for every request before processing

    Args:
        query_params: URL query parameters (dict)
        body_params: POST body parameters (dict)
        headers: HTTP headers (dict)
        agent: The agent instance to configure
    """
    # Access proxy-aware URL building
    base_url = agent.get_full_url(include_auth=True)

    # Configure agent based on request
    if query_params.get('transfer') == 'true':
        # This is a transfer call
        agent.prompt_add_section(...)

Key Method: get_full_url() 🔗 ↑ TOC

This method intelligently builds URLs that work with:

# Returns the correct URL for the current environment
url = agent.get_full_url(include_auth=True)
# Examples:
# Direct: https://user:pass@yourdomain.com:3001
# Proxy: https://user:pass@proxy.signalwire.com/agent-id

Agent-to-Agent Transfers 🔗 ↑ TOC

The swml_transfer skill enables seamless handoffs between agents while preserving context.

Understanding swml_transfer 🔗 ↑ TOC

agent.add_skill("swml_transfer", {
    "tool_name": "transfer_to_specialist",
    "description": "Transfer to sales or support specialist",
    "parameter_name": "specialist_type",
    "parameter_description": "The type of specialist (sales or support)",
    "required_fields": {
        "user_name": "The customer's name",
        "summary": "Summary of the conversation"
    },
    "transfers": {
        "/sales/i": {  # Regex pattern
            "url": sales_url,
            "message": "Transferring to sales...",
            "return_message": "Call complete."
        }
    }
})

Key Features 🔗 ↑ TOC

Required Fields:

Transfer Configuration:

Accessing Transfer Data 🔗 ↑ TOC

In the receiving agent:

"The customer's name is ${call_data.user_name}"
"They were transferred because: ${call_data.summary}"

Building the Complete System 🔗 ↑ TOC

Let's examine the complete PC Builder Pro system in pc_builder.py:

1. Triage Agent (Alex) 🔗 ↑ TOC

class TriageAgent(AgentBase):
    def __init__(self):
        super().__init__(
            name="PC Builder Triage Agent",
            route="/",  # Root route
            host="0.0.0.0",
            port=3001
        )

        # Configure prompt
        self._configure_prompt()

        # Set voice
        self.add_language(
            name="English",
            code="en-US",
            voice="rime.spore"  # Energetic voice
        )

        # Dynamic configuration for transfers
        self.set_dynamic_config_callback(self.configure_transfer_tools)

2. Dynamic Transfer Configuration 🔗 ↑ TOC

def configure_transfer_tools(self, query_params, body_params, headers, agent):
    # Build URLs with proxy detection
    sales_url = agent.get_full_url(include_auth=True).rstrip('/') + "/sales?transfer=true"
    support_url = agent.get_full_url(include_auth=True).rstrip('/') + "/support?transfer=true"

    # Configure transfers
    agent.add_skill("swml_transfer", {
        "tool_name": "transfer_to_specialist",
        "required_fields": {
            "user_name": "The customer's name",
            "summary": "A comprehensive summary of the conversation"
        },
        "transfers": {
            "/sales/i": {
                "url": sales_url,
                "message": "Perfect! Let me transfer you to our sales specialist.",
                "post_process": True
            },
            "/support/i": {
                "url": support_url,
                "message": "I'll connect you with technical support.",
                "post_process": True
            }
        }
    })

3. Sales Agent with Transfer Detection 🔗 ↑ TOC

class SalesAgent(AgentBase):
    def __init__(self):
        # ... initialization ...

        # Dynamic prompt configuration
        self.set_dynamic_config_callback(self.configure_dynamic_prompt)

    def configure_dynamic_prompt(self, query_params, body_params, headers, agent):
        if query_params.get('transfer') == 'true':
            # This is a transfer - add context
            agent.prompt_add_section(
                "Call Transfer Information",
                body="This call has been transferred from triage.",
                bullets=[
                    "Customer name: ${call_data.user_name}",
                    "Transfer reason: ${call_data.summary}",
                    "Greet them by name and acknowledge the transfer"
                ]
            )
        else:
            # Direct call - standard greeting
            agent.prompt_add_section(
                "Initial Greeting",
                body="This is a direct call to sales.",
                bullets=["Greet warmly", "Ask for name", "Understand needs"]
            )

4. Creating the Server 🔗 ↑ TOC

def create_pc_builder_app(host="0.0.0.0", port=3001):
    # Create server
    server = AgentServer(host=host, port=port)

    # Create and register agents
    triage = TriageAgent()
    server.register(triage, "/")

    sales = SalesAgent()
    server.register(sales, "/sales")

    support = SupportAgent()
    server.register(support, "/support")

    # Add info endpoint
    @server.app.get("/info")
    async def info():
        return {
            "agents": {
                "triage": {"endpoint": "/"},
                "sales": {"endpoint": "/sales"},
                "support": {"endpoint": "/support"}
            }
        }

    return server

Testing Multi-Agent Flows 🔗 ↑ TOC

Starting the System 🔗 ↑ TOC

# Run the complete system
python tutorial/pc_builder.py

# You'll see:
# Triage Agent (Alex): http://localhost:3001/
# Sales Agent (Morgan): http://localhost:3001/sales
# Support Agent (Sam): http://localhost:3001/support

Testing Individual Agents 🔗 ↑ TOC

# Test triage agent
curl http://localhost:3001/

# Test sales agent directly
curl http://localhost:3001/sales

# Test with transfer flag
curl "http://localhost:3001/sales?transfer=true"

Testing Transfer Flow 🔗 ↑ TOC

  1. Call Triage Agent: Start with Alex
  2. Provide Information: Give name and describe needs
  3. Request Transfer: Say "I want to buy a gaming PC"
  4. Observe Handoff: See context preserved in sales

Monitoring Transfers 🔗 ↑ TOC

Look for these in the logs:


Production Considerations 🔗 ↑ TOC

Security 🔗 ↑ TOC

Authentication:

# Set custom credentials
export SWML_AUTH_USER=myuser
export SWML_AUTH_PASS=mypassword

# Or let the system generate them (check logs)

SSL/HTTPS:

SWML_SSL_ENABLED=true \
SWML_SSL_CERT_PATH=/path/to/cert.pem \
SWML_SSL_KEY_PATH=/path/to/key.pem \
python tutorial/pc_builder.py

Deployment Patterns 🔗 ↑ TOC

1. Single Server:

2. Distributed Agents:

3. Lambda/Serverless:

def lambda_handler(event, context):
    server = create_pc_builder_app()
    return server.run(event, context)

Monitoring 🔗 ↑ TOC

Health Checks:

# Built-in health endpoint
curl http://localhost:3001/health

Logging:

# Set appropriate log level
server = AgentServer(log_level="info")  # or "debug" for more detail

Metrics to Track:


Summary 🔗 ↑ TOC

You've built a complete multi-agent system! You've mastered:

Core Concepts:

Architecture Patterns:

What's Next?

In the next lesson, you'll learn advanced features including custom SWAIG functions, error handling, and production deployment strategies.

Practice Exercises 🔗 ↑ TOC

  1. Add a Fourth Agent: Create a billing agent and add transfer routes
  2. Custom Transfer Messages: Personalize transfer messages based on context
  3. Transfer Validation: Add logic to prevent invalid transfers
  4. Multi-Language: Add Spanish versions of each agent

Troubleshooting 🔗 ↑ TOC

Common Issues:


← Lesson 2: Adding Intelligence with Knowledge Bases | Tutorial Overview | Lesson 4: Advanced Features →