Request Lifecycle

View as MarkdownOpen in Claude

The Complete Call Flow

Understanding the request lifecycle helps you debug issues and optimize your agents. Here’s the complete flow:

Complete call lifecycle.
Complete Call Lifecycle

Phase 1: Call Setup

When a call arrives at SignalWire:

Call setup phase.
Call Setup

Key points:

  • SignalWire knows which agent to contact based on phone number configuration
  • The request includes Basic Auth credentials
  • POST is the default; GET requests are also supported for SWML retrieval

Phase 2: SWML Generation

Your agent builds and returns the SWML document:

1## Inside AgentBase._render_swml()
2
3def _render_swml(self, request_body=None):
4 """Generate SWML document for this agent."""
5
6 # 1. Build the prompt (POM or text)
7 prompt = self._build_prompt()
8
9 # 2. Collect all SWAIG functions
10 functions = self._tool_registry.get_functions()
11
12 # 3. Generate webhook URLs with security tokens
13 webhook_url = self._build_webhook_url("/swaig")
14
15 # 4. Assemble AI configuration
16 ai_config = {
17 "prompt": prompt,
18 "post_prompt": self._post_prompt,
19 "post_prompt_url": self._build_webhook_url("/post_prompt"),
20 "SWAIG": {
21 "defaults": {"web_hook_url": webhook_url},
22 "functions": functions
23 },
24 "hints": self._hints,
25 "languages": self._languages,
26 "params": self._params
27 }
28
29 # 5. Build complete SWML document
30 swml = {
31 "version": "1.0.0",
32 "sections": {
33 "main": [
34 {"answer": {}},
35 {"ai": ai_config}
36 ]
37 }
38 }
39
40 return swml

Phase 3: AI Conversation

Once SignalWire has the SWML, it executes the instructions:

AI conversation loop.
AI Conversation Loop

AI Parameters that control this loop:

ParameterDefaultPurpose
end_of_speech_timeout500msWait time after user stops speaking
attention_timeout15000msMax silence before AI prompts
inactivity_timeout30000msMax silence before ending call
barge_match_string-Words that immediately interrupt AI

Phase 4: Function Calls

When the AI needs to call a function:

SWAIG function call phase.
SWAIG Function Call

Phase 5: Call End

When the call ends, the post-prompt summary is sent:

Call ending phase.
Call Ending

Handling Post-Prompt

Configure post-prompt handling in your agent:

LanguageSet Post-Prompt
Pythonagent.set_post_prompt("Summarize this call...")
TypeScriptagent.setPostPrompt('Summarize this call...')
1from signalwire import AgentBase
2
3class MyAgent(AgentBase):
4 def __init__(self):
5 super().__init__(name="my-agent")
6
7 self.set_post_prompt(
8 "Summarize this call including: "
9 "1) The caller's main question or issue "
10 "2) How it was resolved "
11 "3) Any follow-up actions needed"
12 )
13
14 def on_post_prompt(self, data):
15 """Handle the call summary."""
16 summary = data.get("post_prompt_data", {})
17 call_id = data.get("call_id")
18 self.log_call_summary(call_id, summary)

Request/Response Headers

SWML Request (GET or POST /)

1GET / HTTP/1.1
2Host: your-agent.com
3Authorization: Basic c2lnbmFsd2lyZTpwYXNzd29yZA==
4Accept: application/json
5X-Forwarded-For: signalwire-ip
6X-Forwarded-Proto: https

SWML Response

1HTTP/1.1 200 OK
2Content-Type: application/json
3
4{"version": "1.0.0", "sections": {...}}

SWAIG Request (POST /swaig)

1POST /swaig HTTP/1.1
2Host: your-agent.com
3Authorization: Basic c2lnbmFsd2lyZTpwYXNzd29yZA==
4Content-Type: application/json
5
6{"action": "swaig_action", "function": "...", ...}

SWAIG Response

1HTTP/1.1 200 OK
2Content-Type: application/json
3
4{"response": "...", "action": [...]}

Debugging the Lifecycle

View SWML Output

$## See what your agent returns
$curl -u signalwire:password http://localhost:3000/ | jq '.'
$
$## Using swaig-test
$swaig-test my_agent.py --dump-swml

Test Function Calls

$## Call a function directly
$swaig-test my_agent.py --exec get_balance --account_id 12345
$
$## With verbose output
$swaig-test my_agent.py --exec get_balance --account_id 12345 --verbose

Monitor Live Traffic

1from signalwire import AgentBase
2
3class DebugAgent(AgentBase):
4 def __init__(self):
5 super().__init__(name="debug-agent")
6
7 def on_swml_request(self, request_data=None, callback_path=None, request=None):
8 """Called when SWML is requested."""
9 if request:
10 print(f"SWML requested from: {request.client.host}")
11 print(f"Headers: {dict(request.headers)}")
12
13 def on_swaig_request(self, function_name, args, raw_data):
14 """Called before each SWAIG function."""
15 print(f"Function called: {function_name}")
16 print(f"Arguments: {args}")
17 print(f"Call ID: {raw_data.get('call_id')}")

Error Handling

SWML Errors

If your agent can’t generate SWML:

1def _render_swml(self):
2 try:
3 return self._build_swml()
4 except Exception as e:
5 # Return minimal valid SWML
6 return {
7 "version": "1.0.0",
8 "sections": {
9 "main": [
10 {"answer": {}},
11 {"play": {"url": "https://example.com/error.mp3"}},
12 {"hangup": {}}
13 ]
14 }
15 }

SWAIG Errors

If a function fails:

1def get_balance(self, args, raw_data):
2 try:
3 balance = self.lookup_balance(args.get("account_id"))
4 return FunctionResult(f"Your balance is ${balance}")
5 except DatabaseError:
6 return FunctionResult(
7 "I'm having trouble accessing account information right now. "
8 "Please try again in a moment."
9 )
10 except Exception as e:
11 # Log the error but return user-friendly message
12 self.logger.error(f"Function error: {e}")
13 return FunctionResult(
14 "I encountered an unexpected error. "
15 "Let me transfer you to a representative."
16 )

Next Steps

Now that you understand the complete lifecycle, let’s look at how security works throughout this flow.