Static or Dynamic agents?

View as Markdown

Static vs Dynamic Agents

Choose between static agents (fixed configuration) and dynamic agents (runtime customization) based on whether you need per-call personalization.

Understanding the Difference

AspectStatic AgentDynamic Agent
ConfigurationSet once at startupPer-request based on call data
BehaviorSame for all callersDifferent 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

1from signalwire_agents import AgentBase, SwaigFunctionResult
2
3
4class StaticSupportAgent(AgentBase):
5 """Same behavior for all callers."""
6
7 def __init__(self):
8 super().__init__(name="static-support")
9
10 self.add_language("English", "en-US", "rime.spore")
11
12 self.prompt_add_section(
13 "Role",
14 "You are a customer service agent for Acme Corp. "
15 "Help callers with general inquiries about our products."
16 )
17
18 self.prompt_add_section(
19 "Guidelines",
20 bullets=[
21 "Be helpful and professional",
22 "Answer questions about products",
23 "Transfer complex issues to support"
24 ]
25 )
26
27 self.define_tool(
28 name="get_store_hours",
29 description="Get store hours",
30 parameters={},
31 handler=self.get_store_hours
32 )
33
34 def get_store_hours(self, args, raw_data):
35 return SwaigFunctionResult(
36 "We're open Monday through Friday, 9 AM to 5 PM."
37 )
38
39
40if __name__ == "__main__":
41 agent = StaticSupportAgent()
42 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

