codex-voice-steer provides cxv, a local voice bridge for steering Codex from a microphone, wake word, typed text, or test audio.
It is built for a simple premise: keep the bridge local, inspectable, and user-owned. cxv listens, turns finalized speech into Codex input, shows a compact foreground status stream, and leaves the actual Codex work to Codex.
- A manually launched local CLI for voice-to-Codex sessions.
- A foreground interactive UI plus a small background daemon, both controlled by
cxv. - A wake-word, VAD, and STT pipeline using OpenWakeWord, Silero VAD, and MacParakeet by default.
- A typed route with
cxv textandcxv tttthat uses the same delivery path as finalized speech. - A project that is intentionally compatible with
msdfor spoken Codex responses.
What it is not: an autostart service, launchd setup, container, VM, built-in TTS daemon, Hermes router, Codex patch, or hosted voice product.
cxv has three everyday surfaces:
cxv # start the foreground listener/TUI
cxv text "status?" # send typed input through the voice route
cxv --no-start statusThe daemon is just cxv serve running in the background. Use cxv up and cxv down when you want to manage it explicitly.
cxv up
cxv status
cxv downFor low-latency voice turns, the biggest practical lever is usually the Codex turn settings, not the audio pipeline:
cxv --fast --effort minimal
cxv --fast --effort low text "check the current repo status"Start by cloning the repo and checking that the project runs from the checkout:
git clone https://github.com/1Pio/codex-voice-steer.git
cd codex-voice-steer
uv run --extra audio --extra wake cxv --helpFor regular use, install the cxv command from the checkout so it is available on PATH:
uv tool install --editable ".[audio,wake]"
cxv --helpIf cxv is not found after installation, let uv update your shell path and open a new terminal:
uv tool update-shellThen create the default config:
cxv config init
cxv config editOptional, but recommended for spoken Codex responses: install msd from its own repository and confirm that msd say works before enabling the MSD-aware cxv agent.
Then check local readiness:
cxv doctor
cxv audio devices
cxv wake test-audio /path/to/scarlett.wavcxv doctor treats MSD as optional unless you explicitly configure it as required.
The foreground UI is designed to show the useful parts of a voice turn without flooding the terminal:
15:37:33 wake detected
15:38:10 user: Scarlet?
15:38:10 sent: turn/start
15:38:16 codex action: command: /bin/zsh -lc "sed -n '1,220p' README.md"
15:38:19 codex msd: --text 'I will look at the screen now.' --instruct 'brief, warm, fast'
15:38:30 codex: Done.
15:38:30 turn completed: 019e...
By default, user-facing labels such as user:, codex msd:, and codex: are bold. Operational labels such as sent: and turn completed: stay plain.
timestamp_opacity controls the leading timestamps. secondary_status_opacity controls the whole foreground status line after the timestamp for every non-primary line, such as sent: turn/start, codex action: ..., and turn completed: ....
CXV saves the Codex thread/session returned by codex app-server and resumes it for future voice and text turns.
cxv session status
cxv session new
cxv session new --forcecxv session status shows the saved thread, saved session id, effective resume target, relevant Codex config, and the current delivery behavior. cxv session new starts a fresh Codex thread immediately and saves it for future turns. If a turn is active, it refuses by default; pass --force to interrupt the active turn and replace the saved session.
If codex.thread_id or codex.resume_thread_id is set in config, CXV refuses session new because that pinned config would override the saved session. Unset the pinned key first:
cxv config unset codex.thread_id
cxv config unset codex.resume_thread_idUseful UI settings:
[ui]
timestamp_opacity = 0.45
secondary_status_opacity = 0.7
bold_labels = true
show_codex_tool_traces = true
show_codex_msd_traces = true
show_codex_final_answers = true
max_codex_action_lines = 1
max_codex_msd_lines = 40
max_codex_answer_lines = 200
visible_events = ["wake_detected", "stt_final", "user_final", "sent", "codex_tool_started", "codex_msd_started", "codex_final_answer", "auto_compact_started", "auto_compact_completed", "auto_compact_failed", "turn_completed", "voice_error"]
hidden_events = []visible_events is an allow-list when non-empty. hidden_events suppresses specific events. The same surface can be adjusted per invocation:
cxv --timestamp-opacity 0.45 --secondary-status-opacity 0.7 --show-events wake_detected,user_final,sent,codex_msd_started,codex_final_answer,auto_compact_started,auto_compact_completed,turn_completed
cxv --plain-labelsmsd is a recommended companion for cxv, especially if you want Codex to speak acknowledgements, important status updates, and final results. MSD is a local MLX speech daemon for fast text-to-speech on macOS: it can speak short messages with msd say, render audio files with msd render, keep a warm local model available, and let Codex use voice without turning cxv itself into a TTS server. That matters when you want a voice-first workflow where important Codex output is heard immediately, not only printed in a terminal.
It is not required by default. cxv should work as a voice-to-text-to-Codex bridge without MSD installed, and upstream defaults keep it optional:
[instructions.msd]
enabled = false
require_msd_on_path = falseWhen you do want spoken Codex responses, first install and configure msd from its own repository, confirm that msd say works in your shell, then opt cxv into the MSD-aware Codex agent:
cxv agents install msd
cxv config set codex.agent cxv-voice-msd
cxv config set instructions.msd.enabled trueOnly set instructions.msd.require_msd_on_path = true when you intentionally want cxv doctor to fail if msd is missing.
When Codex uses msd say, the interactive UI renders that action as codex msd: and shows only the arguments after the exact msd say command. This keeps spoken-response text readable while hiding shell wrappers such as /bin/zsh -lc.
For repeatable checks, use files or explicit loopback input. Speaker playback into the microphone is not a reliable receipt.
cxv audio devices
cxv config set audio.device "Loopback Input"
cxv wake test-audio /path/to/scarlett.wav
cxv voice test-audio /path/to/full-turn.wav
cxv voice test-audio /path/to/full-turn.wav --sendcxv voice test-audio --send runs the full route and sends the finalized transcript to Codex.
Normal cxv usage does not require collecting wake samples, generating synthetic negatives, or training a new wake model. The shipped Scarlett model is already bundled for the default wake word. This section is only for users who want to inspect, evaluate, or train their own compatible OpenWakeWord model, similar to how the bundled Scarlett model was produced.
Wake sample collection uses real microphone takes and keeps runtime source separate from training/evaluation tooling:
cxv wake samples init ./scarlett-samples
cxv wake samples session ./scarlett-samples --label positive --preset scarlett
cxv wake samples session ./scarlett-samples --label negative --prompt starlet --tag hard-negative
cxv wake samples session ./scarlett-samples --label noise --prompt keyboard --tag keyboard
cxv wake samples list ./scarlett-samples
cxv wake samples score ./scarlett-samples --model models/wake/scarlett.onnx --threshold 0.5Good hard negatives include starlet, Charlotte, star lit, let, start it, ordinary speech, and natural near misses. Good noise samples include keyboard, fan, room noise, silence, and handling noise. Use --keep-weak for intentional silence or very quiet noise takes.
Synthetic negative augmentation is optional and stays separate from real microphone samples. It uses msd render when MSD is installed, normalizes output to 16 kHz mono PCM16 WAV, and writes metadata that cxv wake samples list can read.
cxv wake samples synthetic-msd "$HOME/Documents/cxv-wake-samples/scarlett-synthetic-negatives-v1" \
--prompts tools/prompts/wake-negative-hard.txt \
--prompts tools/prompts/wake-negative-normal.txt \
--tag synthetic-negative \
--voices Aiden,Ryan,Vivian,Serena,Dylan,Eric,Uncle_Fu \
--languages English,German,French,Spanish \
--instructs "neutral, clear, natural" "fast, casual" "quiet, close microphone" \
--count 500Synthetic prompts reject exact scarlett and scarlet tokens. The goal is to add realistic non-wake pressure, not to silently mix generated wake-word clips into the real dataset.
Runtime remains OpenWakeWord-based. LiveKit tooling is used only for external training/evaluation support, not as a runtime dependency.
The bundled Scarlett model lives at:
models/wake/scarlett.onnx
src/codex_voice_steer/resources/wake/scarlett.onnx
See models/wake/README.md and docs/wakeword-livekit-training.md for model receipts, training notes, and current limitations.
cxv --help
cxv config show
cxv agents list
cxv agents install slim
cxv agents install msd
cxv models
cxv doctorUse cxv down to stop the daemon cleanly. Use cxv --no-start status when you only want to inspect state without starting anything.