feat: Donut voiced-interview slice-3 — retire-ready KB-recall bridge
Grounds the interview character in the ingested corpus while she stays in-voice. Tier-3 agents are tool-less by design in v1, so this is the consumer-side workaround (DEC-6, worldtree-dev ruling): per opted-in interview turn, ratatoskr consults Mimir out-of-band, extracts the passages, and pins them as memory_context on the character's turn. She frames the pinned corpus as her own memory. - src/ratatoskr/kb_bridge.py (new, RETIRE-READY): pin_kb_context — THE single seam (INV-KB-1). Allowlist-gated (INV-KB-4: ratatoskr:donut only), hard-timeout-bounded, degrades to [] on any failure/timeout/empty (INV-KB-3, never raises; CancelledError propagates). Imports nothing from the SDK-adapter / TTS core. aclosing() closes the SDK stream deterministically on the DoneEvent break. - wt.stream_turn: memory_context passthrough (defaults None — inert for every other caller and for the bridge's own retirement). Seam-review catch: the contract's original touch list undercounted wt.py by one file (recorded in the contract). - web/server.py: TurnHandle.agent_id + the single pin_kb_context call-site on the turn path; the browser now sends agent_id so the allowlist can gate. - web/static/index.html: the turn POST carries agent_id. Consult prompt tuned live: "search_library EXACTLY ONCE, no read_note" converges Mimir in ~3-15s (the softer "do one search" phrasing looped past 25s on conversational questions). TDD: 12 kb_bridge unit tests + wt memory_context forwarding + 2 server wiring tests (531 green). Live-smoked on :8081/b128: pin_kb_context grounds in the DCC corpus (real excerpts, <20s) and Donut answers in-voice; degrades cleanly on a slow consult. KNOWN LIMIT surfaced (not a bridge defect): DCC's fiction index is weak (failed backfill, a worldtree-dev item), so grounding is opportunistic — the bridge's real payoff is a corpus the model does not already know. Per docs/contracts/donut_voiced_interview.contract.md (slice 3 of 3).
This commit is contained in:
@@ -19,9 +19,11 @@ scope: >
|
||||
consult (an existing agent turn).
|
||||
touches:
|
||||
- src/ratatoskr/web/server.py # /api/tts route + the retrieval-pinning seam on the turn path
|
||||
- src/ratatoskr/web/static/index.html # speak-on-done playback, 🔊 toggle, <audio> sink
|
||||
- src/ratatoskr/web/static/index.html # speak-on-done playback, 🔊 toggle, <audio> sink; turn POST carries agent_id
|
||||
- src/ratatoskr/web/entrypoint.py # RATATOSKR_TTS_URL override (the tts swap seam)
|
||||
- src/ratatoskr/tts.py # NEW — Zonos gateway client + PAD->emotion-dial mapping
|
||||
- src/ratatoskr/kb_bridge.py # NEW, RETIRE-READY — consumer-side retrieval + memory_context pinning
|
||||
- src/ratatoskr/wt.py # stream_turn gains a memory_context passthrough (seam-review: the contract's original touch list undercounted this by one file; the param defaults None so the bridge's RETIREMENT stays inert — deleting kb_bridge.py + the one call-site leaves wt.stream_turn's SDK-parity param harmless)
|
||||
- docs/characters/donut.md # NEW — Princess Donut persona (content; the tier3 define source)
|
||||
depends_on:
|
||||
- "Zonos gateway: POST http://10.100.79.3:8890/v1/audio/speech (infra-ops; WG-internal, no auth; wav; verified 2026-08-02)"
|
||||
@@ -138,16 +140,26 @@ POST /api/tts {text, agent_id?} -> audio/wav
|
||||
|
||||
### FN pin_kb_context (kb_bridge.py — RETIRE-READY, INV-KB-1)
|
||||
```
|
||||
pin_kb_context(question: str, agent_id: str, *, client) -> list[dict] # memory_context items, or []
|
||||
pin_kb_context(question: str, agent_id: str | None, *, client) -> list[dict] # memory_context items, or []
|
||||
# The bridge. Consumer-side retrieval + pinning (DEC-6).
|
||||
steps:
|
||||
- gate on the interview-character allowlist (INV-KB-4); not listed -> [].
|
||||
- out-of-band Mimir consult: a one-shot turn "search the well for <question topic> (<char's corpus>)".
|
||||
- extract cited evidence text (bounded length).
|
||||
- gate on the interview-character allowlist (INV-KB-4); not listed / blank question -> [].
|
||||
- out-of-band Mimir consult (a throwaway session + one turn), HARD-bounded by a timeout.
|
||||
- extract the answer text (prefer DoneEvent.response; fall back to text deltas), bounded length.
|
||||
- return [{"kind":"corpus_reference","text":<extract>}].
|
||||
error/empty: any failure or no hits -> [] (INV-KB-3; never raises to the turn path).
|
||||
error/empty/timeout: any failure or no hits -> [] (INV-KB-3; never raises to the turn path).
|
||||
CancelledError (browser disconnect) is NOT caught — it propagates.
|
||||
CONSULT PROMPT (foot-gun mitigation, tuned live 2026-08-02): force "search_library EXACTLY ONCE,
|
||||
no read_note" — converges Mimir in ~3-15s. The softer "do one search" phrasing let Mimir loop
|
||||
read_note<->search past a 25s ceiling on conversational (non-keyword) questions (live-observed).
|
||||
KNOWN LIMIT (surfaced by the live smoke, NOT a bridge defect): the bridge's GROUNDING VALUE is gated
|
||||
by Mimir's retrieval quality on the target corpus. DCC's fiction-wing index is currently weak
|
||||
(scores ~0.02, failed backfill — a standing worldtree-dev item), so hits are noisy/partial; the
|
||||
model's own DCC training knowledge already grounds Donut well, so the bridge is opportunistic here.
|
||||
Its real payoff is a corpus the model does NOT know AND that indexes cleanly.
|
||||
RETIREMENT: when Worldtree #361 reference_knowledge reaches Tier-3, delete this module + the single
|
||||
server.py call-site; Donut then searches in-voice natively.
|
||||
server.py call-site (wt.stream_turn's memory_context param stays, inert); Donut then
|
||||
searches in-voice natively.
|
||||
```
|
||||
|
||||
### FN client: speakOnDone (index.html)
|
||||
|
||||
Reference in New Issue
Block a user