Skip to main content

Static Vs Dynamic

Static vs Dynamic Agents

Summary: 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

from signalwire_agents import AgentBase, SwaigFunctionResult


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 SwaigFunctionResult(
"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

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:
- call["call_id"]: Unique call identifier
- call["from"]: Caller's phone number
- call["from_number"]: Alternative caller number field
- call["to"]: Number that was called
- call["direction"]: "inbound" or "outbound"
callback_path: Optional callback path for routing
request: Optional FastAPI Request object for accessing query params, headers, etc.

Returns:
Optional dict with modifications to apply (usually None for simple cases)
"""
pass

Example: Dynamic Personalized Agent

from signalwire_agents import AgentBase, SwaigFunctionResult


class DynamicPersonalizedAgent(AgentBase):
"""Customizes greeting based on caller."""

# Simulated customer database
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")

# Base configuration
self.set_params({
"end_of_speech_timeout": 500,
"attention_timeout": 15000
})

# Functions available to all callers
self.define_tool(
name="get_account_status",
description="Get the caller's account status",
parameters={},
handler=self.get_account_status
)

# Store caller info for function access
self._current_caller = None

def on_swml_request(self, request_data=None, callback_path=None, request=None):
"""Customize behavior based on caller."""
# Extract call data (nested under 'call' key in request_data)
call_data = (request_data or {}).get("call", {})
caller_num = call_data.get("from") or call_data.get("from_number", "")

# Look up caller in database
customer = self.CUSTOMERS.get(caller_num)

if customer:
# Known customer - personalized experience
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']}\n"
f"Membership 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:
# Unknown caller - generic experience
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 SwaigFunctionResult(
f"Account {self._current_caller['account']} is active. "
f"Tier: {self._current_caller['tier'].upper()}"
)
return SwaigFunctionResult(
"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"

Important: 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")

# Base functions for everyone
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.")

# Add admin functions only for admin callers
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 SwaigFunctionResult("General information...")

def admin_reset(self, args, raw_data):
return SwaigFunctionResult("System reset initiated.")

def admin_report(self, args, raw_data):
return SwaigFunctionResult("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!"
})

# Configure for this tenant
self.add_language("English", "en-US", tenant["voice"])

self.prompt_add_section(
"Role",
f"You are a customer service agent for {tenant['company']}. "
f"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()

# Log for debugging
call_data = (request_data or {}).get("call", {})
self.log.info("request_received",
caller=call_data.get("from"),
called=call_data.get("to")
)

# Configure based on request
self._configure_for_caller(request_data)