***

title: Local Development
description: Run and test SignalWire SDK agents locally during development with hot reloading, tunneling, and debug tools.
slug: /guides/local-development
max-toc-depth: 3
---------------------

For a complete index of all SignalWire documentation pages, fetch https://signalwire.com/docs/llms.txt

[ref-agentserver]: /docs/server-sdks/reference/python/agents/agent-server

## Server configuration

### Custom host and port

| Language   | Custom Host and Port                         |
| ---------- | -------------------------------------------- |
| Python     | `agent.run(host="0.0.0.0", port=8080)`       |
| TypeScript | `agent.run({ host: "0.0.0.0", port: 8080 })` |

### Using serve() directly (Python)

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

```python
# Development server
agent.serve(host="127.0.0.1", port=3000)

# Listen on all interfaces
agent.serve(host="0.0.0.0", port=3000)
```

## Development endpoints

| Endpoint        | Method   | Purpose                                                         |
| --------------- | -------- | --------------------------------------------------------------- |
| `/`             | GET/POST | SWML document                                                   |
| `/swaig`        | POST     | SWAIG function calls                                            |
| `/post_prompt`  | POST     | Post-prompt handling                                            |
| `/debug`        | GET/POST | Debug information                                               |
| `/debug_events` | POST     | Real-time debug events (when `enable_debug_events()` is active) |
| `/health`       | GET      | Health check ([AgentServer][ref-agentserver] only)              |

## Testing your agent

### View SWML output

```bash
# 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

```bash
# 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

```bash
# 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

```bash
# Install
npm install -g localtunnel

# Start tunnel
lt --port 3000
```

## Environment variables for development

These environment variables work across all SDK languages:

| Variable                   | Purpose                   | Default        |
| -------------------------- | ------------------------- | -------------- |
| `SWML_BASIC_AUTH_PASSWORD` | Auth password             | Auto-generated |
| `SWML_BASIC_AUTH_USER`     | Auth username             | `signalwire`   |
| `SWML_PROXY_URL_BASE`      | Public URL (behind proxy) | Auto-detected  |
| `SWML_ALLOW_PRIVATE_URLS`  | Allow private IP requests | `false`        |

```bash
# 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:

```python
import os

# Option 1: Environment variable
os.environ['SWML_PROXY_URL_BASE'] = 'https://abc123.ngrok.io'

# Option 2: Auto-detection from X-Forwarded headers
# 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:

```python
import logging
logging.basicConfig(level=logging.DEBUG)

agent = MyAgent()
agent.run()
```

Or via environment variable:

```bash
export SIGNALWIRE_LOG_MODE=default
python my_agent.py
```

## Hot reloading

For automatic reloading during development, use uvicorn directly:

```bash
# 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:

```python
# dev.py
from my_agent import MyAgent

agent = MyAgent()
app = agent._app  # Expose the ASGI app for uvicorn
```

Then run:

```bash
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:

```python
from signalwire import AgentServer
from pathlib import Path

# Create your agents
from my_agents import SupportAgent, SalesAgent

HOST = "0.0.0.0"
PORT = 3000

server = AgentServer(host=HOST, port=PORT)
server.register(SupportAgent(), "/support")
server.register(SalesAgent(), "/sales")

# Serve static files from web directory
web_dir = Path(__file__).parent / "web"
if web_dir.exists():
    server.serve_static_files(str(web_dir))

server.run()
```

**Directory Structure:**

```text
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:**

| Route      | Handler                  |
| ---------- | ------------------------ |
| `/support` | SupportAgent             |
| `/sales`   | SalesAgent               |
| `/health`  | AgentServer health check |
| `/*`       | Static files (fallback)  |

## Common development issues

| Issue                  | Solution                                     |
| ---------------------- | -------------------------------------------- |
| Port already in use    | Use different port: `agent.run(port=8080)`   |
| 401 Unauthorized       | Check `SWML_BASIC_AUTH_*` env vars           |
| Functions not found    | Verify function registration                 |
| SWML URL wrong         | Set `SWML_PROXY_URL_BASE` for ngrok          |
| Connection refused     | Ensure agent is running on correct port      |
| Static files not found | Check `web_dir.exists()` and path is correct |