Skip to content

Voice

CLI overview · Control plane & integrations · Full voice guide

Use the voice family to operate the reference speech topology without confusing proxy ports with model ownership. The declared topology owner runs the STT and TTS model serves. Companion Node may own the realtime proxy and loopback forwarders, but it is model-free by default.

Choose a workflow

Goal Start here Then
Operate one co-located managed stack voice up --dry-run Apply with --confirm; voice down stops it in reverse dependency order.
Inspect audio readiness voice audio status Read a bounded tail with voice audio logs.
Change STT/TTS lifecycle voice audio up --dry-run Apply with --confirm; down stops native processes and stops/removes managed containers.
Run the realtime proxy interactively voice proxy run Interrupt the foreground process when finished.
Manage the background proxy voice proxy status Preview up, down, or restart, then apply with --confirm.
Forward Mini-local audio ports voice proxy bridge --dry-run Run the bridge in the foreground after reviewing both routes.
Measure a voice candidate voice benchmark Retain structured evidence with --evidence-out.
Qualify an STT model voice corpus prepare Validate it, then run voice benchmark --scope stt.
Inspect configuration overlays voice profiles list Validate one resolved overlay with voice profiles validate.
Prepare an optional sidecar voice sidecar validate Render a host command or Compose skeleton; neither command launches it.

Command map

Operate one co-located managed stack

Command Purpose
voice up Start managed STT, TTS, then the managed realtime proxy.
voice down Stop the managed realtime proxy, then managed STT/TTS.

Operate topology-owned audio serves

Command Purpose
voice audio status Read bounded STT/TTS readiness from the declared owner.
voice audio logs Read a bounded log tail for each owned audio serve.
voice audio up Preview or start managed/native STT and TTS serves.
voice audio down Preview or stop native STT/TTS processes and stop/remove managed containers.

Operate the Mini realtime layer

Command Purpose
voice proxy run Run the authenticated realtime WebSocket proxy in the foreground.
voice proxy status Inspect owned background-proxy process state.
voice proxy logs Read a bounded background-proxy log tail.
voice proxy up Preview or start the owned background proxy.
voice proxy down Preview or stop the owned background proxy.
voice proxy restart Preview or restart the same owned proxy instance.
voice proxy bridge Forward Mini-local STT/TTS ports to Dark in the foreground.

Evaluate and inspect configuration

Command Purpose
voice benchmark Replay one end-to-end voice session against resolved endpoints.
voice corpus prepare Build the deterministic 24-human/6-synthetic English STT corpus.
voice corpus validate Fail closed on malformed JSONL, audio metadata, paths, or hashes.
voice profiles list List named overlays without contacting a service.
voice profiles validate Validate one fully merged profile offline.

Prepare the optional sidecar

Command Purpose
voice sidecar validate Validate sidecar URLs, models, image, and secret references.
voice sidecar command Render shell-safe host argv without executing it.
voice sidecar compose Render a loopback-bound Compose service without writing or running it.

Co-located stack lifecycle

voice up and voice down are deliberately narrow aggregates for one managed host. The STT, TTS, and realtime-proxy tables must all use lifecycle = "managed", reference the same serves manifest, and belong to its voice group. STT/TTS use host-relative 127.0.0.1 URLs for host callers, while the managed proxy joins the audio-owned anvil-voice Docker network and uses the stt / tts service names. The command refuses native, external, incomplete, or split-host configurations before lifecycle I/O.

Preview the complete dependency order before applying:

anvil-serving voice up --config ~/.anvil-serving/voice.toml --dry-run
anvil-serving voice up --config ~/.anvil-serving/voice.toml --confirm
anvil-serving voice down --config ~/.anvil-serving/voice.toml --dry-run
anvil-serving voice down --config ~/.anvil-serving/voice.toml --confirm

Bring-up is STT/TTS first, then the proxy. A failed audio bring-up skips the proxy. Tear-down is the reverse; a failed proxy stop leaves audio running so a still-live proxy does not lose its dependencies. Both commands emit one combined result covering the audio and proxy phases.

The aggregate is manifest-owned and does not require topology resolution. If topology options are supplied explicitly, the command verifies that STT, TTS, and the realtime proxy resolve to one Docker owner before importing the handler.

The reference Mini/Dark topology is intentionally split and must keep using the explicit voice audio ... and voice proxy ... commands below. The aggregate does not compose separate controller operations across owners.

Audio lifecycle

