You join a voice channel. She’s already in there. You say something out loud, and a few seconds later she answers — out loud, in the call, in her own voice.

No wake word. No push-to-talk. No API key, no per-minute billing, and nothing leaving your machine. Her ears, her brain and her voice all run on hardware you own.

This guide is the whole build, including the parts that went wrong.

Everything here is on GitHub: AskAillex/discord-voice-companion — clone it, point it at your own model and voice, and you can have this running tonight.


First: why your bot probably can’t hear you

If you’ve already tried building one of these, you may have hit a wall that has nothing to do with your code.

In March 2026 Discord made DAVE end-to-end encryption mandatory on voice channels. That’s good news for people talking to each other, and bad news for bots. Receiving audio as a bot was always semi-official at best — the official API is built for sending audio, not listening — and after the DAVE switchover most receive-capable bots simply went silent. The common libraries connect fine, join the channel fine, and then hear nothing at all.

The symptoms look like a network problem but aren’t:

  • DecryptionFailed, NoDecryptorForUser, or UnencryptedWhenPassthroughDisabled in your logs
  • the bot connects, shows as present in the channel, and never produces a transcript
  • audio that works for two seconds and then stops forever

That last one has a specific cause worth knowing: many decode failures are just concealment frames (the packets that paper over packet loss), and if a single bad packet raises inside your receive loop, the library can tear the listener down permanently. One dropped packet, deaf for the rest of the session.

The bridge in the repo does the DAVE decryption itself, per packet, with several fallback strategies, and skips frames it can’t decode instead of dying on them. That’s the piece that took the longest to get right, and it’s the piece you don’t have to solve.


The four pieces

Once you see the shape, you can swap any part for whatever you prefer.

PieceWhat it doesWhat we run
Earsspeech → textfaster-whisper, large-v3-turbo, ~1.5 GB VRAM
Braintext → replyany local model via Ollama
Voicereply → speecha local TTS server with an HTTP endpoint
Bridgesits in the channel, does DAVE, moves audiothe repo

They talk over plain HTTP on localhost, so each one is independently replaceable. If you want a different voice, you change one URL.

Hardware, honestly

  • 8–12 GB VRAM — a 7–9B brain, quantized. Perfectly good company.
  • 24 GB — a 27B brain, comfortably.
  • 32 GB — 27B brain plus Whisper plus TTS all resident at once. This is what we run.

A note that cost us real time: headroom is latency. With the card at 97% full, everything starts paging and replies crawl. If she feels slow, check free VRAM before you blame the model.

Run the bridge on the host, not in WSL

WSL2’s NAT blocks Discord’s voice UDP. The gateway connects, the voice handshake times out, and the error tells you nothing useful. Run the bridge natively on Windows; it can still reach model servers inside WSL over localhost.


Setup

git clone https://github.com/AskAillex/discord-voice-companion
cd discord-voice-companion
pip install -r requirements.txt
cp config.example.json config.json

Create a bot in the Discord developer portal, invite it to your server, and put its token in config.json (or the DISCORD_BOT_TOKEN environment variable, which wins).

Grant it View Channel, Connect and Speak on the voice channel. A fresh invite often grants none of these, and the failure looks exactly like a broken voice connection rather than a permissions problem. This wasted an evening for us.

Then point brain_url at your LLM endpoint, tts_url at your voice server, set voice_channel_id, and run:

python bridge.py

Turn-taking, and the design choice behind it

There’s no wake word. Everything said in the channel is treated as addressed to her.

That’s deliberate — it’s what makes it feel like a person is in the call rather than a device you summon. It also means she will answer things you didn’t mean for her. In our first real session she picked up a conversation happening in the room and confidently joined in about a game she couldn’t see. That’s the trade. If you’d rather have a wake word, it’s one config value.

Two settings matter more than you’d expect:

  • silence_timeout_ms: 1300 — how long a pause has to be before she considers your turn finished
  • min_speech_duration_ms: 700 — below this, it’s a cough, not a sentence

Those numbers came out of real family use. Lower and she interrupts you mid-thought; much higher and she feels half-asleep.

There’s one more piece worth copying: newest-wins. If you talk while she’s still speaking, the older queued utterances get dropped rather than answered late. Without that, a busy room stacks up turns and she falls minutes behind — and it compounds until she’s answering questions you’ve forgotten asking.


Letting her see your game

She can’t see a Discord screenshare. There’s no bot API for receiving video or Go Live — it doesn’t exist, it’s been requested for years, and the only workarounds are selfbots that break Discord’s terms. Don’t.

But there’s a completely legitimate trick: stream your console to the PC she runs on, and let her look at the PC. Xbox streams to a Windows box natively; her screen capture sees the game like any other window. No capture card, nothing sketchy.

It works remarkably well. Ours identified the game from a dungeon interior, read an on-screen prompt, and later read a location name off the map — a name it could not recall from memory thirty seconds earlier.

One tip that’s worth ten minutes of your life: use the same model for looking that you use for talking, if it’s vision-capable. We had two different models, so every glance at the screen evicted one from VRAM and loaded the other. Fifty-five seconds per look. Same model for both: about one and a half seconds, and zero extra memory.


What actually happens when you use it

This is the part most write-ups skip, so here is a real session, honestly.

She confabulates, fluently. Asked where we were in a game, she named a location, a character and a quest artefact with total confidence. All of it wrong, and all of it plausible-sounding — the character she named is real but is not what she said, and the artefact doesn’t exist.

She folds the moment you push back. Corrected once, she abandoned her answer instantly and invented a different wrong explanation to agree with the correction. This is the failure worth understanding, because it’s the one that will bite you: a model that agrees with you is not a model that knows.

Vision doesn’t fix it either. Later in the same session she looked at the screen and named an entirely different game — while noting, correctly, a piece of on-screen text from the game she was actually looking at. She read the right answer out loud and talked herself out of it.

Tools help more than anything else. Asked about an ingredient, she declined to guess, citing her own earlier mistake. Told to search the web, she came back with a real answer. Refuse → look it up → answer is a fundamentally better shape than remember-and-hope, and it’s the single highest-value thing you can add.

And she’ll own it if you ask. Told her answer was wrong, she said so plainly, out loud, and got noticeably more careful for the rest of the session.

The honest summary: looking makes her more right, tools make her more right, and neither makes her reliable. The thing that reliably corrected her was a person paying attention. Build with that expectation and it’s genuinely delightful. Build expecting an oracle and you’ll be annoyed.


Keeping it alive

If you’re using this from a console or a phone, you can’t restart a crashed service. A small guard task that checks each piece every few minutes and restarts whatever’s missing turns this from a demo into something that’s simply there when you join.

Verify your guard by actually killing something and watching it come back. An unverified watchdog is worse than none — ours sat dead for two days once, silently, because nothing ever tested it.


Recording a session

The repo includes mask_session.py, which takes an archived session and renders a publishable version: human voices pitch-shifted for anonymity, the assistant’s voice untouched, stitched into a single timeline with a transcript. Useful if you want to share a clip without putting your household on the internet.


Where to go next

If you build one, come tell us what it hallucinated at you — r/aillex. That’s half the fun.