feat(core): compile-time voice feature gate (voice + audio_toolkit) (#4803) - #4833
Conversation
…inyhumansai#4803) Introduce a compile-time `voice` Cargo feature (default-ON) that pulls the now-optional `hound` (WAV I/O) and `lettre` (podcast email) dependencies. Default builds are byte-identical; slim builds drop the pair via `--no-default-features`. whisper-rs / cpal are intentionally left untouched (inference domain — future gate).
…ed stub (tinyhumansai#4803) Keep `pub mod voice;` always compiled as a facade: real submodules and the inference::voice re-exports are gated `#[cfg(feature = "voice")]`, and a new `#[cfg(not(feature = "voice"))] mod stub` mirrors the public surface that always-on / other-gated callers depend on (server, dictation_listener, streaming, reply_speech, cloud_transcribe, cli, create_stt_provider, effective_stt_provider, publish_ptt_transcript_committed) with no-op / None / disabled-error bodies. Callers need no per-call cfg; the disabled build catches any signature drift.
… behind voice (tinyhumansai#4803) Compile out the `audio_toolkit` module, its controller + voice-controller registration in core::all, and the three podcast agent tools + tool re-export when the `voice` feature is off. The voice CLI adapter stays registered so `openhuman voice` returns a clear disabled error via the facade stub. Add registry assertions covering both feature configs (controllers present when on, absent when off).
…ion (tinyhumansai#4803) Add a Compile-time domain gates section to AGENTS.md: the default-on `voice` feature, the facade/stub pathfinder pattern, the slim-profile `--no-default-features --features "<explicit list>"` convention, and the scope correction that whisper-rs / llama / cpal are NOT dropped by this gate (inference domain, future gate).
DoD for tinyhumansai#4803 requires CI to build the core with the gate OFF — the disabled build is the only thing that catches stub-facade signature drift. Adds a rust-feature-gate-smoke lane running `cargo check --no-default-features --features tokenjuice-treesitter`. Pathfinder lane; extend the --features list as sibling gates land.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d48ae12fd1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| rust-feature-gate-smoke: | ||
| name: Rust Feature-Gate Smoke (gates off) | ||
| needs: [changes] |
There was a problem hiding this comment.
Wire feature-gate smoke into the PR gate
In PRs where the repository rules only require the historical PR CI Gate check (the workflow comment says that name is kept for the ruleset), this new smoke job can fail without blocking merge because pr-ci-gate neither lists rust-feature-gate-smoke in its needs nor checks its result. That defeats the stated purpose of this lane: a future stub-signature drift in --no-default-features --features tokenjuice-treesitter could be red in CI while the required gate still passes.
Useful? React with 👍 / 👎.
Summary
voicefeature (default-ON) that gates thevoice+audio_toolkitdomains, composing with the runtimeDomainSet.voiceflag from feat(core): DomainSet on CoreBuilder + DomainRegistration filter seam (feature-gate prerequisite) #4796.hound+lettredeps.pub mod voice;stays compiled either way (real code under#[cfg(feature="voice")], no-op/disabled-error stubs when off), so the ~22 always-on / other-gated callers ofvoice::need no per-call#[cfg].Problem
#4795 wants one compile-time feature gate per subsystem family so slim harness builds drop code + deps; #4796 shipped the runtime
DomainSetaxis. This PR adds the compile-time half for voice. Two scope realities the issue under-stated, confirmed by a full sweep:whisper-rs/llamaare NOT dropped by this gate. They live in theinferencedomain (src/openhuman/inference/local/service/whisper_engine.rs);cpalis shared withaccessibility. Onlyhound(voice) andlettre(audio_toolkit) are exclusive to the gated domains. Whisper awaits a separate futureinferencegate. The issue-level DoD line claiming whisper drops is superseded (noted in AGENTS.md).voiceis widely consumed (credentials startup, channels reply-speech/STT, dictation websocket, socketio, meet_agent, desktop_companion) — ~22 call sites — so it cannot be a clean leaf removal.Solution
Cargo.toml:hound+lettre→optional = true;voice = ["dep:hound", "dep:lettre"];voiceadded todefault.src/openhuman/voice/mod.rs): real submodules + inference re-exports are#[cfg(feature="voice")]; newsrc/openhuman/voice/stub.rs(#[cfg(not(feature="voice"))]) re-exposes the exact caller surface (server,dictation_listener,streaming,reply_speech,cloud_transcribe,cli,create_stt_provider,effective_stt_provider,publish_ptt_transcript_committed) with no-op /None/ disabled-error bodies matching the real signatures.audio_toolkit: whole module#[cfg(feature="voice")]; its 3 podcast agent tools + registration + the voice controller/CLI registration insrc/core/all.rsare#[cfg]'d out when off.rust-feature-gate-smokelane runscargo check --no-default-features --features tokenjuice-treesitter— the disabled build is the only thing that catches stub-signature drift, so it must run in CI.fullmeta-feature):cargo build --no-default-features --features "<explicit gates>". Documented in AGENTS.md.When off: voice/audio controllers are unknown-method over
/rpcand absent from/schema, theaudio_generate_podcasttools are absent,openhuman voicereturns a disabled error.Submission Checklist
#[cfg]attrs, optional deps, acfg(not(feature="voice"))stub module not compiled in the default/coverage build, docs, CI yaml).diff-coverreports "no lines with coverage information"; the two new registration tests cover the on/off behavior. CI coverage-gate authoritative. — Diff coverage ≥ 80%N/A: compile-time build-config change, no user-facing feature row## Related—N/A: no matrix rows affectedCloses #NNNin the## RelatedsectionImpact
voiceon and is byte-identical.--no-default-featuresdrops the voice/audio domains +hound/lettre. Note: whisper/cpal are NOT dropped (inference-owned).{voice: off}but Meet/companion on, their speech paths hit the disabled stubs (transcription/synthesis no-op, not a crash). Acceptable for slim profiles; whether the Meet gate should implyvoiceis a follow-up.Related
DomainSetseam. Follow-up (per @M3gA-Mind on feat(core): runtime DomainSet composition axis — group-tagged registry + ambient filter (#4796) #4808): feat(core): converge tool gating with controller gating via per-Tool DomainGroup metadata #4821 tool/controller gating convergence.AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
feat/4803-voice-feature-gated48ae12fd17b2d07e23a26818a10e227d84681c9Validation Run
pnpm --filter openhuman-app format:checkpnpm typecheckcore::all::tests::voice_and_audio_controllers_registered_when_feature_on(default) +..._absent_when_feature_off(disabled),core::all50 passed,openhuman::voice/openhuman::audio_toolkitgreencargo fmt --checkclean · defaultcargo checkexit 0 · disabledcargo check --no-default-features --features tokenjuice-treesitterexit 0 ·cargo clippy --lib0 warnings in changed files (both configs)Validation Blocked
command:cargo test --lib(full)error:OOM on local machine (known)impact:full matrix runs in CI; locally scoped to voice/audio_toolkit/core::all/tools::ops — all green in both feature configsBehavior Changes
voicecompile-time gate; default unchanged