Local Development

View as MarkdownOpen in Claude

Server configuration

Custom host and port

LanguageCustom Host and Port
Pythonagent.run(host="0.0.0.0", port=8080)
TypeScriptagent.run({ host: "0.0.0.0", port: 8080 })

Using serve() directly (Python)

For more control, use serve() instead of run():

1# Development server
2agent.serve(host="127.0.0.1", port=3000)
3
4# Listen on all interfaces
5agent.serve(host="0.0.0.0", port=3000)

Development endpoints

EndpointMethodPurpose
/GET/POSTSWML document
/swaigPOSTSWAIG function calls
/post_promptPOSTPost-prompt handling
/debugGET/POSTDebug information
/debug_eventsPOSTReal-time debug events (when enable_debug_events() is active)
/healthGETHealth check (AgentServer only)

Testing your agent

View SWML output

$# Get the SWML document (use credentials from agent startup output or env vars)
$curl -u "$SWML_BASIC_AUTH_USER:$SWML_BASIC_AUTH_PASSWORD" http://localhost:3000/
$
$# Pretty print with jq
$curl -u "$SWML_BASIC_AUTH_USER:$SWML_BASIC_AUTH_PASSWORD" http://localhost:3000/ | jq .

Using swaig-test CLI

$# List available functions
$swaig-test my_agent.py --list-tools
$
$# Test a specific function
$swaig-test my_agent.py --exec get_weather --city "Seattle"
$
$# Dump SWML output
$swaig-test my_agent.py --dump-swml

Exposing local server

SignalWire needs to reach your agent via a public URL. Use ngrok or similar:

Connection Flow: SignalWire Cloud —> ngrok tunnel —> localhost:3000

Steps:

  1. Start your agent (see run command table above)
  2. Start ngrok: ngrok http 3000
  3. Use ngrok URL in SignalWire: https://abc123.ngrok.io

Using ngrok

$# Start your agent (example with Python; substitute your language's run command)
>python my_agent.py
>
># In another terminal, start ngrok
>ngrok http 3000

ngrok provides a public URL like https://abc123.ngrok.io that forwards to your local server.

Using localtunnel

$# Install
$npm install -g localtunnel
$
$# Start tunnel
$lt --port 3000

Environment variables for development

These environment variables work across all SDK languages:

VariablePurposeDefault
SWML_BASIC_AUTH_PASSWORDAuth passwordAuto-generated
SWML_BASIC_AUTH_USERAuth usernamesignalwire
SWML_PROXY_URL_BASEPublic URL (behind proxy)Auto-detected
SWML_ALLOW_PRIVATE_URLSAllow private IP requestsfalse
$# Authentication (username defaults to 'signalwire' when not set)
$# Only the password is required; omit both to disable auth for local testing
$export SWML_BASIC_AUTH_PASSWORD="test123"
$
$# Or disable authentication entirely for local testing
$export SWML_BASIC_AUTH_USER=""
$export SWML_BASIC_AUTH_PASSWORD=""
$
$# Override proxy URL if behind ngrok
$export SWML_PROXY_URL_BASE="https://abc123.ngrok.io"
$# APP_URL is also accepted as a fallback (useful for Dokku deployments)
$
$# Allow DataMap/skill requests to private IPs (e.g., local backend services)
$export SWML_ALLOW_PRIVATE_URLS=true

Proxy URL configuration

When behind ngrok or another proxy, the SDK needs to know the public URL:

1import os
2
3# Option 1: Environment variable
4os.environ['SWML_PROXY_URL_BASE'] = 'https://abc123.ngrok.io'
5
6# Option 2: Auto-detection from X-Forwarded headers
7# The SDK automatically detects proxy from request headers

Development workflow

1. Code

Write/modify your agent code.

2. Test Locally

  • swaig-test my_agent.py --dump-swml
  • swaig-test my_agent.py --exec function_name --param value

3. Run Server

python my_agent.py (or your language’s equivalent run command)

4. Expose Publicly

ngrok http 3000

5. Test with SignalWire

Point phone number to ngrok URL and make test call.

Debug mode

Enable debug logging:

1import logging
2logging.basicConfig(level=logging.DEBUG)
3
4agent = MyAgent()
5agent.run()

Or via environment variable:

$export SIGNALWIRE_LOG_MODE=default
$python my_agent.py

Hot reloading

For automatic reloading during development, use uvicorn directly:

$# Install uvicorn with reload support
$pip install "uvicorn[standard]"
$
$# Run with auto-reload
$uvicorn my_agent:agent._app --reload --host 0.0.0.0 --port 3000

Or create a development script:

1# dev.py
2from my_agent import MyAgent
3
4agent = MyAgent()
5app = agent._app # Expose the ASGI app for uvicorn

Then run:

$uvicorn dev:app --reload --port 3000

Serving static files

Use AgentServer.serve_static_files() to serve static files alongside your agents. This is useful for web dashboards, documentation, or any static content:

1from signalwire import AgentServer
2from pathlib import Path
3
4# Create your agents
5from my_agents import SupportAgent, SalesAgent
6
7HOST = "0.0.0.0"
8PORT = 3000
9
10server = AgentServer(host=HOST, port=PORT)
11server.register(SupportAgent(), "/support")
12server.register(SalesAgent(), "/sales")
13
14# Serve static files from web directory
15web_dir = Path(__file__).parent / "web"
16if web_dir.exists():
17 server.serve_static_files(str(web_dir))
18
19server.run()

Directory Structure:

my_project/
├── server.py
├── my_agents.py
└── web/
├── index.html
├── styles.css
└── app.js

Key Points:

  • Use server.serve_static_files(directory) to serve static files
  • Agent routes always take priority over static files
  • Requests to / serve index.html from the static directory
  • Both /support and /support/ work correctly with agents

Route Priority:

RouteHandler
/supportSupportAgent
/salesSalesAgent
/healthAgentServer health check
/*Static files (fallback)

Common development issues

IssueSolution
Port already in useUse different port: agent.run(port=8080)
401 UnauthorizedCheck SWML_BASIC_AUTH_* env vars
Functions not foundVerify function registration
SWML URL wrongSet SWML_PROXY_URL_BASE for ngrok
Connection refusedEnsure agent is running on correct port
Static files not foundCheck web_dir.exists() and path is correct