docs: QUICKSTART env table + 'make a real phone call' walkthrough + README status update (slice-5 T9)
QUICKSTART gains a Twilio Media Streams section: env-var table for the four RUTSTER_TWILIO_* vars, the run-with-twilio-live command, the point-Twilio- at-rutster webhook/TwiML walkthrough, + the outbound-call curl example. The /v1/trunk routes' auth-deferral (slice 6) is flagged. A 'what's different from WebRTC' note explains the architectural reuse -- the reflex stack is ingress-agnostic (Reflex<TapAudioPipe> + LocalVadReflex REUSED from slice-4). README's spearhead status is corrected + extended: slices 1-4 are merged to main (the prior status stalled at '1-3 merged, slice-4 active' -- stale); 4.5 (sim/benchmark, ADR-0010) + step 5 (PSTN via rented transport, ADR-0007) are the active build targets. ADR-0007 honored: rutster parses zero SIP bytes. T9 of slice-5. Signed-off-by: Aaron D. Lee <himself@adlee.work>
This commit is contained in:
25
README.md
25
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)
|
||||
|
||||
|
||||
@@ -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 `<Connect><Stream>` 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<TapAudioPipe>` + `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 |
|
||||
|
||||
Reference in New Issue
Block a user