Voice — Wake-word + Talk Mode (settings + privacy)
Toggles obrigatórios em Settings (`voice.enabled`, `voice.talkMode`, `voice.hotWord`, `voice.backend`, `voice.bargeIn`, `voice.debugRecord`), defaults seguros (tudo OFF na instalação fresca exceto `bargeIn` que é ON quando `talkMode` ativo), e contrato de privacidade — ring buffer pré-wake de 2 s **nunca** sai do dispositivo, post-wake só vai pro `services/ai/voice` configurado, nada é logado/persistido a menos que `debugRecord = true`. Surface compartilhada: `KodeVoiceSettingsTile` em `engines/sdk/koder_kit`.
Voice Wake-word + Talk Mode Spec — v0.1
Normative spec derived from
services/ai/voice/docs/rfcs/RFC-001-wake-word-talk-mode.md. Implementação obrigatória viaKodeVoiceSettingsTileemengines/sdk/koder_kit(Flutter) — nunca rolar UI de voz local.
Scope
Aplica-se a *odo app Koder com microfone* em qualquer plataforma: Flutter mobile (Android + iOS), Flutter desktop (Linux + macOS + Windows), e qualquer surface futura que capture áudio do usuário (TV em casos específicos, web PWA com getUserMedia). CLI/TUI sem mic *ão*implementam wake-word; recebem texto via stdin como hoje.
1 — MUST: expose "Voz" toggle group in Settings
Todo app Koder com mic capability *eve*ter um agrupamento "Voz" (Voice em en-US) na tela de Settings, contendo, na ordem:
- *oz ativa*(
voice.enabled) - *odo Talk*(
voice.talkMode) — mostrado apenas quandovoice.enabled = true - *alavra de ativação*(
voice.hotWord) — input com sugestões - *etector*(
voice.backend) — dropdown - *arge-in*(
voice.bargeIn) — visível apenas em Talk Mode - *ravar áudio para depuração*(
voice.debugRecord) — visívelapenas em builds dev/debug
A implementação é via drop-in KodeVoiceSettingsTile do koder_kit. *unca*desenhar a tile localmente — todo app importa o componente e o renderiza onde quiser na tela de Settings.
2 — MUST: defaults seguros em fresh install
Em uma instalação nova (ou após Reset Settings) os valores armazenados devem corresponder à tabela abaixo. *bsência da chave = mesmo valor que default* ou seja, configuração mínima é silently OFF.
| Chave | Default | Notas |
|---|---|---|
voice.enabled |
*FF* | Privacy |
voice.talkMode |
*FF* | Exige voice.enabled = true; UI *eve*desabilitar (greyed-out) quando enabled é false |
voice.hotWord |
"hey kode" |
Lower"oi kode") |
voice.backend |
autoselect | Plugin escolhe (preferindo openWakeWord); usuário pode forçar "openwakeword" ou "porcupine" |
voice.bargeIn |
*N*(em Talk Mode) | Cancela TTS quando o usuário começa a falar — relevante apenas com talkMode ativo |
voice.debugRecord |
*FF* | Disponível *penas em builds dev/debug* em release builds o toggle não aparece sequer |
Quando voice.enabled for toggled OFF em runtime, o app *eve*parar imediatamente o capture de mic (interromper qualquer detector ativo) + limpar o ring buffer (ver §4).
3 — MUST: hot-word configurável
voice.hotWord aceita qualquer string que tenha um modelo carregável no detector configurado. O KodeVoiceSettingsTile *eve*
- Sugerir a lista de hot-words shipped (
hey kode,oi kode, etc.) - Aceitar input livre com validação preview (resolve modelo? exibe
✓/✗ inline)
- Quando o detector não tem modelo para a palavra escolhida, retornar
o erro
voice.ErrModelMissinge exibir a mensagem padronizada perspecs/errors/user-facing-messages.kmd
4 — MUST: privacidade do ring buffer pré-wake
O detector mantém um *ing buffer rolante de 2 segundos*de áudio PCM 16-bit mono 16 kHz. Esse buffer é uma estrutura de memória local e *unca*sai do dispositivo. Especificamente:
- *UNCA*enviado a
services/ai/voiceantes da wake-event detection - *UNCA*persistido em disco
- *UNCA*logado em telemetria, breadcrumbs, traces ou error reports
(mesmo quando
voice.debugRecord = true— o pré-wake permanece efêmero) - *UNCA*acessível por outro processo (no Flutter mobile o buffer
vive no plugin native; no desktop vive no processo do app — nenhum IPC channel exporta esse buffer)
Quando o detector dispara (wake event), o *onteúdo do buffer*+ o áudio capturado a partir desse instante é encaminhado ao endpoint configurado em services/ai/voice para STT. O encaminhamento dessa amostra *eve*documentar via KoderApp.config():
- A URL do endpoint para o qual o áudio é enviado
- Confirmação visual no app (ex: indicador de mic ativo, sound effect
curto) — operadores nunca devem ouvir o app sem saber que está ativo
5 — MUST: debugRecord só em dev builds
voice.debugRecord = true faz com que *oda a captura post-wake*seja salva localmente em ~/.kode/voice/debug/<utc-timestamp>.wav. Esse caminho é local-only, mas é uma feature trapdoor — em release builds o toggle *ão pode aparecer*
- A flag é gateada por
KoderApp.isDebugBuildnokoder_kit. - Quando o usuário ativa
debugRecord(mesmo em dev), o app *eve*exibir um warning visível ("Debug recording está ON — áudio sendo gravado em disco") e logar a ativação no console.
- O ring buffer pré-wake *ontinua não sendo persistido*mesmo em
modo debug. Privacy
bydefault não pisa nesse contrato.
6 — MUST: barge-in semantics em Talk Mode
voice.bargeIn = true (default em Talk Mode) significa: quando o TTS está reproduzindo a resposta do agente E o VAD detecta fala do usuário, o TTS *eve*ser cancelado imediatamente (fade-out ≤ 100 ms para evitar pop) e o detector volta para escuta.
voice.bargeIn = false é o modo half-duplex tradicional: o usuário espera o TTS terminar antes de falar. Útil em ambientes ruidosos onde o falso-positivo de VAD seria mais incômodo que esperar.
7 — MUST: error surface
Erros de voz que cheguem ao usuário *evem*seguir specs/errors/user-facing-messages.kmd: texto humanizado em ptBR/enUS, botão "Ver detalhes" expandindo o erro técnico, ID único no formato <APP>-VOICE-<CODE>-<SEQ>. Mapping mínimo:
| Cenário | ID | Texto pt-BR |
|---|---|---|
| Permissão de mic negada | <APP>-VOICE-MIC-001 |
"Permita o acesso ao microfone para usar comandos de voz." |
| Modelo de hot-word não encontrado | <APP>-VOICE-MODEL-001 |
"A palavra '\(hot' não tem modelo no detector \)backend." |
| Endpoint de voz indisponível | <APP>-VOICE-NET-001 |
"Servidor de voz indisponível. Tente novamente." |
| Detector backend falhou na inicialização | <APP>-VOICE-INIT-001 |
"Não foi possível ativar o detector $backend." |
8 — Observability
KodeApp.metrics()deve emitir três counters bumping em wake-event:voice.wake.detected,voice.wake.false_positive(registrado quando o usuário cancela explicitamente o pipeline pós-wake),voice.wake.error.- Latency histograms (per RFC-001 §performance):
voice.wake.detect_ms,voice.stt.first_token_ms,voice.tts.first_audio_ms. - *ão*emitir métricas que vazem o conteúdo capturado (transcrição,
texto post-wake) — apenas counters + latência.
9 — Adoption checklist (per app)
Quando um app Koder ganha mic capability, os pull requests *evem*incluir:
- [ ] Importa
KodeVoiceSettingsTiledokoder_kitna tela de Settings - [ ] Toggles aparecem na ordem da §1
- [ ] Em fresh install, todos os defaults da §2 são respeitados
- [ ] Ring buffer pré-wake satisfaz o contrato da §4 (nunca leaves device)
- [ ]
debugRecordtoggle não aparece em release builds - [ ] Mensagens de erro seguem a tabela da §7
CI gate: koder-relay status-style probe não cobre voice; falar com /k-test <module> quando o tile + adoption sweep landar (KSTACK-89).
See also — sibling specs
Voice (este spec) cobre *ake-word + Talk Mode* ouvir o usuário ativamente. Para áudio genérico (memos, podcast, attachments, ringtones) o spec separado é specs/media/audio.kmd. Mic permission do SO é compartilhada, mas Voice e Audio têm UX, toggles em Settings e contratos próprios.
Sibling spec mais amplo: specs/media/ — image, video, audio e document.
Non-normative — referências
- RFC:
services/ai/voice/docs/rfcs/RFC-001-wake-word-talk-mode.md - Sub
tickets: KSTACK45 (openWakeWord), KSTACK46 (Porcupine), KSTACK47(streaming STT), KSTACK
48 (streaming TTS), KSTACK49 (Flutter native plugin), KSTACK50 (este spec), KSTACK51 (latency benchmark gate) - Implementation surface:
engines/sdk/koder_kit/lib/src/voice/(a criar — esta surface ainda não existe; landa em KSTACK-89 junto com a adoção apps-side)