Phase A-B planning artifacts for the spearhead-4half-and-5 release: - Strategic plan (.omo/plans/): ADR-0010 deviation, 3-dev dispatch shape, auto-spawn flow via task(subagent_type=general, run_in_background=true) - Slice 4½ spec + plan: rutster-sim crate, SimAudioPipe, LatencyProbe, ConcurrencyRunner, TickLagGauge, sim-bench CI gate - Slice 5 spec + plan: G711Codec, TwilioMediaStreamsServer, TrunkSession, trunk_driver::drive, MediaLeg enum, CallControlClient trait - 4 kickoff prompts (PM + dev-a/b/c) updated for auto-spawn framing - AGENTS.md PM launch checklist item 4: auto-spawn-dev-via-task() flow Signed-off-by: Aaron D. Lee <himself@adlee.work>
35 KiB
Strategic plan — spearhead step 4½ (benchmark + sim harness) AND step 5 (rented transport)
- Status: Draft (awaiting Momus review)
- Date: 2026-07-05
- Spearhead footprint: steps 4½ + 5 (ADR-0010 inserts 4½ between 4 and 5; ADR-0007 defines step 5)
- Depends on (already merged):
- slice-1 WebRTC media core (PR #1)
- slice-2 agent tap (PR #2)
- slice-3 OpenAI Realtime brain (PR #4)
- slice-4 barge-in / VAD-driven playout kill (PRs #7–#13)
- slice-"5" scalability seams (librutster, NOT spearhead 5) — config.rs, event_sink.rs, MediaThread tick-lag gauge, drain lifecycle, advertised media address (PR #14)
- ADR-0009 amendment shared spend accounting (PR #15)
- Execution paradigm: tmux-based multi-agent PM + N-dev, relay on port 7110, dev slots driven by Kimi K2.7 worker subagents (256k context window). See AGENTS.md "Multi-agent coordination — the relay".
- Multi-agent-kickoff role count: 1 PM + 3 devs (dev-a / dev-b / dev-c).
- Branch prefix:
slice-4-half/andslice-5/(slice-level, not umbrella — see Git workflow).
1. Strategic scope
Two spearhead milestones bundled into one planning pass because (a) both ship on the same fused-vertical spine, (b) step 4½'s measurement harness is the proof artifact step 5's reflex claims depend on, and (c) bundling captures one PM setup + one relay poller lifecycle + one worktree layout.
1.1 What each slice IS
| Slice | Spearhead step | ADR | One-sentence goal | Output crate |
|---|---|---|---|---|
| 4½ — benchmark + sim | 4½ (inserted by ADR-0010) | ADR-0010 | A self-hostable simulation harness + CI-regressed latency thresholds that measures slice-4's ≤60 ms barge-in kill budget at 1/10/50 concurrency, exposing the single-thread HoL block debt with data not vibes. | crates/rutster-sim/ (new) |
| 5 — rented transport | 5 of 6 | ADR-0007 | A real phone number via rented CPaaS raw-media fork (Twilio Media Streams MVP), no first-party SIP. PSTN audio enters the same reflex loop WebRTC legs use. | crates/rutster-trunk/ (currently a stub; fills in this slice) |
1.2 The ADR-0010 sequencing fork (load-bearing — surface to user before execution starts)
ADR-0010 explicitly re-sequences the back half of the spearhead:
Sequencing after 4½: rung-2 escalation (human takeover) is pulled forward ahead of steps 5–6. It is the white space no competitor ships… it works entirely on WebRTC ingress (no PSTN dependency), and it is the capability that answers "why not point the trunk at OpenAI directly?" before step 5 makes that question live. Steps 5 (rented transport) and 6 (spend cap) follow, unchanged in content.
Per ADR-0010, the canonical post-4½ order is:
4 (merged) → 4½ (this plan) → rung-2 escalation (deferred) → step 5 (this plan) → step 6 (spend cap)
This plan bundles 4½ + step 5 per the user's explicit 2026-07-05 directive ("write a plan to cover then implement 4.5 and 5"), which pushes step 5 AHEAD of rung-2 escalation. This is a deliberate ADR-0010 deviation, recorded here so it isn't a silent override. Reasons the user might choose this:
- The momentum value of a real phone number is the visceral demo the spearhead has been aiming at since slice-1.
- 4½'s simulation harness already proves the wedge; escalation's white-space argument ("why not point trunk at OpenAI") waits, but doesn't degrade.
- Rung-2 escalation is genuinely greenfield (no precedence in the codebase); step 5 is a more bounded integration against an existing slice-5-seams config. Risk profile favors 5 first.
Default action: proceed with this plan as written (4½ + step 5). If on review the user prefers to honor ADR-0010 literally, swap step 5 → rung-2 escalation in Phase 2; everything in Phase 1 + 3 still applies (Phase 1 = both specs, Phase 3 dispatch infra is ADR-agnostic).
1.3 Out-of-scope for THIS strategic plan
| Item | Returns in | Why out |
|---|---|---|
| rung-2 escalation (human takeover, whisper, warm handoff) | later, post-step-5 OR per ADR-0010 (preferred ordering) | ADR-0010 says first; this plan defers per §1.2 fork |
| Spend cap / abuse gate (spearhead step 6) | later (post-step-5) | sequenced last; needs trunk to spend against |
| Layer-2 out-of-tree SBC adapter | graduation rung | ADR-0007 explicitly defers — only the layer-1 CPaaS-fork MVP ships in step 5 |
| Twilio ConversationRelay / managed Voice-AI products | never in core | ADR-0007 forbids — they consume the reflex loop that is rutster's differentiation |
| LLM-driven synthetic callers (4½ extension) | post-4½ refinement | ADR-0010 says scripted scenarios are 4½; LLM callers are a later extension |
| VAD tuning framework | post-spearhead | slice-4 §1.2 defers; 4½ still ships the const threshold |
| Inbound endpoint registration (desk phones) | never | ADR-0007 green-zone, out-of-tree, never first-party |
| Per-session media threads / threadpool shard | later rung | slice-4 §1.2 defers; 4½'s concurrency sweep surfaces the debt with data |
| Barge-in across multi-party / conferencing | later rung | slice-4 §1.2 defers |
Docker / compose up |
later rung | not in spearhead |
| Browser-based Playwright e2e | post-spearhead | unchanged from slice-1's deferral |
1.4 FOB / green-zone classification (ADR-0008 application)
Slice 4½ — entirely FOB. The sim harness:
- Hot-path-adjacent (it drives the same 20 ms media loop)
- Differentiating (the proof artifact; the wedge's measurement claim)
- Self-hosted, memory-safe Rust ⇒ FOB
The harness MAY link str0m and tokio (mature, actively-maintained) as trusted deps — same pattern as rutster-media. No green-zone members introduced.
Step 5 — split:
- FOB: the WebSocket SERVER that ingests the provider's media fork, µ-law ↔ 24 kHz PCM transcoding, the
TrunkAudioPipe(implAudioPipe) that plugs into the existing media-leg seam, the reflex-loop wiring (slice-4'sReflex/LocalVadReflexapply transparently because they decorateAudioPipe, agnostic to leg type). All hot-path or differentiating ⇒ FOB. - Green zone: Twilio REST API client (call control: answer/hangup/originate), credential storage, account-SID/account-token plumbing. This is a CPaaS dependency — ADR-0007 explicit. Lives in
crates/rutster-trunk/at arm's length: behind aCallControlClienttrait with aTwilioCallControlClientimpl + aMockCallControlClienttest double. The hot path never touches Twilio's REST API. The brain never holds provider credentials (ADR-0007 / ADR-0009).
1.5 The seam invariant (sacred)
crates/rutster-media/src/loop_driver.rs + crates/rutster-media/src/rtc_session.rs stay byte-identical through both slices. CI pinned-blob gate (slice-4 Task 10) keeps guarding. Every dev directive broadcasts this. The only exception: a future ADR explicitly says otherwise.
For slice 4½: no seam risk — the harness drives via existing media-leg path.
For step 5: the TrunkAudioPipe plugs into the existing MediaThread HashMap<ChannelId, RtcSession> shape; the existing RtcSession codepath (for WebRTC legs) is untouched. A new MediaCmd::RegisterTrunk variant routes through the existing command channel; the std thread accepts both WebRTC and trunk legs.
2. Phase 1 — Design specs (parallelizable)
Two design specs land before any code. Both can be written concurrently (different domains, no shared file between them).
2.1 Slice 4½ spec
Path: docs/superpowers/specs/2026-07-XX-slice-4-half-benchmark-sim-design.md
Required sections (mirror slice-4 spec skeleton):
- §1 Scope (1.1 in / 1.2 out — see §1.2 + §1.3 of this plan)
- §2 Architecture delta (the harness reusable across slices; measurement boundary; CI thresholds)
- §3 Component design (Scenario, SimAudioPipe, LatencyProbe, ConcurrencyRunner, TickLagGauge)
- §4 Data flow (caller-speech-onset → kill-fires path; the policy "drop + observe" for hot path)
- §5 Measurement plan (the budgets: ≤60 ms kill, p50/p99 mouth-to-ear, 1/10/50 concurrency targets)
- §6 Why these decisions (local vs LLM caller; scripted scenarios first; CI gate)
- §7 Done-criteria (CI threshold tables, regression fail-the-build contract)
- §8 Open decisions (threshold values; CI bench feature flag; machine-noise normalization)
- §9 Cross-references (ADR-0010, slice-4 §5.1 budget, slice-5/seams tick-lag gauge)
Pre-decided design calls (to lock at spec time, not negotiate during impl):
- The harness is a
cargo test --features=sim-benchorcargo bench-style entrypoint; defaultcargo test --alldoes NOT trigger simulated benchmarks (they're opt-in to keep CI fast for code changes). - A SECOND CI job runs
cargo test --features=sim-benchper PR + nightly; failure fails the build (CI gate, ADR-0010). - Scenario format is TOML or YAML (TBD at spec time). Each scenario = serialized caller PCM segments + timing + an expectation.
- Latency measurement uses
Instant::now()(already monotonic) and aligned ring-buffer capture (PcmFrame-tagged timestamps at sink/source). - Concurrency sweep asserts p99 kill-time ≤ 80 ms at 1 / 10 / 50 calls (80 ms = 60 ms budget + 20 ms observer slack; ADR-0010 explicitly allows the 60 ms kill + 1 tick).
- Tick-lag gauge (already in
media_thread.rsfrom slice-5/seams) is the primary readout for "is the single-thread poll-loop under tension?" Expected at 1 call: ≤2 ms lag. At 50 calls: ≤10 ms lag. >20 ms ⇒ test fails (the HoL debt is real and needs a threadpool shard, slice-4 §1.2 explicitly deferred).
2.2 Step 5 spec
Path: docs/superpowers/specs/2026-07-XX-slice-5-rented-transport-design.md
Required sections:
- §1 Scope (1.1 in / 1.2 out — see §1.2 + §1.3 of this plan)
- §2 Architecture delta (the trunk as a media-leg ingress parallel to WebRTC; µ-law↔PCM transcoding; provider REST at arm's length; the
MediaThreadextension) - §3 Component design (TwilioMediaStreamsServer, TrunkAudioPipe, G711Codec, CallControlClient trait + TwilioCallControlClient impl)
- §4 Data flow (provider WS connect → JSON "Start" → "Media" base64 µ-law frames → decode → PcmFrame → media-leg pipe → reflex loop → encode return)
- §5 Provider abstraction (the trait + the seam: config selects
twilio|telnyx|mock; only Twilio ships in MVP, the trait locks the seam) - §6 Why these decisions (no SIP, ever; raw-audio fork, not managed Voice-AI; FOB vs green-zone split)
- §7 Done-criteria (PSTN sim → reflex → barge-in e2e; media-leg abstraction works for both WebRTC + trunk; credential never leaks into the brain)
- §8 Open decisions (Twilio CallCtl vs raw-WS-only; Twilio chunk size / µ-law rate negotiation; account-SID storage — env vs KMS-backed Vault sidecar)
- §9 Cross-references (ADR-0007, ADR-0008, ADR-0009 spend-accounting amendment, slice-4 §3.1 reflex layering, slice-5/seams config surface)
Pre-decided design calls:
-
MVP provider is Twilio Media Streams (raw-audio WSS fork at 8 kHz µ-law). Cleaner doc'd API than Telnyx; well-trodden.
-
The provider REST call-control client (
answer/hangup/originate) is green-zone: behind a trait, the Twilio impl usesreqwest. The FOB never holds Twilio credentials directly — they live incrates/rutster-trunkconfig behind aTwilioCredentialsstruct, scoped to the trunk crate only. -
µ-law (G.711) codec: build it in-core (~30 lines, table-driven, learner-facing comments — it's a fascinating piece of telephony history). The 8 kHz ↔ 24 kHz resampling uses linear interpolation (the slice-1 PcmFrame is 24 kHz; Twilio ships 8 kHz). No new dep.
-
The trunk leg participates in
MediaThread'sMediaCmdenum asRegisterTrunk { provider, credentials, reply }+HangupTrunk { id, reply }. The existingRegister/AcceptOffer/Delete/Shutdownvariants are unchanged. The std thread accepts the registration, opens aTrunkAudioPipe, wiresReflex<TapAudioPipe>(same composition as slice-4) and puts it in the sameHashMap<ChannelId, RtcSession-or-TrunkSession>. Actually — to keep the seam clean, theEnum value carries the leg variant. -
The
AudioPipetrait works as-is for trunk legs. The existingTapAudioPipecurrently routes PCM overtx_pcm_in/rx_audio_outagainst the tap's WebSocket; for trunk legs, the tap WS is the trunk WS (provider raw-audio fork = the brain's audio_out / the caller's audio_in, swapped for the PSTN caller).Wait — that's a clean design: the trunk leg IS a TapAudioPipe where the WS endpoint is Twilio's media fork instead of OpenAI's WS endpoint. The tap protocol is replaced by Twilio's JSON framing. Different protocol on the WS, same
AudioPipeshape on the FOB side. ✓.
2.3 Spec writing mode
Specs are written by the PM in the main session (not delegated). Rationale: they encode load-bearing decisions cross-referencing ADRs, prior slices, and the reflex-loop seam. Dev terminals consume specs; they don't write them.
After both specs land, both get a Momus review (one per spec — .omo/plans/<spec-name> invocation pattern is non-standard, see OpenCode Momus docs; use the task(subagent_type="momus", prompt="<spec-path>") form per AGENTS.md).
3. Phase 2 — Implementation plans
Two implementation plans, one per slice. Each derived from its spec.
3.1 Slice 4½ implementation plan
Path: docs/superpowers/plans/2026-07-XX-slice-4-half-benchmark-sim.md
Tasks (Kimi-256k-sized — each fits comfortably, ≤4 files, ≤200 LOC, ≤6 tool calls per step):
| Task # | Title | Files | Depends on | Parallelizable-now? |
|---|---|---|---|---|
| S1 | crates/rutster-sim/ skeleton + Scenario/ScenarioStep types + PcmSegment |
new crate, crates/rutster-sim/src/{lib.rs,scenario.rs}, root Cargo.toml member |
none | FOUNDATION — lands first |
| S2 | SimAudioPipe: AudioPipe — scripted PCM playback on next_pcm_frame, captures received frames on on_pcm_frame (timestamps each) |
crates/rutster-sim/src/sim_audio_pipe.rs |
S1 | after S1 |
| S3 | LatencyProbe — mouth-to-ear + barge-in kill-time probes; ring-buffer capture timestamps from SimAudioPipe |
crates/rutster-sim/src/latency.rs |
S2 | after S2 |
| S4 | ScenarioRunner — drives one SimCall end-to-end against a target binary URL (or in-process via MediaThread) |
crates/rutster-sim/src/runner.rs |
S2, S3 | after S3 |
| S5 | ConcurrencyRunner — N concurrent SimCalls, computes p50/p99 + aggregates per-call latencies |
crates/rutster-sim/src/concurrency.rs |
S4 | after S4 |
| S6 | TickLagGauge — reads slice-5/seams tick-lag metric from media_thread.rs (already exposed via MediaCmd::Stats); exposes as primary readout in sweep report |
crates/rutster-sim/src/tick_lag.rs |
S5 | after S5 |
| S7 | CI bench feature + 1/10/50 sweep thresholds — cargo test --features=sim-bench; CI job asserts thresholds |
.github/workflows/ci.yml, crates/rutster-sim/Cargo.toml |
S5, S6 | after S5 + S6 |
| S8 | One shipped scenario pack (scenarios/loud-barge.toml, scenarios/quiet-advisory.toml, scenarios/sustained-call.toml) + LEARNING.md pointer |
crates/rutster-sim/scenarios/*, LEARNING.md |
S4 | filler; any time after S4 |
Critical path: S1 → S2 → S3 → S4 → S5 → S6 → S7 (linear). S8 is filler.
Why linear: each task consumes the prior task's types. Fanning out across this 8-task chain stalls. Instead, this slice is best executed by ONE Kimi dev (dev-a) doing the linear chain, while dev-b and dev-c work on the step-5 spec/plan + the step-5 fundamental tasks (see §3.2).
3.2 Step 5 implementation plan
Path: docs/superpowers/plans/2026-07-XX-slice-5-rented-transport.md
Tasks:
| Task # | Title | Files | Depends on | Parallelizable-now? |
|---|---|---|---|---|
| T1 | G711Codec — µ-law encode/decode (table-driven, learner-commented) + 8 kHz↔24 kHz resampling |
crates/rutster-trunk/src/g711.rs, crates/rutster-trunk/src/lib.rs |
new crates/rutster-trunk/Cargo.toml deps (none — pure std) |
FOUNDATION — lands first |
| T2 | CallControlClient trait + MockCallControlClient (in-process test double) + TwilioCredentials config struct + env parser |
crates/rutster-trunk/src/provider/{mod.rs, mock.rs, twilio.rs} |
T1 (independent — provider module imports g711 types? probably not — different file) |
after T1 OR parallel |
| T3 | TwilioMediaStreamsServer — accept inbound WSS connection, parse JSON Start/Media/Stop frames, decode base64 µ-law → PCM frames, encode return |
crates/rutster-trunk/src/twilio_media_streams.rs |
T1 | after T1 |
| T4 | TrunkAudioPipe: AudioPipe — wraps the Twilio media-streams session as an AudioPipe; produces PCM frames on next_pcm_frame (decoded from Twilio "Media" frames), consumes PCM on on_pcm_frame (encoded to µ-law + sent as JSON "Media" frames back) |
crates/rutster-trunk/src/trunk_audio_pipe.rs |
T1, T3 | after T3 |
| T5 | MediaThread::RegisterTrunk — extends MediaCmd enum (Register, AcceptOffer, Delete, Shutdown, Stats, Drain from slice-5) with RegisterTrunk variant; accepts a TrunkAudioPipe, wires Reflex<TapAudioPipe> (same composition as slice-4) and wraps LocalVadReflex outer; places in session map |
crates/rutster/src/media_thread.rs, crates/rutster/src/routes.rs (new POST /v1/sessions/trunk route) |
T4, + slice-4 (merged) + slice-5 (merged) | after T4 |
| T6 | TwilioCallControlClient impl — REST API client (answer/hangup/originate); uses reqwest; credentials from env (slice-5/seams config.rs already has the parser pattern) |
crates/rutster-trunk/src/provider/twilio.rs (extend), crates/rutster/src/config.rs (env parser) |
T2 | parallel with T3/T4/T5 |
| T7 | Reflex applies to PSTN leg — verify slice-4's Reflex<TapAudioPipe> + LocalVadReflex decorate the trunk TrunkAudioPipe transparently (or wire Reflex<TrunkAudioPipe> if the leg has its own pipe type) |
crates/rutster-trunk/tests/reflex_on_trunk.rs |
T4, T5 | after T5 |
| T8 | PSTN sim e2e — MockCallControlClient + an in-process Twilio-media-streams simulator; verify barge-in fires on PSTN leg; CDR / EventSink emission |
crates/rutster-trunk/tests/sim_integ.rs |
T5, T7 | after T7 |
| T9 | QUICKSTART update — env-var table for Twilio credentials; "make a real phone call" section | docs/QUICKSTART.md, README.md |
T5, T6 | after T5 + T6 |
| T10 | CI seam gate re-pin + cargo deny for any new Twilio dep | .github/workflows/ci.yml, deny.toml |
T6 (introduces reqwest dep) |
after T6 |
Critical path: T1 → T3 → T4 → T5 → T7 → T8. T2/T6 can start parallel with T1 if dev-b picks up T2 right away (provider trait doesn't depend on g711). T9 is filler (after T5 + T6 land). T10 is final sweep.
Note on T2 ordering: T2 is the spec'd out CallControlClient trait + mock + credential struct. It DOES NOT depend on T1 (g711 codec) — they live in different modules. Dev-b can pick up T2 in parallel with dev-c on T1.
3.3 Cross-slice dependency graph
4½ spec → 4½ plan → S1 → S2 → S3 → S4 → S5 → S6 → S7
↘ S8 (filler; parallel after S4)
5 spec → 5 plan → T1 (foundation, parallel with T2) → T3 → T4 → T5 → T7 → T8
↘ T2 → T6 ↗
T9 (filler after T5 + T6) T10 (final sweep)
4½'s harness doesn't depend on step 5 code (the sim harness drives any media leg; we'll bus it against the WebRTC ingress for the MVP latency thresholds — 4½'s tests run against WebRTC, not trunk, since slice-4 proved barge-in on WebRTC first). But the harness CAN test trunk legs once trunk lands — false dependency avoided.
Step 5's reflex reuse depends on slice-4 (merged) — not on 4½. So 4½ and step 5 implementation can run in PARALLEL across different dev terminals, in different worktrees.
4. Phase 3 — Multi-agent execution (tmux + Kimi K2.7 workers, 256k context)
4.1 Dev count + role assignment
3 dev terminals: dev-a, dev-b, dev-c. Each dev's terminal runs its own persistent agent (Claude Code or GLM session) which dispatches Kimi K2.7 Code worker subagents (task(subagent_type="kimi-worker", load_skills=[...], run_in_background=false, prompt="...")) for the unit-of-work tasks.
Why Kimi-worker: 256k context window is plenty for the per-task payloads above (each task's plan section + required reading + code ≅ 30–50k tokens). Kimi K2.7 Code is cheap + capable for slice-bound file-scoped Rust work. Each dev-session holds the cross-task context (worktree state, recent file edits, relay inbox); each Kimi-worker subagent does ONE task with TDD discipline.
Dispatch shape:
| Dev | Slice 4½ tasks | Step-5 tasks | Worktree path |
|---|---|---|---|
dev-a |
S1 → S2 → S3 → S4 → S5 → S6 → S7 → S8 (the linear chain) | — | /home/alee/Sources/rutster.slice-4-half-sim branch slice-4-half/sim-harness-dev-a |
dev-b |
— | T2 → T6 → T9 → T10 (the provider/REST + docs + final sweep chain) | /home/alee/Sources/rutster.slice-5-trunk branch slice-5/rented-transport-dev-b |
dev-c |
— | T1 → T3 → T4 → T5 → T7 → T8 (the media-streams + AudioPipe + MediaThread wiring chain) | /home/alee/Sources/rutster.slice-5-trunk-b branch slice-5/rented-transport-dev-c |
Why split step-5 across two devs: the file-set on the FOB side (crates/rutster-trunk/src/{g711.rs, twilio_media_streams.rs, trunk_audio_pipe.rs} + crates/rutster/src/media_thread.rs) is non-overlapping with the provider/REST + config side (crates/rutster-trunk/src/provider/* + crates/rutster/src/config.rs + docs/QUICKSTART.md). dev-c on FOB leg wiring, dev-b on provider/config/docs — same pattern as slice-4 (dev-a on reflex stack, dev-b on tap-client + mock brain).
4.2 Dispatch schedule (PM-side, derived after specs + plans land)
D-Day:
T+0 PM terminal opens (single tmux window — start.sh --tmux opens relay + pm only),
scans .omo/plans + specs 4½ + step-5.
T+10m PM applies multi-agent-kickoff skill → generates 4 kickoff prompts
(saved under `docs/superpowers/kickoffs/2026-07-XX-spearhead-4half-5-{pm,dev-a,dev-b,dev-c}-prompt.md`).
T+15m User pastes ONLY the PM prompt into the pm window. Relay + poller already running
(PM launch checklist steps 1+2 done by the user before pasting).
T+30m PM auto-spawns dev-a/b/c via 3 `task(subagent_type="general", run_in_background=true,
prompt="<dev-prompt-file-content>")` calls. Each returns a `bg_...` task ID; PM
retains them for `background_output(task_id="bg_...")` polling.
Each dev subagent:
- cd's into its worktree (creating it per the prompt's setup bash)
- Reads plan + spec + AGENTS.md
- Dispatches `task(subagent_type="kimi-worker", ...)` per plan task
- Posts STATUS reports to the relay (`from="dev-X"`, `to="pm"`, `kind="status"`)
- Returns REVIEW-READY + tea PR URL when its slice is complete
T+1h+ PM polls each dev's `bg_...` between its own turns + drains the relay inbox for
kind=status messages. Surfaces actionable items to the user before they have to ask.
Opens PR reviews (via tea) as each dev completes a task group.
T+5h ballpark EOL: all tasks merged, final CI sweep, Momus review of implementation,
optional surfacing of any ADR-0010 escalation-vs-step-5 deviation for the user to ratify.
4.3 Per-task payload shape (what each Kimi-worker subagent receives)
Each task becomes one Kimi-worker invocation. The dev-session (holding context across tasks) calls:
task(
subagent_type="kimi-worker",
load_skills=["programming"], // shared/programming — strict-types, TDD
run_in_background=false,
prompt="<task-prompt>"
)
Where <task-prompt> is the Kimi-sized work unit. It carries six sections (matching AGENTS.md delegation protocol):
TASK: <S1 / T4 / etc. — short name>
EXPECTED OUTCOME: <files touched + tests added + commit message>
REQUIRED TOOLS: <Read/Grep/Edit/Bash + cargo test scoped to crate>
MUST DO:
- Read the plan file: docs/superpowers/plans/<plan-path> -- section Task <N>
- Read AGENTS.md "Code style (Rust)" section -- learner-facing comments REQUIRED
- DCO signoff on every commit: `git commit -s`
- Hot-path policy (if touching 20 ms tick code): NO ?-propagate; match-and-continue
- Worktree: cd <dev-worktree-path> before any edit
MUST NOT DO:
- Do NOT touch crates/rutster-media/src/{loop_driver.rs,rtc_session.rs} -- seam invariant
- Do NOT cross-touch files owned by another dev (see plan: parallel-safe set per task)
- Do NOT push --force, --reset-hard, branch -D, rm -rf
- Do NOT start the next task in this subagent -- report STATUS only
CONTEXT:
- Branch: <dev-branch>
- Plan: docs/superpowers/plans/<plan-path>
- Spec: docs/superpowers/specs/<spec-path>
- Predecessor task status (if applicable): <SHA + commit message>
- Successor task waiting on this: <task name + dev role>
Task size validation (Kimi 256k constraint): each <task-prompt> plus its required reading (plan section, spec section, AGENTS.md relevant section) plus the dev's code-write payload fits comfortably in ≤40k tokens. The 256k ceiling gives 6× headroom for Kimi to read more broadly (other slice docs, str0m API exploration, etc.) if it benefits the work. Empirically kimi-worker is fast at file-scoped Rust work at this size.
4.4 Relay + worktree layout
| Item | Value |
|---|---|
| Relay path | ~/Sources/relay (standalone, per AGENTS.md) |
| Relay port | 7110 (rutster's port per AGENTS.md table) |
| Poller command | setsid env RELAY_PORT=7110 python3 ~/Sources/relay/poller.py >> /tmp/relay-poller/7110/poller.log 2>&1 < /dev/null & disown |
watch.sh window |
RELAY_PORT=7110 ~/Sources/relay/watch.sh (live tail of inbox.log + poller.log) |
| Worktrees | /home/alee/Sources/rutster.slice-4-half-sim (dev-a) · /home/alee/Sources/rutster.slice-5-trunk (dev-b) · /home/alee/Sources/rutster.slice-5-trunk-b (dev-c) |
| Branch scheme | slice-4-half/sim-harness-dev-a · slice-5/rented-transport-dev-b · slice-5/rented-transport-dev-c |
| PM does NOT own a worktree | PM only reads main + reviews PRs (per multi-agent-kickoff role boundaries) |
PR via tea |
tea pulls create --head <branch> --base main --title "..." --description "..." (per AGENTS.md Git workflow) |
| Merge strategy | squash-merge for both slices (single-PR-per-task shape, linear history); carve-out for stacked branches (slice-4-half / slice-5 branch pair) → rebase-merge if SHAs carry to a dependent sibling branch |
4.5 Multi-agent kickoff — files this plan hands to the kickoff skill
After both specs + both impl plans land, the kickoff skill consumes:
<<SPEC_PATH>>— there are TWO specs. The kickoff skill's<<SPEC_PATH>>placeholder is single-valued; we substitute with both, comma-separated, in the PM prompt's required-reading list (the kickoff skill's templates support explicit required-reading lists per role). Dev prompts reference ONLY their own slice's spec (dev-a → 4½ spec only; dev-b/dev-c → step-5 spec only).<<PLAN_LIST_BULLETS>>for the PM:- Plan A:
docs/superpowers/plans/2026-07-XX-slice-4-half-benchmark-sim.md— slice 4½ sim harness; dev-a; S1→S8 linear; readouts feed ADR-0010's "is the wedge measured?" debt - Plan B:
docs/superpowers/plans/2026-07-XX-slice-5-rented-transport.md— step-5 rented transport; dev-b (provider+docs) + dev-c (FOB media-streams + MediaThread wiring); 10 tasks T1→T10; Twilio Media Streams MVP per ADR-0007
- Plan A:
<<DEV_COUNT>>= 3<<RELEASE_LABEL>>=spearhead-4half-and-5<<DEV_LETTERS_LIST>>=A, B, C<<ALL_ROLES_LIST>>=pm, dev-a, dev-b, dev-c<<RELAY_PORT>>=7110<<PROJECT_RULES_FILE>>=AGENTS.md(not CLAUDE.md — rutster uses AGENTS.md)
4.6 Per-dev scope summaries (for the kickoff <<DEV_SCOPE_SUMMARY>> placeholders)
dev-a (slice 4½): Implements the entire crates/rutster-sim/ crate (currently non-existent) — Scenario/ScenarioStep types, SimAudioPipe implementing rutster-media's AudioPipe trait, LatencyProbe with ring-buffer timestamp capture, ScenarioRunner for end-to-end call simulation, ConcurrencyRunner for N-concurrent-call p50/p99 latency measurement, TickLagGauge integration with slice-5/seams MediaThread::Stats metric, and a CI-regressed bench feature gating cargo test --features=sim-bench. Linear chain S1→S8; ships ADR-0010's "wedge is now measured by data, not arithmetic" closure.
dev-b (step 5, provider + docs): Implements the green-zone side of crates/rutster-trunk/ — the CallControlClient trait + MockCallControlClient (in-process test double) + TwilioCallControlClient (REST client via reqwest), the TwilioCredentials config struct + env parser wired into crates/rutster/src/config.rs, the QUICKSTART + README updates for "make a real phone call", and the cargo-deny + CI seam-gate re-pinning after the new reqwest dep. Chain T2→T6→T9→T10; tightly bounded — never touches the FOB media-stream machinery.
dev-c (step 5, FOB media + MediaThread wiring): Implements the FOB side of crates/rutster-trunk/ — the in-core µ-law (G.711) codec + 8kHz↔24kHz resampling (table-driven, learner-commented), the TwilioMediaStreamsServer (accept inbound WSS, parse JSON Start/Media/Stop frames, base64 decode → PCM frames), the TrunkAudioPipe: AudioPipe (wraps the WSS as an AudioPipe that produces PCM on next_pcm_frame / consumes + encodes on on_pcm_frame), the MediaCmd::RegisterTrunk extension to the existing MediaThread, the reflex-on-trunk-leg verification test, and the PSTN-sim e2e integration test. Chain T1→T3→T4→T5→T7→T8; the FOB hot-path / differentiating work.
5. Risks + open forks
5.1 ADR-0010 deviation (the load-bearing one)
§1.2 above. The user's directive ("4.5 and 5") pushes step 5 ahead of rung-2 escalation, contradicting ADR-0010's explicit pull-forward. Surfacing at user level with proposed default: proceed with this plan as written; ratify the deviation by amending ADR-0010 (or leave ADR-0010 intact + note the deviation on the slice-5 PR description). ADR-0010 doesn't strictly forbid the deviation — it states a default ordering. The user's call is legitimate; this plan records it loudly.
5.2 Twilio account credentials in CI
Slice 5's e2e needs Twilio credentials OR a Twilio simulator. The FOB-side TwilioMediaStreamsServer is testable in-process (simulator → real WSS→ real TrunkAudioPipe → real MediaThread → real Reflex); the green-zone-side TwilioCallControlClient needs either:
- (a)
cargo test --features=twilio-liverunning against real Twilio (only on maintainer-triggered runs, not per-PR) - (b) a
MockCallControlClientfor the per-PR sweep + a manual "live override" path
Default: (b). CI runs the mock. The maintainer runs live e2e manually when validating a release. Documented in QUICKSTART.
5.3 Reflex re-composition for trunk legs
Slice-4's Reflex<TapAudioPipe> composes with LocalVadReflex outside. For trunk legs, the AudioPipe is TrunkAudioPipe. The composition is LocalVadReflex<Reflex<TrunkAudioPipe>> — same shape, different inner. The reflex trait + decorators are pipe-agnostic by design (slice-4 §3.2); the same wrappers instantiate against the new pipe.
Risk: if the trunk AudioPipe has different lifecycle semantics (e.g. needs explicit WebSocket close on barge), the slice-4 barge_in_flush interface might be too narrow. T7 (the reflex-on-trunk verification test) surfaces this. If it triggers, STOP + post kind=question to PM; do not silently widen the trait.
5.4 CI bench feature flag interaction with the existing test matrix
Slice-4's CI matrix runs cargo test --all on stable + 1.85. Adding --features=sim-bench to a separate job is fine; just ensure the bench feature doesn't ADD a default-on dependency (it must be opt-in only, or default cargo test --all stays fast for routine PRs).
5.5 Slice-5 / "scalability seams" naming collision
The merged infra-seams slice (PR #14) is internally labeled "slice-5" in its plan path (docs/superpowers/plans/2026-07-04-slice-5-scalability-seams.md) but the plan itself states: "this is not spearhead step 5 (rented-transport trunk)." That infra slice is the pre-paver for spearhead step 4½ + step 5. Newcomers may be confused. Mitigation: the new step-5 plan path uses slice-5-rented-transport (unambiguous); the existing one stays as-is (already merged). Document the distinction in the spec's §1.
5.6 Single-thread contention on trunk leg
Slice-4 §1.2 explicitly defers per-session media threads / threadpool shard. Slice 4½'s concurrency sweep (target: ≤10 ms tick-lag at 50 calls) reads the gauge but the trunk leg will be the first time a "second kind" of leg shares the std thread. If contention is observed during slice-5 dev-c e2e, the slice-4-half/seams debt pays off — the threadpool-shard graduation is the answer with data, not doctrine. Don't pull the graduation forward speculatively.
6. Phase order summary (PM cheat-sheet)
Phase A — Specs (PM, in main session, parallel):
1a. Write `docs/superpowers/specs/2026-07-XX-slice-4-half-benchmark-sim-design.md`
1b. Write `docs/superpowers/specs/2026-07-XX-slice-5-rented-transport-design.md`
→ Momus review both
Phase B — Impl plans (PM, in main session, parallel):
2a. Write `docs/superpowers/plans/2026-07-XX-slice-4-half-benchmark-sim.md`
2b. Write `docs/superpowers/plans/2026-07-XX-slice-5-rented-transport.md`
→ Momus review both
Phase C — Multi-agent kickoff (PM, in main session):
3. Run `multi-agent-kickoff` skill — generate pm + dev-a + dev-b + dev-c prompts
to `docs/superpowers/kickoffs/2026-07-XX-spearhead-4half-5-{pm,dev-a,dev-b,dev-c}-prompt.md`
4. Verify relay (7110) is running; verify poller (≥3 cycle-complete lines)
Phase D — Execute (PM + dev terminals, separate windows):
5. User opens 4 terminals, pastes prompts
6. Each dev dispatches Kimi-worker subagents per task
7. PM consolidates relay inbox + opens PRs as tasks finish
8. Momus sweeps the merged stack
7. Stopping condition
This strategic plan is done when:
- Both specs (4½ + step-5) are merged to main.
- Both impl plans are merged to main.
- The multi-agent-kickoff prompts exist + are committed (or the user opts to paste them ad-hoc).
- Kimi-worker dispatch has executed every task in §3.1 + §3.2; all PRs merged.
- CI is green on main:
cargo fmt --check,cargo clippy -- -D warnings,cargo test --all(stable + 1.85),cargo deny check, the newcargo test --all --features=sim-benchjob. - The seam gate still passes —
loop_driver.rs+rtc_session.rsbyte-identical. - Momus review of the merged stack passes.
- ADR-0010 status reconciled (amend OR note deviation on the step-5 PR description).
At this point the spearhead has steps 1–5 complete; only step 6 (spend cap) + rung-2 escalation remain (in ADR-0010-recommended order: escalation first, then spend cap; per §1.2 fork, the user may re-confirm).
8. First action (PM-side, after plan ratification)
If this plan is ratified post-Momus:
- Confirm the ADR-0010 deviation choice with the user (proceed as-written, swap step 5 → escalation, or amend ADR-0010).
- Read AGENTS.md "Multi-agent coordination" + "PM-mode discipline" sections in full.
- Start the poller if not running (verify ≥3 cycle-complete lines).
- Begin Phase A (specs).