name: clankvox-dev
description: >-
Build, test, lint, run, and debug the clankvox Rust media plane and validate
it against the Clanky agent that drives it over IPC. Use when editing voice /
Go Live transport, DAVE, codecs, RTP/RTCP, playback pacing, or the stdin/stdout
IPC contract; when you need the logging knobs (RUST_LOG, AUDIO_DEBUG); when
you need the agent-side smoke names (clankvox:setup, smoke:voice:*); or when
a change spans the clankvox <-> clanky-agent seam and both sides must move
together.
clankvox Development
clankvox is Clanky's standalone Rust media plane — Discord voice and Go Live
transport (realtime sockets, RTP/RTCP, Opus, DAVE, codecs, playback pacing). It
runs as a subprocess of the Node agent (../clanky-agent) and speaks a
stdin/stdout IPC contract to it. It carries transport truth only; product policy
lives in the agent.
Read docs/architecture.md for the process model and module map, and CONTRIBUTING.md for the human-contributor PR rules. This skill is the agent runbook: how to build, test, log, validate, and where each change lands across the two repos.
Build
From this repo, plain cargo works — .cargo/config.toml force-pins the build
env (OPUS_STATIC=1, OPUS_NO_PKG=1, CMAKE_POLICY_VERSION_MINIMUM=3.5; the
last lets cmake >= 4 build the bundled libopus in audiopus_sys):
cargo build --release
From the sibling agent repo, build clankvox through Clanky's wrapper — it is
idempotent (a no-op once the release binary exists; --force rebuilds), installs
the Rust toolchain if missing, and builds --locked:
cd ../clanky-agent
pnpm clankvox:setup # build the release binary
pnpm clankvox:setup --force # force a rebuild
The agent locates the crate via CLANKY_CLANKVOX_DIR (source dir; defaults to a
sibling clone) and can skip the build with CLANKY_CLANKVOX_BIN (path to a
prebuilt binary).
Test, format, lint
cargo test --locked --all-features
cargo fmt --all --check
cargo clippy --locked --all-targets --all-features -- -D warnings
clippy::pedantic is enabled (see Cargo.toml [lints.clippy]), and CI
denies warnings after applying the repo's explicit allow-list.
Agent-side smokes
The Rust <-> Node boundary is exercised from ../clanky-agent, not from here.
After a transport or IPC change, run the relevant smokes there:
cd ../clanky-agent
pnpm smoke:clankvox:ipc # IPC framing (tag byte + length) round-trip
pnpm smoke:voice:config # voice config / setup contract
pnpm smoke:voice:control # control-plane commands (connect, publish, pause)
pnpm smoke:voice:fault # fault / disconnect / recovery handling
pnpm smoke:voice:realtime # realtime capture/playback path
Prefer these script names over the file paths behind them — the paths rot, the scripts are stable.
Logging
clankvox writes structured tracing logs to stderr; the IPC channel stays
clean (line-delimited JSON on stdin, framed tag-byte + length messages on
stdout). The default filter (main.rs:125) is:
info,davey=warn,davey::cryptor::frame_processors=off
Override with the standard env knobs:
RUST_LOG=debug— transport detail (WS opcodes, UDP, DAVE handoffs)AUDIO_DEBUG=1— extra audio-path IPC logging (main.rs:136)
Trust subprocess telemetry, not the agent's view, for floor state: Clanky can look logically done while clankvox still has buffered playback draining.
High-Value Edit Locations
Task-oriented map. Rust files are in this repo; TypeScript files are in
../clanky-agent.
| Changing… | Rust (this repo) | Agent side (../clanky-agent) |
|---|---|---|
| voice/session connect | src/connection_supervisor.rs, src/voice_conn.rs | agent/lib/voice/supervisor.ts |
| inbound speaking/audio capture | src/capture_supervisor.rs, src/voice_conn/udp_rx.rs | — |
| TTS/music playback | src/playback_supervisor.rs, src/audio_pipeline.rs, src/music.rs | agent/lib/voice/control.ts |
| media subprocess pipelines | src/process_unix.rs, src/music.rs, src/stream_publish.rs | — |
| Go Live watch transport | src/voice_conn.rs, src/video.rs, src/capture_supervisor.rs | agent/lib/voice/discordStreamDiscovery.ts |
| Go Live publish transport | src/stream_publish.rs, src/voice_conn.rs, src/connection_supervisor.rs | agent/lib/voice/discordStreamDiscovery.ts |
| IPC contract | src/ipc.rs, src/ipc_protocol.rs, src/ipc_router.rs | agent/lib/voice/clankvoxIpcClient.ts |
The full module map is docs/architecture.md.
Coordination with the agent
clankvox almost never changes alone. A new transport event or command is a
two-repo change — land both sides together:
- Rust IPC message contracts:
src/ipc.rs,src/ipc_protocol.rs - TS IPC client:
../clanky-agent/agent/lib/voice/clankvoxIpcClient.ts - Stream discovery / control plane:
../clanky-agent/agent/lib/voice/discordStreamDiscovery.ts - Voice orchestration entry point:
../clanky-agent/agent/channels/voice.ts(attachVoiceRuntime()), backed byagent/lib/voice/supervisor.tsandagent/lib/voice/control.ts
The agent repo is ../clanky-agent — its voice code lives under
agent/lib/voice/ and agent/channels/, and its build/test entry points are the
clankvox:setup and smoke:* scripts above. The repo was renamed and its voice
code relocated over time, so if an external doc points you at an older sibling
name, a src/voice/ path under an agents/ tree, a standalone Discord-voice
orchestration module, or a voice:* build/test script, treat it as stale and
resolve against the paths and scripts here instead.
Manual validation
Audio changes
cargo testcd ../clanky-agent && pnpm smoke:voice:realtime(addsmoke:voice:controlif you touched connect/publish control).- In a live voice session, confirm:
- join still works
- inbound capture still arrives
- TTS still drains
- music still starts, pauses, and resumes cleanly
Go Live changes
cargo testcd ../clanky-agent && pnpm smoke:voice:control(exercises stream watch/publish lifecycle) andpnpm smoke:voice:faultfor disconnect paths.- Validate the role you changed (
stream_watchvsstream_publish) — a fix for one role is not automatically correct for the other. - Confirm the right transport-state events still emit over IPC.
Sharp edges
- DAVE transition handling is protocol-sensitive; small changes break encryption
handoffs.
daveyreportsUnencryptedWhenPassthroughDisabledfor Go Live video frames — that path is a known open limitation (see docs/go-live.md, "DAVE Video Decrypt"). - Stream-server behavior is role-specific.
voice,stream_watch, andstream_publishare separate legs with different lifecycle expectations; they share protocol code but not identical behavior. - Sender and receiver video paths share depacketization/packetization code but diverge on keyframe and DAVE handling.
- Discord's raw UDP path (
protocol: "udp") does not honour PLI/FIR keyframe requests; clankvox sends them best-effort only. Do not assume on-demand keyframes are available.