Every audio lifecycle command requires topology because loopback is host-relative and STT/TTS are model workloads. Inspect before changing state:

anvil-serving voice audio status --topology ~/.anvil-serving/operator-topology.toml --profile dark-audio
anvil-serving voice audio logs --topology ~/.anvil-serving/operator-topology.toml --profile dark-audio --tail 50

status uses a three-second readiness deadline per managed serve by default; --ready-timeout accepts 0.1 through 60 seconds. logs defaults to 200 lines per serve and accepts 1 through 5000. Native file reads are capped at 1 MiB.

Preview and apply use the same owner, runtime, and lifecycle resolution:

anvil-serving voice audio up --topology ~/.anvil-serving/operator-topology.toml --profile dark-audio --dry-run
anvil-serving voice audio up --topology ~/.anvil-serving/operator-topology.toml --profile dark-audio --timeout-seconds 300 --confirm
anvil-serving voice audio down --topology ~/.anvil-serving/operator-topology.toml --profile dark-audio --dry-run
anvil-serving voice audio down --topology ~/.anvil-serving/operator-topology.toml --profile dark-audio --confirm

The command refuses split STT/TTS ownership, local execution against a remote owner, and lifecycle/runtime mismatches. Managed endpoints require Docker; native endpoints require a native runtime. External endpoints are reported and left untouched. Mini runs model workloads only when topology explicitly marks an experimental same-host mode such as mini-audio.

Audio up and down share an overall 300-second lifecycle deadline. Override it with --timeout-seconds from 1 through 7200 seconds; the deadline covers both STT and TTS subprocess work rather than resetting for each serve.

Realtime proxy

The reference proxy profile keeps models on Dark while the realtime session layer runs on Mini:

anvil-serving voice proxy run --topology ~/.anvil-serving/operator-topology.toml --profile mini-dark-audio-proxy

run resolves the Mini owner, verifies that the manifest listener matches the topology endpoint, and probes the router, STT, and TTS endpoints before binding. It remains in the foreground until interrupted. A candidate overlay is scoped to that process:

anvil-serving voice proxy run --topology ~/.anvil-serving/operator-topology.toml --profile mini-dark-audio-proxy --candidate voice-candidate --candidate-overlay candidate.toml

For managed background operation, inspect and preview first:

anvil-serving voice proxy status --topology ~/.anvil-serving/operator-topology.toml --profile mini-dark-audio-proxy
anvil-serving voice proxy logs --topology ~/.anvil-serving/operator-topology.toml --profile mini-dark-audio-proxy --tail 50
anvil-serving voice proxy up --topology ~/.anvil-serving/operator-topology.toml --profile mini-dark-audio-proxy --dry-run
anvil-serving voice proxy up --topology ~/.anvil-serving/operator-topology.toml --profile mini-dark-audio-proxy --confirm
anvil-serving voice proxy restart --topology ~/.anvil-serving/operator-topology.toml --profile mini-dark-audio-proxy --confirm
anvil-serving voice proxy down --topology ~/.anvil-serving/operator-topology.toml --profile mini-dark-audio-proxy --confirm

The default owned records are ~/.anvil-serving/run/voice-proxy.pid and voice-proxy.log. Status and lifecycle commands reject stale, reused, or foreign PID state. Logs are a bounded tail rather than a follow stream.

The bridge is a separate foreground process. Mini-local ports 127.0.0.1:30110 and 127.0.0.1:30111 forward to Dark; they are not local model serves:

anvil-serving voice proxy bridge --topology ~/.anvil-serving/operator-topology.toml --profile mini-dark-audio-proxy --dry-run
anvil-serving voice proxy bridge --topology ~/.anvil-serving/operator-topology.toml --profile mini-dark-audio-proxy

Topology supplies listen ports and Dark target addresses. The listener must remain 127.0.0.1; localhost, wildcard targets, and public target IPs are rejected.

Benchmark

Benchmark the resolved manifest or one already-loaded candidate:

anvil-serving voice benchmark --profile mini-dark-audio-proxy --candidate current-fast
anvil-serving voice benchmark --candidate-base-url http://127.0.0.1:30001/v1 --candidate-model MODEL --evidence-out artifacts/voice/candidate.json

--candidate-overlay applies after the selected profile. A direct candidate URL and model must be supplied together; the optional token flag names an environment variable. These overrides never rewrite the manifest.

