Skip to content

Call Flow Customization

For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.

Understanding Call Flow

By default, AgentBase generates a simple call flow:

answer -> ai

The SDK provides three insertion points to customize this flow:

Call flow insertion points for pre-answer, post-answer, and post-AI verbs.

Call flow insertion points

Verb Insertion Methods

MethodPurposeCommon Uses
add_pre_answer_verb()Before answeringRingback, screening, routing
add_post_answer_verb()After answer, before AIAnnouncements, disclaimers
add_post_ai_verb()After AI endsCleanup, transfers, surveys

The verb insertion methods are available in all languages:

LanguagePre-AnswerPost-AnswerPost-AI
Pythonadd_pre_answer_verb(verb, params)add_post_answer_verb(verb, params)add_post_ai_verb(verb, params)
TypeScriptaddPreAnswerVerb(verb, params)addPostAnswerVerb(verb, params)addPostAiVerb(verb, params)

Pre-Answer Verbs

Pre-answer verbs run while the call is still ringing. Use them for:

  • Ringback tones: Play audio before answering
  • Call screening: Check caller ID or time
  • Conditional routing: Route based on variables
Python
TypeScript
#!/usr/bin/env python3
from signalwire import AgentBase

class RingbackAgent(AgentBase):
    """Agent that plays ringback tone before answering."""

    def __init__(self):
        super().__init__(name="ringback", port=3000)

        # Play US ringback tone before answering
        # IMPORTANT: auto_answer=False prevents play from answering the call
        self.add_pre_answer_verb("play", {
            "urls": ["ring:us"],
            "auto_answer": False
        })

        # Configure AI
        self.add_language("English", "en-US", "rime.spore")
        self.prompt_add_section("Role", "You are a helpful assistant.")

if __name__ == "__main__":
    agent = RingbackAgent()
    agent.run()

Generated SWML:

{
  "sections": {
    "main": [\
      {"play": {"urls": ["ring:us"], "auto_answer": false}},\
      {"answer": {}},\
      {"ai": {...}}\
    ]
  }
}

Pre-Answer Safe Verbs

Only certain verbs can run before the call is answered:

VerbPre-Answer SafeNotes
playYes*Requires auto_answer: false
connectYes*Requires auto_answer: false
sleepYesWait for duration
setYesSet variables
requestYesHTTP request
switchYesVariable-based branching
condYesConditional branching
ifYesIf/then/else
evalYesEvaluate expressions
gotoYesJump to label
labelYesDefine jump target
hangupYesReject call
transferYesRoute elsewhere

*These verbs auto-answer by default. Set auto_answer: false for pre-answer use.

Available Ringback Tones

ToneDescription
ring:usUS ringback tone
ring:ukUK ringback tone
ring:itItalian ringback tone
ring:atAustrian ringback tone

Post-Answer Verbs

Post-answer verbs run after the call is connected but before the AI speaks:

Python
TypeScript
#!/usr/bin/env python3
from signalwire import AgentBase

class WelcomeAgent(AgentBase):
    """Agent that plays welcome message before AI."""

    def __init__(self):
        super().__init__(name="welcome", port=3000)

        # Play welcome announcement
        self.add_post_answer_verb("play", {
            "url": "say:Thank you for calling Acme Corporation. "
                   "Your call may be recorded for quality assurance."
        })

        # Brief pause before AI speaks
        self.add_post_answer_verb("sleep", {"time": 500})

        # Configure AI
        self.add_language("English", "en-US", "rime.spore")
        self.prompt_add_section(
            "Role",
            "You are a customer service representative. "
            "The caller has just heard the welcome message."
        )

if __name__ == "__main__":
    agent = WelcomeAgent()
    agent.run()

Generated SWML:

{
  "sections": {
    "main": [\
      {"answer": {}},\
      {"play": {"url": "say:Thank you for calling..."}},\
      {"sleep": {"time": 500}},\
      {"ai": {...}}\
    ]
  }
}

Common Post-Answer Uses

Use CaseExample
Welcome message{"url": "say:Thank you for calling..."}
Legal disclaimer{"url": "say:This call may be recorded..."}
Hold music{"url": "https://example.com/hold.mp3"}
Pause{"time": 500} (milliseconds)
RecordingUse record_call=True in constructor

Post-AI Verbs

Post-AI verbs run after the AI conversation ends:

#!/usr/bin/env python3
from signalwire import AgentBase

