Follow users in voice
Usevoice.followUsers when you want the Discord voice bot to stay with one or more known Discord users instead of joining a fixed channel at startup or waiting for /vc join.
followUsersaccepts raw Discord user IDs anddiscord:<id>values. OpenClaw normalizes both forms before matching voice-state events.followUsersEnableddefaults totruewhenfollowUsersis configured. Set it tofalseto keep the saved list but stop automatic voice following.followUserscontrols voice residency only. It does not grant speaker access or owner authority; configurecommands.ownerAllowFromand guild or channel users and roles separately.- When a followed user joins an allowed voice channel, OpenClaw joins that channel. When the user moves, OpenClaw moves with them. When the active followed user disconnects, OpenClaw leaves.
- If multiple followed users are in the same guild and the active followed user leaves, OpenClaw moves to another tracked followed user’s channel before leaving the guild. If several followed users move at once, the latest observed voice-state event wins.
allowedChannelsstill applies. A followed user in a disallowed channel is ignored, and a follow-owned session moves to another followed user or leaves.- OpenClaw reconciles missed voice-state events on startup and at a bounded interval. Reconciliation samples configured guilds and caps REST lookups per run, so very large
followUserslists may take more than one interval to converge. - If Discord or an admin moves the bot while it is following a user, OpenClaw rebuilds the voice session and preserves follow ownership when the destination is allowed. If the bot is moved outside
allowedChannels, OpenClaw leaves and rejoins the configured target when one exists. - DAVE receive recovery may leave and rejoin the same channel after repeated decrypt failures. Follow-owned sessions keep their follow ownership through that recovery path, so a later followed-user disconnect still leaves the channel.
- Use
followUsersfor personal or operator setups where the bot should automatically be in voice when you are. - Use
autoJoinfor fixed rooms. AddwhenOccupied: truewhen the bot should be present only while humans are in that room; omit it for always-on voice presence. - Use
/vc joinfor one-off joins or rooms where automatic voice presence would be surprising.
- Voice receive logs show
discord voice: opus decoder: libopus-wasm. - Realtime playback encodes raw 48 kHz stereo PCM to Opus with the same bundled
libopus-wasmpackage before handing packets to@discordjs/voice. - File and provider-stream playback transcodes to raw 48 kHz stereo PCM with ffmpeg, then uses
libopus-wasmfor the Opus packet stream sent to Discord.
- Discord PCM capture is converted to a WAV temp file.
tools.media.audiohandles STT, for exampleopenai/gpt-4o-mini-transcribe.- Batch capture respects the largest applicable audio input limit, including configured model and CLI fallbacks. Oversized captures stop with a warning to speak a shorter segment; partial audio is not transcribed. This limit does not buffer or truncate direct realtime streams.
- The transcript is sent through Discord ingress and routing while the response LLM runs with a voice-output policy that hides the agent
ttstool and asks for returned text, because Discord voice owns final TTS playback. voice.model, when set, overrides only the response LLM for this voice-channel turn.voice.ttsis merged overtts; streaming-capable providers feed the player directly, otherwise the resulting audio file is played in the joined channel.
voice.agentSession block, each voice channel gets its own routed OpenClaw session. For example, /vc join channel:234567890123456789 talks to the session for that Discord voice channel. The realtime model is only the voice front end; substantive requests are handed to the configured OpenClaw agent. If the realtime model produces a final transcript without calling the consult tool, OpenClaw forces the consult as a fallback so the default still behaves like talking to the agent.
Legacy STT plus TTS example:
agent-proxy mode the bot joins the configured voice channel, but OpenClaw agent turns use the target channel’s normal routed session and agent. The realtime voice session speaks the returned result back into the voice channel. The supervisor agent can still use normal message tools according to its tool policy, including sending a separate Discord message if that is the right action.
While a delegated OpenClaw run is active, command-authorized Discord conversation transcripts are treated as live run control before starting another agent turn. Phrases such as “status”, “cancel that”, “use the smaller fix”, or “when you’re done also check tests” are classified as status, cancel, steering, or follow-up input for the active session. Status, cancel, accepted steering, and follow-up outcomes are spoken back into the voice channel so the caller knows whether OpenClaw handled the request.
When OpenClaw cancels a delegated consult, Discord records cancellation rather than a failure and does not play the generic error fallback. Matching late provider tool calls receive the same terminal cancellation instead of restarting the work. The voice session remains available for the next request; timeouts and genuine failures keep their normal error handling.
Useful target forms:
target: "channel:123456789012345678"routes through a Discord text channel session.target: "123456789012345678"is treated as a channel target.target: "dm:123456789012345678"ortarget: "user:123456789012345678"routes through that direct-message session.
bargeIn: true lets Discord speaker-start events and already-active speaker audio cancel active realtime responses before the next captured turn reaches OpenAI. Very early barge-in signals with audioEndMs below minBargeInAudioEndMs are treated as likely echo/noise and ignored so the model does not cut off at the first playback frame.
Expected voice logs:
- On join:
discord voice: joining ... voiceSession=... supervisorSession=... agentSessionMode=... voiceModel=... realtimeModel=... - On realtime start:
discord voice: realtime bridge starting ... autoRespond=false interruptResponse=false bargeIn=false minBargeInAudioEndMs=... - On speaker audio:
discord voice: realtime speaker turn opened ...,discord voice: realtime input audio started ... outputAudioMs=... outputActive=..., anddiscord voice: realtime speaker turn closed ... chunks=... discordBytes=... realtimeBytes=... interruptedPlayback=... - On skipped stale speech:
discord voice: realtime forced agent consult skipped reason=incomplete-transcript ...orreason=non-actionable-closing ... - On realtime response completion:
discord voice: realtime audio playback finishing reason=completed ... audioMs=... chunks=...; buffered audio can still be playing after this line. - On ordinary playback backpressure:
discord voice: realtime audio playback buffering ... bufferedBytes=...; playback continues when Discord drains the buffered audio. - Discord acknowledges scoped provider playback marks after their PCM is consumed. xAI waits for these acknowledgments before starting a following response; clearing discarded audio does not report it as played.
- On playback stop/reset:
discord voice: realtime audio playback stopped reason=... audioMs=... elapsedMs=... chunks=... - On realtime consult:
discord voice: realtime consult requested ... voiceSession=... supervisorSession=... question=... - On agent answer:
discord voice: agent turn answer ... - On queued exact speech:
discord voice: realtime exact speech queued ... queued=... outputAudioMs=... outputActive=..., followed bydiscord voice: realtime exact speech dequeued reason=player-idle ... - On barge-in detection:
discord voice: realtime barge-in detected source=speaker-start ...ordiscord voice: realtime barge-in detected source=active-speaker-audio ..., followed bydiscord voice: realtime barge-in requested reason=... outputAudioMs=... outputActive=... - On realtime interruption:
discord voice: realtime model interrupt requested client:response.cancel reason=barge-in, followed by eitherdiscord voice: realtime model audio truncated client:conversation.item.truncate reason=barge-in audioEndMs=...ordiscord voice: realtime model interrupt confirmed server:response.done status=cancelled ... - On ignored echo/noise:
discord voice: realtime model interrupt ignored client:conversation.item.truncate.skipped reason=barge-in audioEndMs=0 minAudioEndMs=250 - On disabled barge-in:
discord voice: capture ignored: ... reason=protected playback - On idle playback:
discord voice: realtime barge-in ignored reason=... outputActive=false ... playbackChunks=0
realtime audio playback startedmeans Discord has begun playing assistant audio. The bridge starts counting assistant output chunks, Discord PCM bytes, provider realtime bytes, and synthesized audio duration from this point.realtime speaker turn openedmarks a Discord speaker becoming active. If playback is already active andbargeInis enabled, this can be followed bybarge-in detected source=speaker-start.realtime input audio startedmarks the first actual audio frame received for that speaker turn.outputActive=trueor a nonzerooutputAudioMshere means the mic is sending input while assistant playback is still active.barge-in detected source=active-speaker-audiomeans OpenClaw saw live speaker audio while assistant playback was active. This is useful for distinguishing a real interruption from a Discord speaker-start event with no useful audio.barge-in requested reason=...means OpenClaw asked the realtime provider to cancel or truncate the active response.outputAudioMsandplaybackChunksdescribe generated audio; the subsequentaudioEndMstruncation field records consumed audio for each native item. Discord measures local consumption from Opus packets prepared by AudioPlayer, at 20 ms granularity; it cannot measure remote listener playout.realtime audio playback stopped reason=...is the local Discord playback reset point.player-idlemeans Discord finished consuming the audio; providerresponse.doneand encoder completion alone do not mean playback is finished. Other reasons includebarge-in,provider-clear-audio,forced-agent-consult,stream-close,output-audio-overflow, andsession-close.realtime speaker turn closedsummarizes the captured input turn.chunks=0orhasAudio=falsemeans the speaker turn opened but no usable audio reached the realtime bridge.interruptedPlayback=truemeans that input turn overlapped assistant output and triggered barge-in logic.
outputAudioMs: assistant audio duration generated by the realtime provider before the log line.audioMs: assistant audio duration that OpenClaw counted before playback stopped.elapsedMs: wall-clock time between opening and closing the playback stream or speaker turn.discordBytes: 48 kHz stereo PCM bytes sent to or received from Discord voice.realtimeBytes: provider-format PCM bytes sent to or received from the realtime provider.playbackChunks: assistant audio chunks forwarded to Discord for the active response.sinceLastAudioMs: gap between the last captured speaker audio frame and the speaker turn closing.
- Immediate cut-off with
source=active-speaker-audio, smalloutputAudioMs, and the same user nearby usually points to speaker echo entering the mic. Raisevoice.realtime.minBargeInAudioEndMs, lower speaker volume, use headphones, or setvoice.realtime.providers.openai.interruptResponseOnInputAudio: false. source=speaker-startfollowed byspeaker turn closed ... hasAudio=falsemeans Discord reported a speaker start but no audio reached OpenClaw. That can be a transient Discord voice event, noise gate behavior, or a client briefly keying the mic.audio playback stopped reason=output-audio-overflowmeans sustained delivery problems exceeded the bounded pending-audio queue. Check the associatedDiscord realtime audio playback overflowerror and preceding provider or Discord connection diagnostics; ordinary playback backpressure should not produce this error.Discord voice receive backlog exceededmeans identity lookup, decoding, or local disk could not keep up with incoming audio. OpenClaw bounds each receive stream at 1,000 pending packets and 1 MiB of encoded audio; speak again after the bottleneck clears. Other speakers and any registered capture remain active, and incomplete speech cannot become a new conversation command.conversation audio backlog exceeded,conversation is busy, orconversation transcript limit exceededmeans a conversation limit was reached. Recording continues; wait for pending requests to finish or repeat a shorter, complete request.Discord voice recording backlog exceededmeans pending WAV files reached the recording queue’s chunk or byte limit. The affected utterance stops, and the registered capture remains available after pending audio processing catches up.audio playback stopped reason=stream-closewithout a nearby barge-in orprovider-clear-audiomeans the local Discord playback stream ended unexpectedly. Check the preceding provider and Discord player logs.capture ignored: ... reason=protected playbackmeans OpenClaw intentionally withheld conversational input while assistant audio was active. An explicitly started capture still records that speech. Enablevoice.realtime.bargeInif you want speech to interrupt playback.barge-in ignored ... outputActive=falsemeans Discord or provider VAD reported speech, but OpenClaw had no active playback to interrupt. This should not cut off audio.
voice.model, STT auth for tools.media.audio, TTS auth for tts/voice.tts, and realtime provider auth for voice.realtime.providers or the provider’s normal auth config.