# Converse + Twilio inbound bridge

This reference app connects an inbound Twilio phone call to one Converse session. It keeps Twilio
as the telephony provider: your service owns the phone number, webhook, deployment and application
tools; Converse owns the voice experience.

## Run it

1. Copy `env.example` to `.env` and fill in the three required values.
2. Install and run with uv:

   ```sh
   uv sync
   uv run uvicorn bridge:app --env-file .env --host 0.0.0.0 --port 8000
   ```

3. Put the app behind a public HTTPS endpoint. `PUBLIC_BASE_URL` must be that exact external origin,
   for example `https://voice.example.com`; Twilio signatures are checked against it.
4. Set the Twilio phone number's incoming Voice webhook to `POST https://voice.example.com/voice`.

The webhook returns `<Connect><Stream>`, so this is a bidirectional stream. The bridge accepts only
Twilio-signed HTTP and WebSocket requests. It converts Twilio's 8 kHz G.711 mu-law audio to the
Converse 16 kHz wire format and maps Twilio `mark`/`clear` events to Converse's
`playback_stopped` contract on interruption.

## Add application tools

Edit `tool_manifest()` and `execute_tool()` in `bridge.py`. Tool calls run concurrently with the
audio/event loop, so a slow application lookup does not starve Converse audio. Keep all service
credentials and effects in this bridge—only schemas, arguments and bounded results cross the
Converse session.

This is an inbound reference integration, not a dialer. Converse's acceptable-use policy prohibits
outbound robocalling.


## Maintainer browser smoke test

Production also runs `browser_harness.py` behind the existing team sign-in at
`/internal/twilio-smoke/`. Only the signed `/twilio-smoke/voice` and
`/twilio-smoke/media` callbacks are exposed to Twilio. The UI issues ten-minute
outgoing-only Voice SDK tokens and rate-limits starts per signed-in team account. With no
`CONVERSE_VOICE` override, the broker uses its global Classic default and records it as default
rather than as a user choice.

The production service reads only `/etc/converse/twilio-smoke.env`; use
`PUBLIC_BASE_URL=https://converse.trelis.com/twilio-smoke` and configure the TwiML
App Voice URL as `https://converse.trelis.com/twilio-smoke/voice` with HTTP POST.