For a speech baseline, pass a local regular PCM16, mono, 16-kHz RIFF WAV and its verbatim transcript together. The file must be no longer than 30 seconds; a symbolic link in the input-file path's final component, non-regular files, compressed or malformed WAVs, stereo audio, other sample rates, and any header/data size mismatch are rejected before the benchmark contacts an endpoint:

anvil-serving voice benchmark --scope end-to-end \
  --input-wav artifacts/voice/utterance.wav \
  --reference-text "the recorded utterance, exactly as spoken" \
  --evidence-out artifacts/voice/speech-baseline.json

The evidence retains the input kind, source-WAV and PCM byte counts and SHA-256 values, audio format, duration, and transcript SHA-256. A valid WAV only establishes container properties: the input is labelled supplied-content-unverified, so retain corpus provenance separately before interpreting it as human speech. Supplying a WAV still measures the current serialized STT → LLM → TTS replay. Its structured measurement_scope records serialized-stage-replay, first-nonempty-yielded-TTS-chunk, realtime=false, and acoustic_playback=false. ttfa_ms is null when TTS yields no audio, and otherwise records the first nonempty chunk yielded after any stage-side buffering; it is not a first-response-byte or audible-playback measurement. Without these two options, the command retains compatibility by using a 220-Hz synthetic tone. Its input identity says synthetic-tone-not-speech and not-qualifying; it is only a wire-path smoke, never speech-baseline evidence.

The benchmark records resolved model and endpoint identity with its end-to-end STT, router, and TTS metrics. Evidence output is restricted to the workspace or configured evidence root. Unreachable dependencies return nonzero and do not create a successful measurement record.

STT corpus

Prepare the corpus into an evidence workspace:

anvil-serving voice corpus prepare --config ~/.anvil-serving/voice.audio-dark.toml --out artifacts/stt/corpus
anvil-serving voice corpus validate --manifest artifacts/stt/corpus/manifest.jsonl --expected-cases 30

The versioned JSONL manifest resolves audio paths against itself and accepts only 16-kHz mono WAV/FLAC with matching SHA-256. Run a candidate through an STT-only overlay:

anvil-serving voice benchmark --scope stt \
  --config ~/.anvil-serving/voice.audio-dark.toml \
  --corpus artifacts/stt/corpus/manifest.jsonl \
  --repetitions 3 --concurrency 1 \
  --stt-candidate-overlay examples/primary-node/stt-experiments/overlays/nemotron35-asr.toml \
  --evidence-out artifacts/stt/nemotron-sequential.json

Use a separate --repetitions 1 --concurrency 4 lane. --auto-language-probes 6 records six additional unconditioned human requests; it does not qualify multilingual behavior. Evidence uses stt-benchmark-evidence/v1, writes atomically, and exits nonzero with complete=false if an expected request fails.

Profiles

Profiles are offline manifest overlays:

anvil-serving voice profiles list
anvil-serving voice profiles list --config ~/.anvil-serving/voice.toml
anvil-serving voice profiles validate --profile dark-audio
anvil-serving voice profiles validate --config ~/.anvil-serving/voice.toml --profile mini-dark-audio-proxy

list validates the base manifest and prints declared names. validate requires one name, merges that overlay, and checks the resolved schema. Neither verb resolves topology, probes an endpoint, starts a service, or changes the manifest.

Speech-to-speech sidecar

The sidecar helpers are offline renderers. Validate first:

anvil-serving voice sidecar validate --config examples/huggingface-speech-to-speech/openclaw-gateway.example.toml
anvil-serving voice sidecar validate --config examples/huggingface-speech-to-speech/openclaw-gateway.example.toml --json

Validation rejects inline secrets, credential-bearing URLs, unsafe container loopback, malformed service values, and endpoints that do not end in /v1 or /v1/realtime as required.

Render host argv or a Compose skeleton without launching anything:

anvil-serving voice sidecar command --config examples/huggingface-speech-to-speech/openclaw-gateway.example.toml
anvil-serving voice sidecar command --config examples/huggingface-speech-to-speech/openclaw-gateway.example.toml --with-auth --json
anvil-serving voice sidecar compose --config examples/huggingface-speech-to-speech/openclaw-gateway.example.toml
anvil-serving voice sidecar compose --config examples/huggingface-speech-to-speech/openclaw-gateway.example.toml --service-name voice-sidecar --with-auth

--with-auth emits only an environment-variable reference. It does not read or print the token. Compose output publishes the realtime port on 127.0.0.1, uses the container-specific router URL, and never invokes Docker or writes a file.