diff --git a/README.md b/README.md index 3b5fa38..805ffa1 100644 --- a/README.md +++ b/README.md @@ -51,12 +51,14 @@ pip install -r examples/openai_realtime_brain/requirements.txt OPENAI_API_KEY=sk-... python examples/openai_realtime_brain/openai_realtime_brain.py ``` -> **Status:** Slices 1–3 (WebRTC media core, WS tap, OpenAI Realtime brain) are merged to -> `main`. Slice 4 (barge-in / VAD-driven playout kill) is the active build target, in flight -> on the `slice-4-dev-a-reflex` + `slice-4-dev-b-tap` branches. Design: -> [`docs/superpowers/specs/2026-07-01-slice-4-barge-in-design.md`](docs/superpowers/specs/2026-07-01-slice-4-barge-in-design.md). -> Implementation plan: -> [`docs/superpowers/plans/2026-07-01-slice-4-barge-in.md`](docs/superpowers/plans/2026-07-01-slice-4-barge-in.md). +> **Status:** Slices 1–4 (WebRTC media core, WS tap, OpenAI Realtime brain, +> barge-in / VAD-driven playout kill) are merged to `main`. Slice 4½ +> (sim/benchmark harness, [ADR-0010](docs/adr/0010-spearhead-benchmark-sim-harness.md)) +> + step 5 (PSTN via rented Twilio Media Streams transport, +> [ADR-0007](docs/adr/0007-trunk-rented-transport.md)) are the active build targets, +> in flight on their respective branches. ADR-0007 honored: rutster parses zero +> SIP bytes. See the +> [slice-5 design](docs/superpowers/specs/2026-07-05-slice-5-rented-transport-design.md). ## Documentation @@ -162,12 +164,15 @@ exactly as integrators did on top of Asterisk. ## Status -Slices 1–3 (WebRTC media core, WS tap, OpenAI Realtime brain) are merged to `main`; -spearhead steps 4–6 remain. The +Slices 1–4 (WebRTC media core, WS tap, OpenAI Realtime brain, barge-in / +VAD-driven playout kill) are merged to `main`; spearhead steps 4½ (sim/benchmark +harness, [ADR-0010](docs/adr/0010-spearhead-benchmark-sim-harness.md)) + 5 (PSTN +via rented transport, [ADR-0007](docs/adr/0007-trunk-rented-transport.md)) + 6 +(spend cap) remain. The [vision revision](docs/superpowers/specs/2026-06-26-vision-revision-design.md) and ADRs define the architecture; the -[slice-4 design](docs/superpowers/specs/2026-07-01-slice-4-barge-in-design.md) -documents the active build. +[slice-5 design](docs/superpowers/specs/2026-07-05-slice-5-rented-transport-design.md) +documents the active build. ADR-0007 honored: rutster parses zero SIP bytes. ## First proof (the spearhead) diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index eb95644..282075d 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -2,11 +2,14 @@ Get Rutster running and hear your own voice echoed back in under 5 minutes. -> **Status:** Slices 1–3 are merged to `main`. Slice 4 (barge-in / VAD-driven playout -> kill) is the active build target, in flight on the `slice-4-dev-a-reflex` + -> `slice-4-dev-b-tap` branches. This quickstart exercises the slice-1 WebRTC media -> loopback, which remains the simplest end-to-end demo on `main`. See -> [`docs/superpowers/specs/2026-07-01-slice-4-barge-in-design.md`](superpowers/specs/2026-07-01-slice-4-barge-in-design.md) +> **Status:** Slices 1–4 (WebRTC media core, WS tap, OpenAI Realtime brain, +> barge-in / VAD-driven playout kill) are merged to `main`. Slice 4½ +> (sim/benchmark harness) + step 5 (PSTN via rented Twilio Media Streams +> transport) are the active build targets, in flight. This quickstart's first +> section exercises the slice-1 WebRTC media loopback (the simplest end-to-end +> demo on `main`); the optional "Make a real phone call" section below covers +> step-5 PSTN ingress. See +> [`docs/superpowers/specs/2026-07-05-slice-5-rented-transport-design.md`](superpowers/specs/2026-07-05-slice-5-rented-transport-design.md) > for the active build target's design. --- @@ -75,6 +78,86 @@ RUST_LOG=rutster=debug cargo run --- +## Make a real phone call (PSTN via Twilio Media Streams, optional) + +The WebRTC loopback above proves the media core + the reflex loop. Spearhead +step 5 takes it to a **real phone number**: a PSTN caller dials your Twilio +number, Twilio forks the call's audio over a WebSocket to rutster, and the +same reflex loop (barge-in, brain tap) runs against the PSTN leg — **no +first-party SIP stack** ([ADR-0007](adr/0007-trunk-rented-transport.md)). + +### Prerequisites + +- A [Twilio account](https://www.twilio.com/) (trial works). +- A Twilio phone number capable of Voice + Media Streams. +- A publicly-reachable HTTPS URL for rutster (Twilio must call back to you). + For local dev, use [ngrok](https://ngrok.com/) to tunnel `localhost:8080` + to a public URL: `ngrok http 8080`. + +### Twilio credentials + +Set these environment variables to enable PSTN ingress: + +| Env var | Purpose | Example | +|---|---|---| +| `RUTSTER_TWILIO_ACCOUNT_SID` | Twilio account ID | `ACxxx...` | +| `RUTSTER_TWILIO_AUTH_TOKEN` | Twilio auth token (secret; never logged) | `xxx...` | +| `RUTSTER_TWILIO_MEDIA_BIND` | Where rutster's Media Streams WSS server binds | `0.0.0.0:8081` | +| `RUTSTER_TWILIO_WEBHOOK_BASE` | Your public URL Twilio calls back to | `https://your.ngrok.io` | + +Without these set, the binary runs WebRTC-only (slices 1–4 ingress + barge-in). +With them set, the binary accepts PSTN fork calls against `/twilio/media-stream`. + +### Run with Twilio enabled + +The live Twilio REST client is feature-gated behind `twilio-live` — the routine +CI gate uses the in-process mock; you only need the live client for a real call: + +```bash +export RUTSTER_TWILIO_ACCOUNT_SID=AC... +export RUTSTER_TWILIO_AUTH_TOKEN=... +export RUTSTER_TWILIO_MEDIA_BIND=0.0.0.0:8081 +export RUTSTER_TWILIO_WEBHOOK_BASE=https://your.ngrok.io +cargo run --features=twilio-live +``` + +### Point Twilio at rutster + +1. In the Twilio console, configure your phone number's **A CALL COMES IN** + webhook to `POST` to `https://your.ngrok.io/v1/trunk/webhook`. +2. Twilio answers an inbound call, hits your webhook; rutster responds with + TwiML instructing Twilio to `` against + `wss://your.ngrok.io/twilio/media-stream`. +3. Twilio opens the WSS; rutster's `TwilioMediaStreamsServer` receives the + call's audio (µ-law @ 8 kHz), decodes via `G711Codec` to 24 kHz PCM, and + feeds the same reflex loop + brain tap as a WebRTC leg. + +### Make an outbound call + +```bash +curl -X POST http://localhost:8080/v1/trunk/sessions \ + -H 'Content-Type: application/json' \ + -d '{"to":"+15551234567","from":"+15550000000"}' +``` + +The handler calls `TwilioCallControlClient::originate`; Twilio places the call +and forks the audio back — the PSTN caller reaches the reflex loop. + +> **Auth note:** the `/v1/trunk/*` routes are **not authenticated** in slice 5 +> (authn defers to slice 6, the spend cap). Do not expose them to the public +> internet without a reverse-proxy auth layer. + +### What's different from WebRTC + +Nothing, architecturally. The PSTN leg enters the same `MediaThread` tick loop +via a `MediaLeg::Trunk(TrunkSession)` variant (the WebRTC leg is `MediaLeg::WebRTC`). +The reflex stack (`Reflex` + `LocalVadReflex`) is **reused +verbatim** from slice 4 — barge-in fires on PSTN caller speech the same way it +fires on WebRTC caller speech. The only difference is the ingress: str0m's RTP +decode (WebRTC) vs the Media Streams WSS pump + G.711 codec (trunk). + +--- + ## Troubleshooting | Symptom | Likely cause / fix |