Files
ratatoskr/persistent-memory.d/2026-08-01-donut-voiced-interview-build.md
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

5.8 KiB

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) DONEdocs/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.