S18 · Microphone & Speaker Management

Declared state: complete for now.

This is the repository plan and evidence, not a live health reading. A task marked done may still depend on credentials, devices, deployment or a newer acceptance test.

Dependencies

No dependencies are declared in the catalog.

Tasks and gates

18.F1 · Watchdog for stale streams on device change. *gate:* `test_audio_watchdog.py`

Tier F · done · Verification: hermetic-test

Gate: `test_audio_watchdog.py`

  • unverified-import · undated · docs/SYSTEMS-DETAILS.md:2300

18.F2 · Output routing and switching. *gate:* `test_audio_output_switch.py`, `test_audio_route.py`

Tier F · done · Verification: hermetic-test

Gate: `test_audio_output_switch.py`, `test_audio_route.py`

  • unverified-import · undated · docs/SYSTEMS-DETAILS.md:2301

18.F3 · VAD + barge-in. *gate:* `test_phase1_vad_bargein.py`

Tier F · done · Verification: hermetic-test

Gate: `test_phase1_vad_bargein.py`

  • unverified-import · undated · docs/SYSTEMS-DETAILS.md:2302

18.F4 · The echo path had two causes; both fixed (L3c). *gate:* `test_aec.py`

Tier F · done · Verification: hermetic-test

Gate: `test_aec.py`

  • unverified-import · undated · docs/SYSTEMS-DETAILS.md:2303

18.R1 · Input level normalisation across devices so the gate threshold means the same thing on

Tier R · open · Verification: exempt-test

Gate: `test_voice_io.py` extended.

    18.R2 · Room acoustics adaptation (near-field vs across-room) feeding the speaker gate.

    Tier R · open · Verification: planned-test

    Gate: new `test_speaker_verdict.py` - planned gate. `test_speaker_verdict.py` [distance]

      18.R3 · Speaker health self-check — a periodic inaudible probe confirms output actually plays.

      Tier R · open · Verification: hermetic-test

      Gate: `test_uptime_watch.py` extended.

        18.E1 · A day of AirPods connect/disconnect churn with zero deaf periods and zero echo.

        Tier E · open · Verification: unregistered-test

        Gate: `edge_readiness.py` recorded run.

          18.F5 · **One long-running supervised native-device process, spoken to over IPC** — audio and camera device access moves behind a process boundary. Jev p=0.69, conf 0.58, and the audio watchdog is explicitly not a sufficient answer (P=0.21): a watchdog inside the process that dies cannot guarantee recovery; only a process boundary can.

          Tier F · open · Verification: planned-test

          Gate: new `test_device_supervisor.py` — killing the device process leaves the edge alive and hearing is restored without operator action.

            18.R4 · Local TTS is the default and the cloud path is opt-in: a normal reply never requires a cloud TTS key, and the outbound audio path is exercised with the network down (D-17, D-78).

            Tier R · in-progress · Verification: hermetic-test

            Gate: new `test_tts_local_default.py` — with the cloud provider key absent, a reply is synthesised locally; with it present the default is still local, and setting the cloud path is an explicit opt-in rather than a fallback that fires on its own.

            • hermetic-green · undated · bench/test_tts_local_default.py
            • hermetic-green · undated · bench/test_tts_local_default.py

            18.R5 · The wake phrase stays on-device and that is asserted: a wake fires with all network egress blocked, using the openWakeWord path that is already local (D-19). The two dead settings that suggest otherwise — MUSHEGH_WAKE_WORD_ENGINE and MUSHEGH_PORCUPINE_ACCESS_KEY, still listed in .env.example but read by nothing since 2026-08-01 — are removed.

            Tier R · in-progress · Verification: hermetic-test

            Gate: new `test_wakeword_offline.py` — with outbound network disabled the wake phrase still fires and barge-in still halts playback, and neither dead setting is present in .env.example or readable from settings.

            • hermetic-green · undated · bench/test_wakeword_offline.py

            18.R6 · Per-language local voices, so local-first can return without costing a language: a language-to-voice map selects the right Piper model per turn, `match` gains a local implementation, and the ElevenLabs opt-in becomes a quality choice rather than the only way to speak French, German, Ukrainian or Russian (D-78).

            Tier R · open · Verification: planned-test

            Gate: new `test_per_language_voice.py` — each of the six supported languages resolves to a local voice that is present on disk, `match` picks the voice for the language just spoken without a build-time pin, a language with no local voice degrades to the configured fallback rather than speaking English into a French sentence, and no code path can select a voice that was not asked for.

              18.R7 · The ear is chosen per turn, and the choice is explainable: a language the fast ear cannot hear moves the NEXT turn to the local one, learned from the transcript rather than predicted from the audio, because prediction was measured and costs more than it saves (D-78).

              Tier R · in-progress · Verification: hermetic-test

              Gate: new `test_stt_by_language.py` — the ear is chosen per language, a one-word turn cannot swap it, a missing local ear degrades explicitly rather than refusing to listen, the first missed turn is counted rather than commented, and the shipped default is unchanged.

              • hermetic-green · undated · bench/test_stt_by_language.py

              18.R8 · Put the 18.R7 router in the live turn path: the pipecat frame loop consults it per utterance, the local ear is built lazily on the first turn that needs it, and the first-missed-turn counter is logged so the residual cost is visible in production rather than in a test.

              Tier R · open · Verification: planned-test

              Gate: new `test_stt_routing_wired.py` — a frame-level test drives a Ukrainian turn through the real processor and asserts the local service is used from the second utterance onward, the local engine is built at most once per process, the fast ear is not rebuilt when the language changes back, and a build failure for the local ear falls back to the primary rather than killing the pipeline. Requires a live-audio rig, so this gate is not hermetic until one exists.

                HUD screen contracts

                All subsystems · Source: docs/systems-plan.json.