Aaron D. Lee 5ce18bf472 slice-5: readyz timeout covers send; fix test thread leak
Task 5 review fixes: the 250ms readiness bound now wraps the whole
Stats round trip (a wedged thread can stall the send, not just the
reply), and the readyz integration test tears down its MediaThread
like the sibling test does.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012QndwfhjyTiZcUYp87dwW8
Signed-off-by: Aaron D. Lee <himself@adlee.work>
2026-07-04 20:35:11 -04:00

Rutster

The open-source engine for building the AI-era contact center — self-hostable, AI-native, memory-safe Rust. A spiritual successor to Asterisk's place in the world, not its protocols or its architecture.

Not a port of Asterisk. rutster inherits the role Asterisk held — the self-hostable engine a technical builder uses to stand up a contact center — and re-aims it at a category AI is actively disrupting, instead of a PBX category UCaaS already ate.

Quickstart

# Prereqs: Rust (rustup), libopus dev headers (libopus-dev / opus-devel / brew install opus)
cargo run
# listening on http://0.0.0.0:8080

Open http://localhost:8080/ → click "Start call" → grant mic → hear yourself echo. Full walkthrough + troubleshooting: docs/QUICKSTART.md.

Slice 3 dev loop — OpenAI Realtime brain

The dev loop without real OpenAI credentials (no API key required):

cargo run -p rutster-brain-realtime --features=mock   # brain on :8082
cargo run                                            # core on :8080

Open http://localhost:8080/ → click "Start call" → speak → hear the mock-brain reply within ~250 ms (the mock echoes audio back, no real OpenAI RTT; this exercises the full brain→core audio round-trip + the new function_call dispatch path).

With real OpenAI Realtime:

export OPENAI_API_KEY=sk-...    # or OPENAI_API_KEY_FILE=/var/secrets/openai
cargo run -p rutster-brain-realtime
cargo run

