Voice — Wake-word + Talk Mode (settings + privacy)

mandatory

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 via KodeVoiceSettingsTile em engines/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:

  1. *oz ativa*(voice.enabled)
  2. *odo Talk*(voice.talkMode) — mostrado apenas quando

    voice.enabled = true

  3. *alavra de ativação*(voice.hotWord) — input com sugestões
  4. *etector*(voice.backend) — dropdown
  5. *arge-in*(voice.bargeIn) — visível apenas em Talk Mode
  6. *ravar áudio para depuração*(voice.debugRecord) — visível

    apenas 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* Privacybydefault; sem opt-in nenhuma feature de voz roda
voice.talkMode *FF* Exige voice.enabled = true; UI *eve*desabilitar (greyed-out) quando enabled é false
voice.hotWord "hey kode" Lowercase ASCII; pode ser sobrescrito por hotwords alternativos (PT-BR: "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.ErrModelMissing e exibir a mensagem padronizada per specs/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/voice antes 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.isDebugBuild no koder_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. Privacybydefault 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 KodeVoiceSettingsTile do koder_kit na 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)
  • [ ] debugRecord toggle 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
  • Subtickets: KSTACK45 (openWakeWord), KSTACK46 (Porcupine), KSTACK47

    (streaming STT), KSTACK48 (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)

Source: ../home/koder/dev/koder/meta/docs/stack/specs/voice/wake-word.kmd