Static vs Dynamic Agents
Understanding the Difference
| Aspect | Static Agent | Dynamic Agent |
|---|---|---|
| Configuration | Set once at startup | Per-request based on call data |
| Behavior | Same for all callers | Different for different callers |
Use Static When:
- Same prompt for everyone
- Generic assistant
- Simple IVR
- FAQ bot
Use Dynamic When:
- Personalized greetings
- Caller-specific data
- Account-based routing
- Multi-tenant applications
Static Agents
Static agents have fixed configuration determined at instantiation time.
Example: Static Customer Service Agent
Python
TypeScript
1 from signalwire import AgentBase, FunctionResult 2 3 class StaticSupportAgent(AgentBase): 4 """Same behavior for all callers.""" 5 6 def __init__(self): 7 super().__init__(name="static-support") 8 9 self.add_language("English", "en-US", "rime.spore") 10 11 self.prompt_add_section( 12 "Role", 13 "You are a customer service agent for Acme Corp. " 14 "Help callers with general inquiries about our products." 15 ) 16 17 self.prompt_add_section( 18 "Guidelines", 19 bullets=[ 20 "Be helpful and professional", 21 "Answer questions about products", 22 "Transfer complex issues to support" 23 ] 24 ) 25 26 self.define_tool( 27 name="get_store_hours", 28 description="Get store hours", 29 parameters={}, 30 handler=self.get_store_hours 31 ) 32 33 def get_store_hours(self, args, raw_data): 34 return FunctionResult( 35 "We're open Monday through Friday, 9 AM to 5 PM." 36 ) 37 38 if __name__ == "__main__": 39 agent = StaticSupportAgent() 40 agent.run()
Dynamic Agents
Dynamic agents customize their behavior based on the incoming request using the on_swml_request method.
The on_swml_request Method
| Language | Hook Method |
|---|---|
| Python | def on_swml_request(self, request_data=None, callback_path=None, request=None) |
| TypeScript | onSwmlRequest(requestData?, callbackPath?, request?) |
1 def on_swml_request(self, request_data=None, callback_path=None, request=None): 2 """ 3 Called before SWML is generated for each request. 4 5 Args: 6 request_data: Optional dict containing the parsed POST body from SignalWire. 7 Call information is nested under the 'call' key. 8 callback_path: Optional callback path for routing 9 request: Optional FastAPI Request object 10 """ 11 pass
Example: Dynamic Personalized Agent
1 from signalwire import AgentBase, FunctionResult 2 3 class DynamicPersonalizedAgent(AgentBase): 4 """Customizes greeting based on caller.""" 5 6 CUSTOMERS = { 7 "+15551234567": {"name": "John Smith", "tier": "gold", "account": "A001"}, 8 "+15559876543": {"name": "Jane Doe", "tier": "platinum", "account": "A002"}, 9 } 10 11 def __init__(self): 12 super().__init__(name="dynamic-agent") 13 self.add_language("English", "en-US", "rime.spore") 14 self.set_params({"end_of_speech_timeout": 500, "attention_timeout": 15000}) 15 self.define_tool(name="get_account_status", description="Get the caller's account status", parameters={}, handler=self.get_account_status) 16 self._current_caller = None 17 18 def on_swml_request(self, request_data=None, callback_path=None, request=None): 19 call_data = (request_data or {}).get("call", {}) 20 caller_num = call_data.get("from") or call_data.get("from_number", "") 21 customer = self.CUSTOMERS.get(caller_num) 22 23 if customer: 24 self._current_caller = customer 25 self.prompt_add_section("Role", 26 f"You are a premium support agent for Acme Corp. " 27 f"You are speaking with {customer['name']}, a {customer['tier']} member.") 28 self.prompt_add_section("Context", 29 f"Customer account: {customer['account']}\nMembership tier: {customer['tier'].upper()}") 30 if customer["tier"] == "platinum": 31 self.prompt_add_section("Special Treatment", 32 "This is a platinum customer. Prioritize their requests and offer expedited service on all issues.") 33 else: 34 self._current_caller = None 35 self.prompt_add_section("Role", 36 "You are a customer service agent for Acme Corp. Help the caller with their inquiry and offer to create an account.") 37 38 def get_account_status(self, args, raw_data): 39 if self._current_caller: 40 return FunctionResult(f"Account {self._current_caller['account']} is active. Tier: {self._current_caller['tier'].upper()}") 41 return FunctionResult("No account found. Would you like to create one?") 42 43 if __name__ == "__main__": 44 agent = DynamicPersonalizedAgent() 45 agent.run()
Request Data Fields
The request_data dictionary is the parsed POST body from SignalWire. Call information is nested under the call key:
| Field | Description | Example |
|---|---|---|
call["call_id"] | Unique call identifier | "a1b2c3d4-..." |
call["from"] | Caller’s phone number | "+15551234567" |
call["from_number"] | Alternative caller number field | "+15551234567" |
call["to"] | Number that was called | "+15559876543" |
call["direction"] | Call direction | "inbound" |
Always use defensive access when working with request_data.
1 def on_swml_request(self, request_data=None, callback_path=None, request=None): 2 call_data = (request_data or {}).get("call", {}) 3 caller_num = call_data.get("from") or call_data.get("from_number", "") 4 call_id = call_data.get("call_id", "")
Dynamic Function Registration
You can also register functions dynamically based on the caller:
1 class DynamicFunctionsAgent(AgentBase): 2 """Different functions for different callers.""" 3 4 ADMIN_NUMBERS = ["+15551111111", "+15552222222"] 5 6 def __init__(self): 7 super().__init__(name="dynamic-functions") 8 self.add_language("English", "en-US", "rime.spore") 9 self.define_tool(name="get_info", description="Get general information", parameters={}, handler=self.get_info) 10 11 def on_swml_request(self, request_data=None, callback_path=None, request=None): 12 call_data = (request_data or {}).get("call", {}) 13 caller_num = call_data.get("from") or call_data.get("from_number", "") 14 15 self.prompt_add_section("Role", "You are a helpful assistant.") 16 17 if caller_num in self.ADMIN_NUMBERS: 18 self.prompt_add_section("Admin Access", 19 "This caller has administrator privileges. They can access system administration functions.") 20 self.define_tool(name="admin_reset", description="Reset system configuration (admin only)", parameters={}, handler=self.admin_reset) 21 self.define_tool(name="admin_report", description="Generate system report (admin only)", parameters={}, handler=self.admin_report) 22 23 def get_info(self, args, raw_data): 24 return FunctionResult("General information...") 25 26 def admin_reset(self, args, raw_data): 27 return FunctionResult("System reset initiated.") 28 29 def admin_report(self, args, raw_data): 30 return FunctionResult("Report generated: All systems operational.")
Multi-Tenant Applications
Dynamic agents are ideal for multi-tenant scenarios:
1 class MultiTenantAgent(AgentBase): 2 """Different branding per tenant.""" 3 4 TENANTS = { 5 "+15551111111": {"company": "Acme Corp", "voice": "rime.spore", "greeting": "Welcome to Acme Corp support!"}, 6 "+15552222222": {"company": "Beta Industries", "voice": "rime.marsh", "greeting": "Thank you for calling Beta Industries!"} 7 } 8 9 def __init__(self): 10 super().__init__(name="multi-tenant") 11 12 def on_swml_request(self, request_data=None, callback_path=None, request=None): 13 call_data = (request_data or {}).get("call", {}) 14 called_num = call_data.get("to", "") 15 16 tenant = self.TENANTS.get(called_num, {"company": "Default Company", "voice": "rime.spore", "greeting": "Hello!"}) 17 18 self.add_language("English", "en-US", tenant["voice"]) 19 self.prompt_add_section("Role", 20 f"You are a customer service agent for {tenant['company']}. Start by saying: {tenant['greeting']}")
Comparison Summary
| Aspect | Static | Dynamic |
|---|---|---|
| Configuration | Once at startup | Per-request |
| Performance | Slightly faster | Minimal overhead |
| Use Case | Generic assistants | Personalized experiences |
| Complexity | Simpler | More complex |
| Testing | Easier | Requires more scenarios |
| Method | __init__ only | on_swml_request |
Best Practices
- Start static, go dynamic when needed - Don’t over-engineer
- Cache expensive lookups - Database calls in
on_swml_requestadd latency - Clear prompts between calls - Use
self.pom.clear()if reusing sections - Log caller info - Helps with debugging dynamic behavior
- Test multiple scenarios - Each caller path needs testing
1 def on_swml_request(self, request_data=None, callback_path=None, request=None): 2 # Clear previous dynamic configuration 3 self.pom.clear() 4 5 # Extract call data 6 call_data = (request_data or {}).get("call", {}) 7 8 # Log for debugging 9 self.log.info("request_received", 10 caller=call_data.get("from") or call_data.get("from_number"), 11 called=call_data.get("to") 12 ) 13 14 # Configure based on request 15 self._configure_for_caller(request_data)