Speak → end-to-end speech-to-speech with OpenAI Realtime within ~700 ms (slice-1's 200 ms + tap round-trip + OpenAI latency + 100 ms playout buffer).

For the foreign-language brain demo (Python, not in CI):

pip install -r examples/openai_realtime_brain/requirements.txt
OPENAI_API_KEY=sk-... python examples/openai_realtime_brain/openai_realtime_brain.py

Status: Slices 13 (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. Implementation plan: docs/superpowers/plans/2026-07-01-slice-4-barge-in.md.

Documentation

Doc For when you want to…
docs/QUICKSTART.md Run it in 5 minutes
docs/DEVELOPMENT.md Iterate on the codebase (workspace layout, per-crate testing, dev loop)
docs/ARCHITECTURE.md Understand the fused per-call vertical + composable platform + agent tap
docs/PORT_PLAN.md See every Asterisk subsystem mapped to a disposition (capability checklist, not template)
docs/adr/ Load-bearing architecture decisions
AGENTS.md Project orientation for any agent (human/AI/hybrid) working in the repo
CONTRIBUTING.md Trunk-based dev workflow, CI gates, commit style, review checklist
LEARNING.md Index of "to learn concept X, read file Y" (learner-facing codebase)

Why it exists

Asterisk won because contact centers were built on it (Vicidial, GOautodial, a thousand integrator builds) — it never tried to be Five9. rutster inherits that position: it is a framework / engine, not a turnkey product.

The white space no incumbent fills:

Competitor What they are rutster's edge
LiveKit Horizontal real-time media infra (Go) rutster owns the contact-center domain (ACD, IVR, queues, recording, CDR, dialer, supervisor) LiveKit will never ship
Cloud CCaaS (Five9, Genesys, NICE, Amazon Connect, Twilio Flex) Proprietary, AI bolted on Self-hostable, AI-native, no per-seat/minute lock-in
Cloud AI-voice (Vapi, Retell, Bland) Cheap managed voice bots You own your calls and training data; it's a contact center (escalation, queues), not a single bot
The brain vendor direct (OpenAI Realtime SIP, Gemini Live) Point a SIP trunk straight at the model API Everything around the call: queues/ACD, human takeover, recording/CDR you own, containment analytics, multi-vendor brains — and the phone still answers when the vendor is down
Dated OSS (Vicidial, FreePBX) Self-hostable, Asterisk-era Modern, AI-native, memory-safe Rust

The wedge is a coherent combination, not a silver bullet:

  1. No-GC real-time determinism — tight turn-taking / barge-in / jitter in a no-GC loop.
  2. One secure auditable boundary — media + local reflexes + spend/abuse control + the tap + audit in a single memory-safe trust domain. One thing to certify (strongest for PCI / HIPAA / TCPA). The carrier trunk is rented (or out-of-tree); bringing PSTN media inside the boundary is the on-prem graduation (ADR-0007), not a day-one claim.
  3. Operational simplicity — one binary, one bill, one deploy.

Honest caveat: the agent brain (STT/LLM/TTS) is necessarily external — audio leaves the box to reach it, same as LiveKit. The real-time edge therefore lives in the local reflexes that don't need the brain (VAD killing TTS the instant the caller speaks, barge-in, jitter, pacing, DTMF), not the brain round-trip. The moat is the whole.

Who it's for

The modern equivalent of the 2006 Linux-nerd-who-stood-up-Asterisk-for-an-SMB: the CLI/IDE/AI-comfortable self-hosting technical builder — runs Claude Code in a terminal, lives in an editor with an AI pair, versions everything in git, self-hosts on principle. Not the no-code admin clicking a flow-designer canvas. The builder serves the non-technical operator downstream, exactly as integrators did on top of Asterisk.

What it is / isn't

  • Is: a Rust media core owning the per-call vertical (RTP/SRTP media termination + local real-time reflexes + a clean audio tap to an external brain + in-boundary spend/abuse control; the carrier trunk is rented transport, not first-party — ADR-0007); a programmable call model exposed as a REST/gRPC API + event stream; WebRTC-first human ingress; a library of contact-center capabilities (ACD, IVR, queues, recording, CDR, dialer, supervisor) delivered as services around the core.
  • Isn't: a TDM/PSTN-hardware PBX — and not a SIP stack. No DAHDI, no Sangoma/Digium cards, no ISDN/SS7, no IAX2/H.323/SCCP/MGCP/Unistim — and no first-party SIP (ADR-0007). PSTN reach is rented transport (a CPaaS raw-media fork, or an out-of-tree SBC for on-prem media). Inbound SIP endpoint registration (desk phones) likewise stays out-of-tree — not the browser/SSO UX rutster targets.

Core design pillars

  1. Memory-safe by construction — Rust everywhere on the hot path; fuzzed sans-IO protocol parsers. rutster parses no SIP at all (ADR-0007): its entire first-party wire surface is WebRTC/RTP/SRTP + the WebSocket tap/ingress protocol — all memory-safe Rust. The carrier-SIP interop tail lives outside the trust boundary (rented transport or an out-of-tree SBC), so the buffer-overflow/RCE CVE class is designed out of rutster's own surface.
  2. Security-as-product — the single auditable boundary is the moat. TLS/SRTP mandatory, deny-by-default routing, built-in toll-fraud controls, mTLS gRPC admin (no plaintext AMI), hard multi-tenancy. Compliance is a buying criterion, not a row.
  3. In-boundary spend / abuse control — spend caps and abuse/pacing control live inside the trust boundary, co-located with the tap and the provider call-control client. The brain never holds provider credentials, and the gate is unskippable for everything rutster mediates — which is everything the brain can do (ADR-0009; pair with provider-side caps for defense in depth).
  4. Data ownership — calls and training data never leave the operator's infra. The self-host wedge and the fuel for the ML self-improvement loop.
  5. Degradation, deterministic, observable — no-GC real-time loop; OpenTelemetry traces that follow a single call across the boundary; config-as-data, not .conf files edited on a box.
  6. Operational simplicity — one binary, one bus, one deploy (compose up).

Layout

  • docs/ARCHITECTURE.md — the fused per-call vertical + composable horizontal platform; the agent tap as the central interface.
  • docs/PORT_PLAN.md — the capability checklist (what a telephony system must handle), not an architecture template. Every Asterisk subsystem mapped to a disposition with rationale.
  • docs/adr/ — decisions. Highlights:
    • 0002 — north star + fused vertical
    • 0007 — rent the trunk transport; no first-party SIP stack (supersedes 0003)
    • 0004 — GPL-3.0-or-later
    • 0005 — Valkey (bus + state store)
    • 0006 — WebRTC-first ingress; SIP endpoint deferred
    • 0009 — spend-gate guarantees re-scoped post-0007 (amends 0002)
    • 0010 — spearhead step 4½: benchmark + simulation harness; escalation pulled forward

Status

Slices 13 (WebRTC media core, WS tap, OpenAI Realtime brain) are merged to main; spearhead steps 46 remain. The vision revision and ADRs define the architecture; the slice-4 design documents the active build.

First proof (the spearhead)

The full thin slice, sequenced so each step is its own proof — never a big bang:

  1. WebRTC media loopback (terminate RTP/SRTP, echo audio to a browser) — proves the media core
  2. Add the tap (route audio to an external echo process and back) — proves the tap interface
  3. Swap echo for the brain (ideally a single speech-to-speech API, e.g. OpenAI Realtime, to collapse STT+LLM+TTS into one integration) — proves agent integration
  4. Add barge-in (VAD-driven playout kill) — proves the reflex
    • 4½ — Benchmark + simulation harness (ADR-0010): synthetic callers drive p50/p99 mouth-to-ear latency + barge-in kill-time under concurrent load, in CI — proves the reflex measurably; seeds rutster-sim
  5. Add a real phone number via rented transport (a CPaaS raw-media fork, e.g. Twilio Media Streams) — proves a PSTN call reaches the reflex loop, no first-party SIP (ADR-0007)
  6. Add the spend cap (hard-stop at threshold) — proves the boundary

Steps 14 are the reflex loop — the hard, most-differentiating part proves itself before trunk integration piles on. "I called my Rust box and an AI answered the phone" is the momentum fuel a solo multi-year build needs. Post-review re-sequencing (ADR-0010): after step 4½, rung-2 escalation (human takeover) is pulled forward ahead of steps 56 — it is the white space no competitor ships (see the 2026-07-03 reviews).

Capability ladder (the grand vision, incrementally)

Rung Capability Reuses
1 Self-serve — AI answers, contains the call the thin-slice first proof
2 Escalation — human agent barges in / takes over when AI breaks down the audiohook/barge primitive
3 Measurement — containment rate, where/why AI failed CDR + analytics on calls you own
4 Self-improvement — every takeover → auto-labeled training data → loop rungs 13 compounding

License

GPL-3.0-or-later (ADR-0004). Strong copyleft in the Asterisk lineage, modernized one notch. The license is the floor, not the moat — the wedge is.

Description
Terribly-named "spiritual" ancestor to the niche Asterisk PBX filled in 2005, but for AI-first call centers in 2026
Readme 4.8 MiB
Languages
Rust 95.1%
Python 2.6%
Dockerfile 1.3%
Shell 0.6%
HTML 0.4%