Static vs Dynamic Agents

View as MarkdownOpen in Claude

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

from signalwire import AgentBase, FunctionResult
class StaticSupportAgent(AgentBase):
"""Same behavior for all callers."""
def __init__(self):
super().__init__(name="static-support")
self.add_language("English", "en-US", "rime.spore")
self.prompt_add_section(
"Role",
"You are a customer service agent for Acme Corp. "
"Help callers with general inquiries about our products."
)
self.prompt_add_section(
"Guidelines",
bullets=[
"Be helpful and professional",
"Answer questions about products",
"Transfer complex issues to support"
]
)
self.define_tool(
name="get_store_hours",
description="Get store hours",
parameters={},
handler=self.get_store_hours
)
def get_store_hours(self, args, raw_data):
return FunctionResult(
"We're open Monday through Friday, 9 AM to 5 PM."
)
if __name__ == "__main__":
agent = StaticSupportAgent()
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

LanguageHook Method
Pythondef on_swml_request(self, request_data=None, callback_path=None, request=None)
TypeScriptonSwmlRequest(requestData?, callbackPath?, request?)
def on_swml_request(self, request_data=None, callback_path=None, request=None):
"""
Called before SWML is generated for each request.
Args:
request_data: Optional dict containing the parsed POST body from SignalWire.
Call information is nested under the 'call' key.
callback_path: Optional callback path for routing
request: Optional FastAPI Request object
"""
pass

Example: Dynamic Personalized Agent

from signalwire import AgentBase, FunctionResult
class DynamicPersonalizedAgent(AgentBase):
"""Customizes greeting based on caller."""
CUSTOMERS = {
"+15551234567": {"name": "John Smith", "tier": "gold", "account": "A001"},
"+15559876543": {"name": "Jane Doe", "tier": "platinum", "account": "A002"},
}
def __init__(self):
super().__init__(name="dynamic-agent")
self.add_language("English", "en-US", "rime.spore")
self.set_params({"end_of_speech_timeout": 500, "attention_timeout": 15000})
self.define_tool(name="get_account_status", description="Get the caller's account status", parameters={}, handler=self.get_account_status)
self._current_caller = None
def on_swml_request(self, request_data=None, callback_path=None, request=None):
call_data = (request_data or {}).get("call", {})
caller_num = call_data.get("from") or call_data.get("from_number", "")
customer = self.CUSTOMERS.get(caller_num)
if customer:
self._current_caller = customer
self.prompt_add_section("Role",
f"You are a premium support agent for Acme Corp. "
f"You are speaking with {customer['name']}, a {customer['tier']} member.")
self.prompt_add_section("Context",
f"Customer account: {customer['account']}\nMembership tier: {customer['tier'].upper()}")
if customer["tier"] == "platinum":
self.prompt_add_section("Special Treatment",
"This is a platinum customer. Prioritize their requests and offer expedited service on all issues.")
else:
self._current_caller = None
self.prompt_add_section("Role",
"You are a customer service agent for Acme Corp. Help the caller with their inquiry and offer to create an account.")
def get_account_status(self, args, raw_data):
if self._current_caller:
return FunctionResult(f"Account {self._current_caller['account']} is active. Tier: {self._current_caller['tier'].upper()}")
return FunctionResult("No account found. Would you like to create one?")
if __name__ == "__main__":
agent = DynamicPersonalizedAgent()
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"

Always use defensive access when working with request_data.

def on_swml_request(self, request_data=None, callback_path=None, request=None):
call_data = (request_data or {}).get("call", {})
caller_num = call_data.get("from") or call_data.get("from_number", "")
call_id = call_data.get("call_id", "")

Dynamic Function Registration

You can also register functions dynamically based on the caller:

class DynamicFunctionsAgent(AgentBase):
"""Different functions for different callers."""
ADMIN_NUMBERS = ["+15551111111", "+15552222222"]
def __init__(self):
super().__init__(name="dynamic-functions")
self.add_language("English", "en-US", "rime.spore")
self.define_tool(name="get_info", description="Get general information", parameters={}, handler=self.get_info)
def on_swml_request(self, request_data=None, callback_path=None, request=None):
call_data = (request_data or {}).get("call", {})
caller_num = call_data.get("from") or call_data.get("from_number", "")
self.prompt_add_section("Role", "You are a helpful assistant.")
if caller_num in self.ADMIN_NUMBERS:
self.prompt_add_section("Admin Access",
"This caller has administrator privileges. They can access system administration functions.")
self.define_tool(name="admin_reset", description="Reset system configuration (admin only)", parameters={}, handler=self.admin_reset)
self.define_tool(name="admin_report", description="Generate system report (admin only)", parameters={}, handler=self.admin_report)
def get_info(self, args, raw_data):
return FunctionResult("General information...")
def admin_reset(self, args, raw_data):
return FunctionResult("System reset initiated.")
def admin_report(self, args, raw_data):
return FunctionResult("Report generated: All systems operational.")

Multi-Tenant Applications

Dynamic agents are ideal for multi-tenant scenarios:

class MultiTenantAgent(AgentBase):
"""Different branding per tenant."""
TENANTS = {
"+15551111111": {"company": "Acme Corp", "voice": "rime.spore", "greeting": "Welcome to Acme Corp support!"},
"+15552222222": {"company": "Beta Industries", "voice": "rime.marsh", "greeting": "Thank you for calling Beta Industries!"}
}
def __init__(self):
super().__init__(name="multi-tenant")
def on_swml_request(self, request_data=None, callback_path=None, request=None):
call_data = (request_data or {}).get("call", {})
called_num = call_data.get("to", "")
tenant = self.TENANTS.get(called_num, {"company": "Default Company", "voice": "rime.spore", "greeting": "Hello!"})
self.add_language("English", "en-US", tenant["voice"])
self.prompt_add_section("Role",
f"You are a customer service agent for {tenant['company']}. Start by saying: {tenant['greeting']}")

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
def on_swml_request(self, request_data=None, callback_path=None, request=None):
# Clear previous dynamic configuration
self.pom.clear()
# Extract call data
call_data = (request_data or {}).get("call", {})
# Log for debugging
self.log.info("request_received",
caller=call_data.get("from") or call_data.get("from_number"),
called=call_data.get("to")
)
# Configure based on request
self._configure_for_caller(request_data)