1def 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 - call["call_id"]: Unique call identifier
9 - call["from"]: Caller's phone number
10 - call["from_number"]: Alternative caller number field
11 - call["to"]: Number that was called
12 - call["direction"]: "inbound" or "outbound"
13 callback_path: Optional callback path for routing
14 request: Optional FastAPI Request object for accessing query params, headers, etc.
15
16 Returns:
17 Optional dict with modifications to apply (usually None for simple cases)
18 """
19 pass

Example: Dynamic Personalized Agent

1from signalwire_agents import AgentBase, SwaigFunctionResult
2
3
4class DynamicPersonalizedAgent(AgentBase):
5 """Customizes greeting based on caller."""
6
7 # Simulated customer database
8 CUSTOMERS = {
9 "+15551234567": {"name": "John Smith", "tier": "gold", "account": "A001"},
10 "+15559876543": {"name": "Jane Doe", "tier": "platinum", "account": "A002"},
11 }
12
13 def __init__(self):
14 super().__init__(name="dynamic-agent")
15
16 self.add_language("English", "en-US", "rime.spore")
17
18 # Base configuration
19 self.set_params({
20 "end_of_speech_timeout": 500,
21 "attention_timeout": 15000
22 })
23
24 # Functions available to all callers
25 self.define_tool(
26 name="get_account_status",
27 description="Get the caller's account status",
28 parameters={},
29 handler=self.get_account_status
30 )
31
32 # Store caller info for function access
33 self._current_caller = None
34
35 def on_swml_request(self, request_data=None, callback_path=None, request=None):
36 """Customize behavior based on caller."""
37 # Extract call data (nested under 'call' key in request_data)
38 call_data = (request_data or {}).get("call", {})
39 caller_num = call_data.get("from") or call_data.get("from_number", "")
40
41 # Look up caller in database
42 customer = self.CUSTOMERS.get(caller_num)
43
44 if customer:
45 # Known customer - personalized experience
46 self._current_caller = customer
47
48 self.prompt_add_section(
49 "Role",
50 f"You are a premium support agent for Acme Corp. "
51 f"You are speaking with {customer['name']}, a {customer['tier']} member."
52 )
53
54 self.prompt_add_section(
55 "Context",
56 f"Customer account: {customer['account']}\n"
57 f"Membership tier: {customer['tier'].upper()}"
58 )
59
60 if customer["tier"] == "platinum":
61 self.prompt_add_section(
62 "Special Treatment",
63 "This is a platinum customer. Prioritize their requests and "
64 "offer expedited service on all issues."
65 )
66 else:
67 # Unknown caller - generic experience
68 self._current_caller = None
69
70 self.prompt_add_section(
71 "Role",
72 "You are a customer service agent for Acme Corp. "
73 "Help the caller with their inquiry and offer to create an account."
74 )
75
76 def get_account_status(self, args, raw_data):
77 if self._current_caller:
78 return SwaigFunctionResult(
79 f"Account {self._current_caller['account']} is active. "
80 f"Tier: {self._current_caller['tier'].upper()}"
81 )
82 return SwaigFunctionResult(
83 "No account found. Would you like to create one?"
84 )
85
86
87if __name__ == "__main__":
88 agent = DynamicPersonalizedAgent()
89 agent.run()

Request Data Fields

The request_data dictionary is the parsed POST body from SignalWire. Call information is nested under the call key:

FieldDescriptionExample
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"

Important: Always use defensive access when working with request_data:

1def 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:

1class 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
10 # Base functions for everyone
11 self.define_tool(
12 name="get_info",
13 description="Get general information",
14 parameters={},
15 handler=self.get_info
16 )
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
22 self.prompt_add_section("Role", "You are a helpful assistant.")
23
24 # Add admin functions only for admin callers
25 if caller_num in self.ADMIN_NUMBERS:
26 self.prompt_add_section(
27 "Admin Access",
28 "This caller has administrator privileges. "
29 "They can access system administration functions."
30 )
31
32 self.define_tool(
33 name="admin_reset",
34 description="Reset system configuration (admin only)",
35 parameters={},
36 handler=self.admin_reset
37 )
38
39 self.define_tool(
40 name="admin_report",
41 description="Generate system report (admin only)",
42 parameters={},
43 handler=self.admin_report
44 )
45
46 def get_info(self, args, raw_data):
47 return SwaigFunctionResult("General information...")
48
49 def admin_reset(self, args, raw_data):
50 return SwaigFunctionResult("System reset initiated.")
51
52 def admin_report(self, args, raw_data):
53 return SwaigFunctionResult("Report generated: All systems operational.")

Multi-Tenant Applications

Dynamic agents are ideal for multi-tenant scenarios:

1class MultiTenantAgent(AgentBase):
2 """Different branding per tenant."""
3
4 TENANTS = {
5 "+15551111111": {
6 "company": "Acme Corp",
7 "voice": "rime.spore",
8 "greeting": "Welcome to Acme Corp support!"
9 },
10 "+15552222222": {
11 "company": "Beta Industries",
12 "voice": "rime.marsh",
13 "greeting": "Thank you for calling Beta Industries!"
14 }
15 }
16
17 def __init__(self):
18 super().__init__(name="multi-tenant")
19
20 def on_swml_request(self, request_data=None, callback_path=None, request=None):
21 call_data = (request_data or {}).get("call", {})
22 called_num = call_data.get("to", "")
23
24 tenant = self.TENANTS.get(called_num, {
25 "company": "Default Company",
26 "voice": "rime.spore",
27 "greeting": "Hello!"
28 })
29
30 # Configure for this tenant
31 self.add_language("English", "en-US", tenant["voice"])
32
33 self.prompt_add_section(
34 "Role",
35 f"You are a customer service agent for {tenant['company']}. "
36 f"Start by saying: {tenant['greeting']}"
37 )

Comparison Summary

AspectStaticDynamic
ConfigurationOnce at startupPer-request
PerformanceSlightly fasterMinimal overhead
Use CaseGeneric assistantsPersonalized experiences
ComplexitySimplerMore complex
TestingEasierRequires more scenarios
Method__init__ onlyon_swml_request

Best Practices

  1. Start static, go dynamic when needed - Don’t over-engineer
  2. Cache expensive lookups - Database calls in on_swml_request add latency
  3. Clear prompts between calls - Use self.pom.clear() if reusing sections
  4. Log caller info - Helps with debugging dynamic behavior
  5. Test multiple scenarios - Each caller path needs testing
1def on_swml_request(self, request_data=None, callback_path=None, request=None):
2 # Clear previous dynamic configuration
3 self.pom.clear()
4
5 # Log for debugging
6 call_data = (request_data or {}).get("call", {})
7 self.log.info("request_received",
8 caller=call_data.get("from"),
9 called=call_data.get("to")
10 )
11
12 # Configure based on request
13 self._configure_for_caller(request_data)