Talk Mode Setup
SkillCommunicationGive a second brain that is already running a voice. Interactive setup for Talk Mode real-time voice, on the dashboard /talk page (zero install) and in Discord voice channels (py-cord sidecar). Detects what is already configured, asks before every write, and defaults voice to a Codex subscription instead of a metered API key. Use when the user wants to add or repair voice: "give my second brain a voice", "set up talk mode", "add voice", "install talk mode", "talk mode isn't working", "discord voice", "voice channel bot", "the bot can't hear me", "/talk join fails".
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Talk Mode Setup skill
What this skill tells your AI
The instructions your AI receives, as published by thesmokedev/taskchad-os in .claude/skills/talk-mode-setup/SKILL.md and read by ahel’s review.
Adds voice to a framework install that is already running. This does not bootstrap a second brain, it gives an existing one a mouth and ears.
For the architecture, the receive-pipeline story, and the design notes, send the
user to docs/talk-mode-showcase.md. Do not restate it here.
Rules of engagement
This skill is interactive. The user drives.
- Reads and checks are free. Run the preflight, inspect
.env, check the OS, look for the venv. Never ask permission to look. - Every write is gated. Before touching
.env, creating a venv, or starting a service, show exactly what you are about to do and get an explicit pick viaAskUserQuestion(or the running provider's question tool). - Detect, then ask only what is missing. Somebody half set up must not re-answer solved questions. Skip any step the preflight already reports green.
- One question per real decision, with concrete options. Never a freeform "ok?".
Step 1. Detect (no questions yet)
cd .claude/scripts
uv run python ../skills/talk-mode-setup/scripts/preflight.py --json
The JSON gives you everything you need to branch:
| Field | Use it to |
|---|---|
source | Which credential currently wins: codex-oauth, env, or configured. |
codexAvailable | Whether a Codex ChatGPT login exists at all. |
preferCodex | Whether TALK_PREFER_CODEX_OAUTH already pins voice to the subscription. If true, skip the billing question. |
talkKeySet / envKeySet | Which API keys are set. Drives the metering warning. |
Discord bot token check | Whether the Discord surface is even possible. |
Discord voice sidecar check | Whether the venv is built, and whether this OS can run it. |
| port checks | Whether the services are already up. |
Also record the OS. sys.platform is enough.
Report the findings back in one short block before asking anything. The user should see the current state before making choices.
Step 2. Ask which surface
Only ask about surfaces that are actually possible on this machine.
AskUserQuestion, header Surface:
- Dashboard only: browser tab, zero install, works on every OS. Fastest path.
- Discord voice: talk in a voice channel. Needs a bot token plus a sidecar venv.
- Both.
Both surfaces work on Windows, macOS, and Linux. The sidecar resolves its
interpreter in the local venv layout and tears down by process group, so
uv sync then /talk join behaves the same everywhere.
One caveat you do not have to reason about: a framework build older than the OS-aware sidecar resolver only ever looked for the Windows layout, so Discord voice could not start on macOS or Linux there. Step 1's preflight detects that case by asking the installed lifecycle which interpreter it spawns, and names it outright. Trust the preflight over any assumption about the platform.
Step 3. Auth, defaulting to the subscription
By default Talk resolves a credential in this order, first hit wins:
TALK_OPENAI_API_KEYOPENAI_API_KEY- Codex OAuth (an existing ChatGPT sign-in via the Codex CLI)
TALK_PREFER_CODEX_OAUTH=true overrides that order for voice only: Codex
becomes the sole source, ahead of both keys. It is a billing directive, not a
ranking, which is why an unusable login fails closed instead of quietly
switching to a metered key.
Prefer Codex. It rides a subscription the user already pays for, with no per-minute API cost. Steer there whenever it is available.
Branch on what Step 1 found:
A. source is already codex-oauth. Nothing to do. Say it is using the
subscription and move on. Do not ask.
B. codexAvailable is true but an API key is winning. This is the metering
trap: by default the key outranks the subscription, so the user is billed per
minute without being told. Surface it and let them choose.
AskUserQuestion, header Billing:
- Use the Codex subscription. Voice rides a plan they already pay for, and
it can never quietly bill them: if the Codex login later lapses, voice stops
with an error naming the fix instead of silently falling back to a metered
key. Frame that as the selling point it is. A surprise invoice is not a
failure mode here.
Action: write
TALK_PREFER_CODEX_OAUTH=trueto.claude/scripts/.env. Show the exact line and confirm before writing it. Reassure them: the key stays where it is. This knob is scoped to voice, soOPENAI_API_KEYkeeps serving STT, the OpenAI-compatible runtime lane, and personas. Nothing else moves. - Keep the API key. Action: nothing. Voice bills per minute. Fine if deliberate.
If preferCodex is already true, this branch does not apply. The directive is
set and Codex has been chosen; do not re-ask.
C. No credential at all. AskUserQuestion, header Credential:
- Sign in with Codex (recommended, no per-minute cost): run
codex login, then confirm withcodex login status. - Wire an API key: write
TALK_OPENAI_API_KEY=sk-...to.claude/scripts/.env. Confirm the exact line before writing it.
A configured key that is set but empty fails closed and does not fall through. If you find a blank key, remove the line rather than blanking it.
After any change, re-run the preflight and confirm source is what the user
picked. Do not assume the edit took.
Step 3.5. Identity — should it know you?
The default voice prompt carries SOUL only: the behavioral contract, none of
the personal context. Out of the box the voice will not know the user's name,
projects, or memory — and it fails SILENTLY (status ready, audio fine, just a
stranger's personality). If the user expects "it opens knowing me", this step is
what delivers it.
AskUserQuestion, header Identity:
- Load my identity on calls (recommended). The voice opens with SOUL + USER +
MEMORY + WORKING already in context — it knows who it's talking to without a
single tool call.
Action: write
TALK_IDENTITY_INCLUDE=SOUL,USER,MEMORY,WORKINGto.claude/scripts/.env. Show the exact line and confirm before writing. - Behavioral contract only. SOUL rules, no personal context. Fine for a shared or demo install. Action: nothing (the default).
The SOUL-drop trap: the env list REPLACES the default, it does not extend
it. TALK_IDENTITY_INCLUDE=USER,MEMORY ships NO soul. Always list SOUL
explicitly — never write this variable without it.
Step 4. Dashboard voice
Confirm before starting anything. AskUserQuestion, header Services, listing
which of these are already listening from Step 1:
cd .claude/scripts && uv run python orchestration/run_api.py # 4322
cd dashboard/server && DASHBOARD_DEV_MODE_NO_AUTH=true npm start # 3141
Then open http://127.0.0.1:3141/talk.
Two things that silently break this:
- Microphone needs a secure context.
localhostor127.0.0.1or HTTPS. A LAN IP denies the mic with no useful error. - Proxy auth is all or nothing. Either
DASHBOARD_DEV_MODE_NO_AUTH=trueon loopback, orDASHBOARD_TOKENset equal toORCHESTRATION_API_TOKENand the page loaded once as/talk?token=<DASHBOARD_TOKEN>. Mismatched or half-set, the proxy refuses to start.
Step 5. Discord voice
Skip entirely unless Step 2 chose it. Any OS is fine.
5a. Bot token. Confirm before writing. In the Discord Developer Portal:
Bot tab, copy the token, then write DISCORD_BOT_TOKEN=... into
.claude/scripts/.env. The main bot and the sidecar share this one token.
Enable Message Content Intent for the main bot. The sidecar itself only requests
guilds and voice_states.
5b. Invite. OAuth2 URL Generator, scopes bot and applications.commands.
Permissions: Send Messages, Read Message History, and under Voice, Connect
and Speak. Missing Connect or Speak fails the join at the Discord layer, not
in this code.
5c. Sidecar venv. Confirm before creating, then:
cd .claude/scripts/discord_voice
uv sync
That creates .venv/ with py-cord[voice], PyNaCl, websockets, and httpx. The
sidecar needs its own venv because py-cord and discord.py both own the
discord import namespace and cannot share one.
5d. Use it. The user never runs bridge.py directly. /talk join spawns the
sidecar (control server on 127.0.0.1:7861) and /talk leave reaps it.
/talk status # state, plus channel, uptime, auth source, and log file once live
/talk join # joins YOUR current voice channel
/talk leave # leaves and writes a memory debrief
/talk is admin-only, Discord-only, and needs the orchestration API on 4322.
Join a voice channel yourself before running /talk join.
If ORCHESTRATION_API_TOKEN is set, the sidecar reads that same variable and
sends it as a bearer token. Mismatched values produce a confusing half-failure:
ordinary conversation still works while every tool call 401s.
Step 6. Verify
Do not stop at "no errors". Confirm the loop end to end:
- Preflight shows no
[FAIL], andsourceis the credential the user chose. - Discord:
/talk statusreportsreadywith a channel, uptime, and auth source. That is the success gate. - Speech in: transcripts show real words, not empty strings or fragments. Fragments mean the receive pipeline, not the model.
- Speech out: an audible reply.
- Identity: if Step 3.5 chose it, ask the voice something only the USER or
MEMORY files know ("what am I working on?"). A generic answer means the
prompt collapsed — the knob being set is not proof it arrived. On
Discord, confirm with the receipt the sidecar logs at session start:
grep "identity roots:" <the log /talk status names>must showprofile=defaultand amemory_dirthat exists on disk. Aprofile=customyou never configured, or a nonexistentmemory_dir, is the collapse — seereferences/troubleshooting.md. - Memory: end the session and confirm the debrief landed in the daily log.
If any step fails, load references/troubleshooting.md.
Step 7. Optional tuning
Only offer this if the user asks. Defaults are sane.
| Knob | Default | Effect |
|---|---|---|
TALK_OPENAI_VOICE | cedar | alloy, ash, ballad, coral, echo, sage, shimmer, verse, marin, cedar. An unknown name raises. |
TALK_OPENAI_MODEL | gpt-realtime-2.1 | Realtime model. |
TALK_IDENTITY_INCLUDE | SOUL | Covered in Step 3.5 (not ask-only — it's the "opens knowing you" switch). Replaces the default, does not extend it, so USER,MEMORY ships no SOUL. List SOUL explicitly. Valid names: SOUL, USER, MEMORY, WORKING, GOALS, SELF. |
TALK_PREFER_CODEX_OAUTH | off | Pins voice to the Codex subscription ahead of both API keys. Fails closed if the login is unusable rather than falling back to metered billing. Scoped to voice; other OPENAI_API_KEY consumers are untouched. |
TALK_ENABLE_CODE_EXEC | off | Fail-closed opt-in for run_python and run_shell. |
DISCORD_VOICE_JITTER_FRAMES | 3 | Input buffer depth, about 60 ms. |
DISCORD_VOICE_SILENCE_DBFS | -35 | Noise gate. Lower passes quieter audio. |
DISCORD_VOICE_DEBUG_PCM | unset | Dumps the exact PCM sent to OpenAI. First thing to reach for on "it cannot hear me". |
Kill switches, set to disabled to refuse: HOMIE_KILLSWITCH_VOICE (all voice),
HOMIE_KILLSWITCH_COMPUTER_USE (the computer and browse tools).
Bundled
scripts/preflight.py: the detection pass.--jsonfor branching.references/troubleshooting.md: symptom-first fixes, load on demand.
Maintenance notes
Both original limits are now resolved. What replaced them is worth protecting.
1. Codex preference (resolved). TALK_PREFER_CODEX_OAUTH makes Codex the
sole voice credential, ahead of both key legs, and fails closed when the
login is unusable rather than falling back to a metered key. That fail-closed
behavior is the whole point of the flag, not an edge case: a silent fallback
would bill someone who explicitly asked not to be billed. If you ever see a
fallback path reintroduced under this flag, the copy in Step 3B becomes a lie
about billing. Treat that as a blocker, not a nit.
The knob is voice-scoped by design, so OPENAI_API_KEY keeps serving STT, the
OpenAI-compatible runtime lane, and personas. Do not "helpfully" broaden it.
2. Discord voice is cross-platform (resolved).
discord_voice_lifecycle._sidecar_python() is OS-aware, so the Windows-only
limitation is gone from the copy. _check_sidecar in scripts/preflight.py
deliberately does NOT hardcode either layout: it imports the lifecycle and asks
which interpreter that build spawns, then checks whether it exists. That keeps
it correct on older Windows-only builds too, where it reports the mismatch by
name instead of telling someone to re-run a uv sync they already ran. Do not
"simplify" it back to a layout guess.
Signals
- GitHub stars
- 23
- Forks
- 5
- Last commit
- Sep 2026
ahel review
K6low
bundled executables the agent is told to run
Automated review, not a security audit. Ruleset v1+k2.
Advanced
- Catalog kind
- skill
- Gateway key
talk-mode-setup- Source
- github.com/thesmokedev/taskchad-os