class SurveyAgent(AgentBase):
    """Agent that logs call outcome after conversation."""

    def __init__(self):
        super().__init__(name="survey", port=3000)

        # Configure AI
        self.add_language("English", "en-US", "rime.spore")
        self.prompt_add_section("Role", "You are a support agent.")

        # After AI ends, log the call and hang up
        self.add_post_ai_verb("request", {
            "url": "https://api.example.com/call-complete",
            "method": "POST"
        })
        self.add_post_ai_verb("hangup", {})

if __name__ == "__main__":
    agent = SurveyAgent()
    agent.run()

Common Post-AI Uses

Use CaseVerbExample
Clean disconnecthangup{}
Transfer to humantransfer{"dest": "tel:+15551234567"}
Post-call surveypromptDTMF collection
Log outcomerequestHTTP POST to API
Connect to queueenter_queue{"name": "support"}

Complete Example

Here’s an agent with all three insertion points:

Python
TypeScript
#!/usr/bin/env python3
from signalwire import AgentBase

class CallFlowAgent(AgentBase):
    """Agent demonstrating complete call flow customization."""

    def __init__(self):
        super().__init__(name="call-flow", port=3000)

        # PRE-ANSWER: Ringback tone
        self.add_pre_answer_verb("play", {
            "urls": ["ring:us"],
            "auto_answer": False
        })

        # POST-ANSWER: Welcome and disclaimer
        self.add_post_answer_verb("play", {
            "url": "say:Welcome to Acme Corporation."
        })
        self.add_post_answer_verb("play", {
            "url": "say:This call may be recorded for quality assurance."
        })
        self.add_post_answer_verb("sleep", {"time": 500})

        # Configure AI
        self.add_language("English", "en-US", "rime.spore")
        self.prompt_add_section(
            "Role",
            "You are a friendly customer service representative. "
            "The caller has just heard the welcome message."
        )
        self.set_params({
            "end_of_speech_timeout": 1000,
            "attention_timeout": 10000
        })

        # POST-AI: Clean disconnect
        self.add_post_ai_verb("hangup", {})

if __name__ == "__main__":
    agent = CallFlowAgent()
    agent.run()

Generated SWML:

{
  "sections": {
    "main": [\
      {"play": {"urls": ["ring:us"], "auto_answer": false}},\
      {"answer": {}},\
      {"play": {"url": "say:Welcome to Acme Corporation."}},\
      {"play": {"url": "say:This call may be recorded..."}},\
      {"sleep": {"time": 500}},\
      {"ai": {...}},\
      {"hangup": {}}\
    ]
  }
}

Controlling Answer Behavior

Disable Auto-Answer

Set auto_answer=False to prevent automatic answering:

class ManualAnswerAgent(AgentBase):
    def __init__(self):
        # Disable auto-answer
        super().__init__(name="manual", port=3000, auto_answer=False)

        # Pre-answer: Play ringback
        self.add_pre_answer_verb("play", {
            "urls": ["ring:us"],
            "auto_answer": False
        })

Customize Answer Verb

Use add_answer_verb() to configure the answer verb:

# Set max call duration to 1 hour
<Badge type="tip" text="Fresh" />
agent.add_answer_verb({"max_duration": 3600})

Dynamic Call Flow

Modify call flow based on caller information using on_swml_request():

class DynamicFlowAgent(AgentBase):
    def __init__(self):
        super().__init__(name="dynamic", port=3000)
        self.add_language("English", "en-US", "rime.spore")
        self.prompt_add_section("Role", "You are a receptionist.")

        # VIP numbers get special treatment
        self.vip_numbers = ["+15551234567", "+15559876543"]

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

        if caller in self.vip_numbers:
            # VIP: No ringback, immediate welcome
            self.clear_pre_answer_verbs()
            self.add_post_answer_verb("play", {
                "url": "say:Welcome back, valued customer!"
            })
        else:
            # Regular caller: Ringback tone
            self.add_pre_answer_verb("play", {
                "urls": ["ring:us"],
                "auto_answer": False
            })

Clear Methods

Remove verbs from insertion points:

agent.clear_pre_answer_verbs()   # Remove all pre-answer verbs
agent.clear_post_answer_verbs()  # Remove all post-answer verbs
agent.clear_post_ai_verbs()      # Remove all post-AI verbs

Method Chaining

All verb insertion methods return self for chaining:

agent = AgentBase(name="chained", port=3000)
agent.add_pre_answer_verb("play", {"urls": ["ring:us"], "auto_answer": False}) \
     .add_post_answer_verb("play", {"url": "say:Welcome"}) \
     .add_post_answer_verb("sleep", {"time": 500}) \
     .add_post_ai_verb("hangup", {})

Call flow insertion points for pre-answer, post-answer, and post-AI verbs.

SignalWire Developer Documentation