Files
rutster/docs/reviews/2026-07-03-adversarial-review.md
Aaron D. Lee 4c36a4ded3 docs(reviews): 2026-07-03 adversarial review + market feature scan
Direction/progress pressure-test (D1-D6, P1-P6, R1-R3) and the
boost.ai/Parloa/Vapi cohort scan (F1-F7, adjacencies A1-A11).
Standing backlog for planning sessions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018vNe64BBDkgo5oQVkBa3XF
2026-07-04 00:00:57 -04:00

349 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Adversarial Review — Direction & Progress
- **Date:** 2026-07-03
- **Scope:** Project direction (ADRs 00010008, vision revision, README/ARCHITECTURE/PORT_PLAN)
and progress to date (slices 13, all six crates, tests, CI) — reviewed adversarially, at the
request of the maintainer.
- **Method:** Full read of the ratified docs; source-level review of the shipped code; market
claims pressure-tested against the July-2026 landscape (web-verified, sources at the end).
- **Repo state reviewed:** `main` @ `c30a452` (Slice 3 — OpenAI Realtime brain, merged 2026-07-01).
Initial commit 2026-06-26 — the project is **one week old** at review time.
- **Status of findings:** Advisory. Nothing here is ratified; each finding names the doc or code
it attacks so it can be accepted, rebutted, or amended per the ADR process.
---
## Verdict
The direction has survived its own internal pressure-tests well. ADR-0007 (rent the trunk) was
the right kill, ADR-0008 (FOB/green-zone) is a genuinely useful mechanical rule, and the doc
discipline is why a review this specific is even possible one week in.
But three things have shifted under the docs:
1. **The knife that killed the SIP moat now hangs over the reflex loop** — the plank everything
currently rests on — and the docs still state it as durable white space.
2. **Two external events** (LiveKit's up-stack march; OpenAI Realtime SIP going GA) have eroded
competitive claims the README and ADR-0002 still assert.
3. **Two ratified ADR claims are now factually wrong** (ADR-0002's "structurally impossible"
spend argument post-0007; ADR-0004's AGPL escape hatch) and need amendment before anyone
else makes these arguments in public.
Progress is genuinely fast — three spearhead steps in one week, green CI, honest ADRs — but all
of it so far is the commodity layer every voice-agent framework ships as a quickstart, and the
project's single most important proof artifact (a latency/barge-in benchmark) does not exist.
---
## Part I — Direction
### D1. The reflex loop fails the test ADR-0007 invented — and it's now the whole moat
**Attacks:** ARCHITECTURE.md ("Biggest technical risk"), README pillar 1 / wedge bullet 1,
ADR-0002 §Consequences.
ADR-0007 killed first-party SIP with three arguments: highest cost, lowest differentiation,
everyone credible already stands on something mature. Apply the same test to the reflex loop,
which ARCHITECTURE.md now names "the risk that remains… also the most-differentiating work":
- **LiveKit Agents ships semantic turn detection today**, plus interruption handling, DTMF,
warm/cold SIP transfer, and sub-500 ms end-to-end voice agents as the documented 2026 default
stack.
- **Pipecat ships interruption handling** as a configuration flag.
- **OpenAI Realtime does server-side VAD and barge-in natively** — and slice 3 already consumes
OpenAI's `speech_started` rather than running local VAD.
Barge-in and turn-taking are **table stakes implemented by every voice-agent framework**, not
white space. Turn-taking *quality* is increasingly fought inside the speech-to-speech models
themselves — the layer rutster explicitly rents.
What residually differentiates is the **quality floor under load**: p99 barge-in kill-latency
when a GC'd Python framework is running 50 concurrent calls. That is a real claim but a narrow
one — and rutster currently has zero measurement infrastructure to demonstrate it (see P3).
Meanwhile the genuinely undisputed white space in the project's own competitor table — **ACD,
queues, human escalation, supervisor tooling, CDR-you-own** — sits at capability-ladder rungs
23, *behind* spearhead steps 46.
**The uncomfortable question:** if every framework has interruption handling and the s2s models
are internalizing turn-taking, what does an ADR-0007-style strategic review conclude about
step 4's billing as "the most-differentiating work"?
**Assessment:** barge-in is still worth building — core-authoritative playout demands it, and it
is needed plumbing. But it is *plumbing you need*, not *the moat*. The sequencing conclusion is
in R1 below.
### D2. OpenAI Realtime SIP is GA and the docs don't account for it
**Attacks:** README competitor table, vision-revision §10 (the momentum-fuel thesis),
spearhead step 5.
A SIP trunk can now be pointed **directly at `sip:$PROJECT_ID@sip.api.openai.com`** — with call
transfer via REFER and a hangup endpoint. Twilio ships a first-party tutorial for exactly this.
Consequences, stated bluntly:
- The spearhead's emotional payoff — *"I called my Rust box and an AI answered the phone"*, which
vision-revision §10 explicitly names as the momentum fuel for a solo multi-year build — is now
a **no-code product feature of the brain vendor**.
- Step 5's topology (CPaaS media fork → rutster → OpenAI) is a strictly more complex path to the
same demo.
- Even primitive escalation (REFER to a human's SIP URI) is achievable OpenAI-direct.
The README competitor table has rows for LiveKit, cloud CCaaS, cloud AI-voice, and dated OSS —
but **no row for the brain vendor itself**, which is now the most dangerous competitor on the
board: it is eating the transport and the reflexes from the other side.
What survives is real: multi-vendor brains, data ownership, recording, queueing, containment
analytics, the auditable boundary. But the pitch must be re-aimed at what trunk→OpenAI
*structurally cannot be*, and step 5 needs an honest answer to "why is this better than pointing
the trunk at OpenAI directly?"
### D3. The compliance wedge's target customer can't run anything currently built or scheduled
**Attacks:** README wedge bullet 2, ADR-0007 §"What this costs", vision-revision §3.
The wedge is declared "strongest for PCI / HIPAA / TCPA." Walk a regulated buyer through the
actual topology: layer-1 PSTN audio traverses the CPaaS's infrastructure, and *all* audio
traverses OpenAI's. The buyer who cares about the auditable boundary is precisely the buyer who
refuses both.
For that buyer, rutster needs **two things that don't exist**:
1. The out-of-tree SBC graduation path — green zone, unbuilt, not scheduled as a deliverable.
2. A self-hosted brain — and the open speech-to-speech ecosystem remains the weak link. The
viable self-hosted stack in mid-2026 is *cascade* (Whisper-class STT + open LLM +
Kokoro/Chatterbox-class TTS); true open s2s is immature. Yet the tap protocol and slice 3 are
shaped around an s2s API.
So the moat, today, is a **promissory note whose redemption depends on two third-party
ecosystems maturing**. ADR-0007's "What this costs" section underprices this: the cost is not
"data-ownership dilutes for PSTN in layer 1," it is "**the flagship customer segment cannot
deploy at all until graduation, and graduation depends on components nobody is building**."
Meanwhile the segment the built path *does* serve (CPaaS + OpenAI, indifferent to the boundary)
is Vapi's and OpenAI-direct's home turf.
**Two concrete moves:** (a) prove the tap is brain-agnostic *now* — a cascade-stack example brain
(Whisper + Llama + local TTS) in `examples/` would make the sovereignty story demonstrable and is
roughly a day of work given the existing Python brain examples; (b) promote "SBC-graduation
reference configuration" from a footnote to a scheduled deliverable.
### D4. The "structurally impossible for a 3-vendor stack" spend claim died with ADR-0007
**Attacks:** ADR-0002 pillar table, README pillar 3, PORT_PLAN §10 (toll-fraud row).
Under ADR-0003 (rutster terminates the trunk) the claim was structural: the brain can't spend
because rutster holds the wire. Under ADR-0007, **rutster doesn't hold the wire either — the
CPaaS does.** The gate is now "rutster holds the provider API credential and the brain doesn't,"
which is an IAM/configuration property, not a structural one:
- Any orchestrator that holds the credentials can make the same claim (Pipecat with a spend
counter and scoped credentials).
- The CPaaS itself ships spend limits.
- A mis-scoped provider credential bypasses rutster entirely.
ADR-0002's pillar table still reads "structurally impossible for a 3-vendor stack" while the
ADR-0007 architecture **is** a 3-vendor stack (CPaaS + rutster + brain). The pillar is now
self-describing.
**What remains genuinely structural** and should replace the current language: pacing,
half-duplex, and playout enforcement over media rutster terminates — the brain proposes audio
but cannot place it on a wire rutster doesn't authorize, and the gate cannot be *skipped* on any
egress rutster mediates. That is a true and defensible claim. The current one is not.
### D5. The AGPL escape hatch in ADR-0004 is legally broken
**Attacks:** ADR-0004 §"Trade-off accepted deliberately".
ADR-0004 states: *"this ADR's 'or-later' clause permits that transition [to AGPL-3.0-or-later]
cleanly, since GPL-3.0-or-later is a strict subset of AGPL-3.0-or-later for recipients."*
This is incorrect. The "or-later" proviso (GPLv3 §14) covers **later versions of the GNU GPL**.
AGPL is a *different license*, not a later GPL version. GPLv3 §13 permits *combining* GPL and
AGPL works into a larger work — it does not permit relicensing GPL-3.0 code as AGPL-3.0.
Today this is moot: a sole copyright holder can relicense at will. But **the first external
contribution accepted without a CLA or copyright assignment permanently closes the AGPL
option** — every contributor would need to consent. Asterisk's own lineage is the precedent:
Digium required copyright assignment precisely to retain relicensing power.
Three options; pick one deliberately, before the first external PR:
1. **Decide AGPL now**, while it costs nothing.
2. **Institute a CLA / DCO-plus-assignment policy** from day one, preserving the option.
3. **Delete the claimed escape hatch** from ADR-0004 and accept GPL-3.0 permanently, mitigated
by the wedge as the ADR already argues.
The current ADR records a hedge the project does not actually hold.
### D6. The Asterisk-position analogy has a scarcity gap (noted, not fatal)
**Attacks:** ADR-0002 §Context, vision-revision TL;DR.
Asterisk won because it made the *expensive thing* — PBX/PSTN interop — free. The expensive
thing in 2026 is the model, and rutster explicitly rents it. The scarcity rutster arbitrates is
**trust and data sovereignty**: real, but thinner, and maturing on a slower clock than the
demo-driven voice-AI wave.
This creates a tension the docs don't name: the project's *emotional* engine (demos, momentum
fuel) and its *strategic* engine (sovereignty, compliance) pull in different directions —
compliance buyers don't show up for demos, and demo-chasers use OpenAI-direct. Given the stated
goal is strategic relevance rather than revenue, this is acceptable — but sequencing should
consciously serve the strategic engine (see R1).
---
## Part II — Progress
### Credit first
Initial commit 2026-06-26; slices 13 merged by 2026-07-01. In one week:
- A working WebRTC media core (`str0m` sans-IO, spec-driven).
- A versioned tap protocol with forward-compat by construction (`#[serde(other)]` catch-all).
- An OpenAI Realtime adapter with a pure-function translator layer and a mock brain enabling a
credential-free dev loop.
- ~7,200 lines landed with green CI, cargo-deny, rustfmt/clippy clean, and integration tests
that exercise the actual seams (reconnect, teardown ordering, playout flush).
- ADRs that record reversals honestly (0003 → 0007) instead of retconning.
- A teaching-grade codebase (LEARNING.md concept index) serving the stated learning goal.
For a solo-plus-agents week this is an outlier pace, and the docs-as-agent-context methodology
is visibly working. Now the adversarial part.
### P1. Three slices in, the claimed differentiator is untouched
Everything shipped — browser echo, WS tap, OpenAI adapter — is the commodity layer that exists
as a quickstart in every voice-agent framework. The advisory interruption events are decoded and
deliberately dropped (`crates/rutster-tap/src/tap_client.rs:409` — *"advisory interruption event
observed; not acted on in slice-3"*).
The pattern to watch: ADR-0003 declared SIP the hard, differentiating core — then ADR-0007
deleted it. The docs now declare the reflex loop the hard, differentiating core — and three
slices have shipped *around* it. Step 4 is where the thesis first touches reality; it should be
treated as the project's actual first proof, not step 4 of 6.
### P2. Doctrine-vs-code drift on the hot path, one week in
ARCHITECTURE.md (Media plane): *"Dedicated timing threads for the 20ms loop, **never the shared
tokio pool**."*
The implementation: a single `tokio::spawn` task with `tokio::time::interval(10ms)` driving
**all sessions serially**, with per-session `.await`s and async-mutex acquisitions inside each
tick (`crates/rutster/src/session_map.rs:215-224`, `251+`). That is the shared pool — plus
head-of-line blocking: one session stalled on a lock delays every other session's media.
Fine at slice scale. But pillar 1 *is* deterministic timing, and the code contradicts the
doctrine with no tracking issue, no debt comment, and no measurement that would reveal when it
stops being fine. Either amend the doctrine ("dedicated threads when N concurrent calls demand
it, measured by X") or file the debt explicitly. Silent drift between ratified docs and code is
the exact failure mode the ADR apparatus exists to prevent.
### P3. The wedge is unmeasured — README's latency numbers are arithmetic, not data
The ~250 ms (mock) and ~700 ms (OpenAI) figures in the README are estimates summed from
component guesses ("slice-1's 200 ms + tap round-trip + OpenAI latency + 100 ms playout
buffer"). `TapMetrics` (`crates/rutster-tap/src/metrics.rs`) is drop/gap counters only — no
latency histograms, no jitter measurement, no barge-in kill-time.
For a project whose entire technical claim versus a weekend of Pipecat is "tighter real-time
behavior, deterministically, under load," the missing artifact is a **repeatable benchmark**:
p50/p99 mouth-to-ear latency and barge-in kill-time at 1 / 10 / 50 concurrent calls, run in CI,
regressed per commit. Until it exists, the no-GC pillar is a brochure claim.
It is also the only credible marketing asset the project could produce this year: *"rutster vs
LiveKit Agents vs Pipecat, p99 barge-in kill latency under load — reproduce it yourself."*
Recommendation: make it part of step 4's definition of done (R2).
### P4. Doc rot at one week old
- `README.md` Status (both the §Status block and the mid-file callout) still says *"Slice 1
(WebRTC media loopback) is the active build target"* directly beneath a quickstart
demonstrating slice 3.
- `fuzz/README.md` still plans SIP/SDP parser fuzz targets landing at step 5 — ADR-0007
abolished the SIP parser. The fuzz story should re-aim at what actually parses hostile-ish
bytes now: tap protocol frames, the provider media-fork framing (step 5), RTP.
Small in isolation — but this repo's methodology is docs-as-agent-context, and stale docs are
corrupted context for every future agent session.
### P5. Process weight is worth an honest measurement
The slice-3 implementation plan is 2,967 lines — roughly 40% the size of the entire codebase it
produced. The spec/ADR discipline is clearly paying for itself (this review is downstream of
it). The per-task SDD scaffolding (briefs, reports, review diffs per task) may or may not be.
The data to check exists in `.superpowers/sdd/`: correlate review-cleanliness per task with
brief length. If short briefs review just as clean, the ritual is costing slices.
### P6. The tap protocol has no auth story, and protocols ossify at first adoption
The core listens plaintext on `0.0.0.0:8080` (`crates/rutster/src/main.rs:38` — documented,
acceptable for slices). The tap dials `ws://` with no authentication in either direction.
Core-as-client is genuinely good design. But "security-as-product" plus the ambition of
tap-as-open-protocol means **v1 of the protocol should carry an auth field** (even a static
bearer token; mTLS later) *before* third-party brains exist. The moment someone else implements
the protocol, mandatory auth becomes a breaking ecosystem change instead of a line in the spec.
---
## Part III — Recommendations
### R1. Re-run the ADR-0007-style strategic review on the spearhead's back half
Likely outcome: barge-in stays (needed plumbing; the benchmark vehicle) but **rung-2 escalation
is pulled forward, ahead of step 5**. Human-takeover with queueing and recording is the actual
white space — the thing none of LiveKit / Pipecat / Vapi / OpenAI-direct ships — and it is the
capability that answers D2's "why not point the trunk at OpenAI directly?" If accepted, this is
an ADR (it amends the spearhead sequence ratified in the vision revision).
### R2. Make the latency/barge-in benchmark harness part of step 4's definition of done
It is simultaneously: the wedge's proof (P3), the doctrine-drift detector (P2), and the only
demo that differentiates rather than reproduces (D1, D2).
### R3. Amend the two false ADR claims now
| Doc | Current claim | Amendment |
|---|---|---|
| ADR-0002 / README pillar 3 / PORT_PLAN §10 | Spend control "structurally impossible for a 3-vendor stack" | Narrow to what is true post-0007: co-located, unskippable on mediated egress; brain never holds provider credentials; pacing/playout structurally enforced over terminated media (D4) |
| ADR-0004 | "or-later permits [AGPL] transition cleanly" | Choose: AGPL now / CLA before first external PR / delete the hatch and accept GPL-3 permanently (D5) |
### Smaller items (cheap, do opportunistically)
- Add a **cascade-stack example brain** (Whisper + open LLM + local TTS) to `examples/` — proves
brain-agnosticism and the sovereignty story (D3).
- Add "the brain vendor itself" as a row in the README competitor table (D2).
- Schedule the SBC-graduation reference config as a named deliverable (D3).
- Fix README status staleness; re-aim `fuzz/README.md` at the post-0007 parser surface (P4).
- Add an auth field to tap protocol v1 before third-party adoption (P6).
- File the dedicated-timing-thread debt as a tracked issue or amend the doctrine (P2).
---
## Appendix — What was reviewed
**Docs:** README.md; docs/ARCHITECTURE.md; docs/PORT_PLAN.md; ADRs 00010008;
vision-revision (2026-06-26); slice 13 design specs; LEARNING.md; fuzz/README.md;
`.superpowers/sdd/progress.md`.
**Code:** all six crates (`rutster`, `rutster-media`, `rutster-tap`, `rutster-tap-echo`,
`rutster-brain-realtime`, plus the `rutster-call-model` / `rutster-spend` / `rutster-trunk`
stubs); integration tests; CI workflow; git history (d3bd621..c30a452).
**Market claims verified (July 2026):**
- LiveKit telephony + Agents: SIP inbound/outbound, DTMF, warm/cold transfer, semantic turn
detection, sub-500 ms default stack —
[LiveKit voice agents](https://livekit.com/voice-agents),
[LiveKit telephony docs](https://docs.livekit.io/telephony/),
[2026 LiveKit Agents guide](https://www.forasoft.com/blog/article/building-multimodal-ai-agents-with-livekit-guide)
- OpenAI Realtime SIP, GA, with REFER transfer + hangup endpoints —
[OpenAI Realtime SIP guide](https://platform.openai.com/docs/guides/realtime-sip),
[Twilio × OpenAI Realtime SIP trunking](https://www.twilio.com/en-us/blog/developers/tutorials/product/openai-realtime-api-elastic-sip-trunking),
[Introducing gpt-realtime (GA)](https://openai.com/index/introducing-gpt-realtime/)
- Self-hosted brain landscape: cascade (Whisper + open LLM + Kokoro/Chatterbox-class TTS)
viable; open s2s immature —
[Self-hosted voice AI stack 2026](https://blog.dograh.com/complete-self-hosted-voice-ai-stack-in-2026/)