Files
ratatoskr/persistent-memory.d/2026-08-01-donut-voiced-interview-build.md
T
vh 3e912b13b3 feat: Donut voiced-interview slice-1 (contract + persona + define) + /snapshot
Slice 1 of the auto-TTS/voiced-KB-character build (operator ask "add auto-tts
to the web gui"): the donut_voiced_interview contract (validated), the Princess
Donut persona (corpus-grounded from a Mimir DCC pull), and ratatoskr:donut
defined on :8081 (server-side; in the picker). Slices 2 (Zonos auto-TTS) + 3
(retire-ready KB-bridge) are TO BUILD.

Snapshot captures the full build state + design (Zonos gateway :8890, voice
"donut" registered, affect-driven emotion dials; the worldtree-dev-ruled
consumer-side retrieval + memory_context pinning bridge, retire-ready) for the
post-clear resume, plus the arcs since v0.22.0 (SDK 1.1.2 repin, bifrost 1.1.5,
canonical sync, release-only versioning, the Sindra saga + local-index
schema-burial foot-gun, the Mimir #382 reference-consumer finding). Handoff at
/tmp/ratatoskr-dev-handoff.md. Release-only cadence: no tag.
2026-08-01 18:17:52 -07:00

79 lines
5.8 KiB
Markdown

# Donut voiced-interview build — ACTIVE (auto-TTS via Zonos + KB-recall bridge + Princess Donut)
`[2026-08-01]` Operator asked: "add auto-tts to the ratatoskr web gui" → grew into a voiced,
corpus-grounded Tier-3 interview character. Contract:
`docs/contracts/donut_voiced_interview.contract.md` (validated). Slice 1 DONE; slices 2-3 TO BUILD.
Operator ruling: **build it today, but design the KB path to RETIRE** (it works around a Worldtree
gap — Tier-3 agents are tool-less by design; KB search is integral, so the gap should close).
## Slice status
- **SLICE 1 (persona) DONE** — `docs/characters/donut.md` authored (corpus-grounded from a Mimir
pull + DCC canon: interview-register performer, "Carl looks intimidating while I talk," vulnerability
under a good question, and the "pinned corpus = her own memory" framing). **Defined:**
`ratatoskr:donut` (role `thoughtful-character`) live on personal :8081, in the console picker.
Chattable now (from training until slice 3). NOT yet committed (uncommitted at snapshot).
- **SLICE 2 (auto-TTS) TO BUILD** — new `src/ratatoskr/tts.py` (Zonos gateway client + `pad_to_dials`) +
`/api/tts` proxy route in `web/server.py` + `index.html` (🔊 toggle, speak-on-`done` playback,
serialize/cancel). TDD (respx-mock the gateway), live-smoke.
- **SLICE 3 (KB-bridge) TO BUILD** — new `src/ratatoskr/kb_bridge.py` (RETIRE-READY, INV-KB-1) +
the one server.py seam. Per interview turn: out-of-band Mimir consult → extract evidence →
POST Donut's turn with `memory_context=[{kind:"corpus_reference", text:"<extract>"}]`. She answers
in-voice. TDD.
## Design — LOCKED (both external deps verified live 2026-08-01)
**TTS (infra-ops Zonos specs, verified):**
- Endpoint: `POST http://10.100.79.3:8890/v1/audio/speech` (the zonos GATEWAY at irv-ml1:8890 — NOT
the engine's :1920, which has a rep-penalty bug). Also GET /v1/voices, /v1/dials, /health. WG-internal,
**no auth**. Reachable from nh3-dev (the console box) over WireGuard.
- Request: JSON, only `input` required. `response_format:"wav"` (16-bit RIFF; **mp3/opus silently
fall back to mislabeled PCM — never use**). Verified: voice=donut → HTTP 200, RIFF/WAVE PCM 16-bit
mono 44.1kHz.
- **NOT streaming** — gateway buffers to a complete clip (~1.8s/sentence). "Speak on done" gives the
whole clip after the synth delay. True streaming = a future gateway enhancement (infra-ops can raise).
- **Voice: `voice:"donut"` is ALREADY REGISTERED** (infra-ops cloned Vuong's Princess Donut bundle;
GET /v1/voices returns Donut; case-folded so "Donut.wav"→voice:"donut"). Custom-voice registration
mechanism: drop a `<Name>.wav` (44.1kHz mono 16-bit, ~10-20s clean single-speaker) in the gateway
voices dir + restart. Audition A/B: http://10.100.10.50:8090/b/donut-voice/
- **Affect-driven emotion (DEC-7, the standout):** map the turn's live PAD (from the `affect_update`
SSE the console ALREADY consumes) → Zonos `emotion_valence`(pleasure)+`emotion_arousal`(arousal).
Reframes the feature as voice OBSERVABILITY (hear the affect the persona pane shows), NOT chat-app
creep — resolves the scope concern. Dials also: `preset`, `emotion_sliders{happy,sad,angry,surprised}`,
`emotion_strength`, `speed`, `seed`.
- **Coupling: direct :8890** (DEC-1) for the emotion dials; the swappable LiteLLM `ext-tts` alias
(10.250.50.70:4000) loses the Zonos emotion params. `tts.py` is the single swap seam.
- **Serialize** one synth per turn (DEC-5) — shared 3090 ~90% VRAM; cancel-on-new-turn.
- **Proxy** (DEC-4): browser → `/api/tts` (nh3-dev) → gateway; never surface irv-ml1 to the client.
**KB-recall (worldtree-dev ruling, wyrd-proven):**
- Tier-3 is DELIBERATELY tool-less in v1 (`build_tier3_agent_context` hard-codes `tool_schemas=[]`);
no search_kb grant, no call-Mimir-as-tool (#361 rejected it, recursion). So: **consumer-side
retrieval + memory_context pinning** — ratatoskr consults Mimir out-of-band, extracts the cited
evidence, pins `memory_context=[{kind:"corpus_reference", text}]` on Donut's turn. She frames it as
her own memory (the persona prompt already instructs this). Our SDK's stream_turn already forwards
`memory_context` verbatim. Latency: one retrieval round-trip/turn.
- **RETIRE-READY (INV-KB-1):** kb_bridge behind ONE seam `pin_kb_context(question, agent_id) ->
list|[]`; retiring = delete the module + one call-site. Delete when Worldtree #361
`reference_knowledge` extends to Tier-3 (operator flagged it an integral GAP; worldtree-dev surfacing
the extension to Vuong — decision inbound to the operator).
## OWED / open loops (post-clear)
- **P&P verification re-run OWED to infra-ops** — #382 deployed (b166: self-describing `_index.md` +
the Mimir prompt rule "hits ARE library content, never discard/substitute"). Re-run the failing
P&P-dismiss shape on :8081, report to infra-ops. **DCC's backfill FAILED** (job predates state-tracking,
no `_index.md` yet — flagged to worldtree-dev) — but the prompt rule alone should stop the discard,
so the KB-bridge's DCC Mimir consults should ground fine. Verify this when building slice 3.
- **worldtree-dev #361-Tier-3 extension** — an inbound surface decision for Vuong (would let Donut
search in-voice + delete the bridge).
- **infra-ops custom-voice** — mechanism proven; hand them a `<Name>.wav` + name for any future character.
## Discipline
Operator said "do both slices" (skip /heid-contract-review, straight to TDD). Run heid code-review +
bug-hunt AFTER each code slice. Release-only versioning cadence (no per-commit tags; `feat:`/`fix:`
conventional commits between releases). CLI to drive Mimir/Donut turns:
`ratatoskr --send "..." --new --agent <id> --end-user-id ratatoskr-tui`. Define:
`python -m ratatoskr.tier3 define --name <slug> --system-prompt "<prompt>" --role thoughtful-character`.