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
- iPhone voice gateway · /iphone
- Microphone & Speaker Management · /systems/S18
All subsystems · Source: docs/systems-plan.json.