Found while pinning the reply-playback guarantee (#11, PR test/11-pin-reply-playback).
What
The output_device config key is honored by the file pipeline but silently ignored by the always-on live driver.
pipeline.py:933 reads it: output_name = cfg.get("output_device") or None (the sounddevice audio.play_wav path).
live_driver.py main() loads cfg = pipeline.load_config() but then passes only args.output_device (the -o/--output-device CLI flag, default None) to run_turn. It never consults cfg["output_device"].
So a user who sets output_device in config.local.json (exactly as config.local.example.json:6 shows: "output_device": "desktop speakers") and runs the live driver without -o gets the ALSA default device, silently. Their configured speaker choice is dropped.
Why a one-line fix is wrong
The two playback paths address devices differently, so args.output_device or cfg.get("output_device") would be a bug:
- The config value is a case-insensitive name substring resolved against sounddevice device enumeration (
pipeline.py:117 comment, audio.py). Example: "desktop speakers".
live_driver._play_wav shells aplay -D <device>, which wants an ALSA PCM string (e.g. plughw:CARD=PowerConf,DEV=0), not a substring.
Passing a sounddevice substring to aplay -D would fail. So honoring the config key on the aplay path needs a design decision, one of:
- A separate config key for the ALSA/aplay path (e.g.
output_device_alsa), the live driver reads that.
- Resolve the substring to an ALSA PCM inside
_play_wav before calling aplay.
- Document that
output_device is sounddevice-only and the live driver is CLI-flag-only (and drop the dead config read expectation).
Option 1 or 3 is likely simplest. Needs a call on which.
Not blocking #11
#11's acceptance criteria (a playback helper, the loop plays the reply, configurable output device with a default via -o, speak's contract unchanged) are met and now test-pinned. This is the follow-on consistency gap, split out so it gets its own decision instead of a blind wiring.
Found while pinning the reply-playback guarantee (#11, PR test/11-pin-reply-playback).
What
The
output_deviceconfig key is honored by the file pipeline but silently ignored by the always-on live driver.pipeline.py:933reads it:output_name = cfg.get("output_device") or None(the sounddeviceaudio.play_wavpath).live_driver.py main()loadscfg = pipeline.load_config()but then passes onlyargs.output_device(the-o/--output-deviceCLI flag, defaultNone) torun_turn. It never consultscfg["output_device"].So a user who sets
output_devicein config.local.json (exactly asconfig.local.example.json:6shows:"output_device": "desktop speakers") and runs the live driver without-ogets the ALSA default device, silently. Their configured speaker choice is dropped.Why a one-line fix is wrong
The two playback paths address devices differently, so
args.output_device or cfg.get("output_device")would be a bug:pipeline.py:117comment,audio.py). Example:"desktop speakers".live_driver._play_wavshellsaplay -D <device>, which wants an ALSA PCM string (e.g.plughw:CARD=PowerConf,DEV=0), not a substring.Passing a sounddevice substring to
aplay -Dwould fail. So honoring the config key on the aplay path needs a design decision, one of:output_device_alsa), the live driver reads that._play_wavbefore calling aplay.output_deviceis sounddevice-only and the live driver is CLI-flag-only (and drop the dead config read expectation).Option 1 or 3 is likely simplest. Needs a call on which.
Not blocking #11
#11's acceptance criteria (a playback helper, the loop plays the reply, configurable output device with a default via
-o, speak's contract unchanged) are met and now test-pinned. This is the follow-on consistency gap, split out so it gets its own decision instead of a blind wiring.