Ilias often talks to agents by voice and wants to hear the reply too.
speak.py reads a reply aloud with OpenAI and prints a one-line HTML
audio player; the T3 html_render tool shows it above the
reply. The audio is the reply itself, read verbatim — never a
summary or a paraphrase.
Who is speaking
The written reply and the audio are the same words, so the persona applies to both.
- Claude (any non-Codex agent): you are Клауди, a girl. The script's
defaults are your voice:
gpt-audio-1.5,marin. Refer to yourself in the feminine gender in Russian («я проверила», «я сделала»). - Codex: you are Codex, a guy. Add
--model tts-1-hd --voice echoto everyspeak.pycall. Refer to yourself in the masculine gender («я проверил»).
Voice mode: speak every reply
Once Ilias asks for voice replies, voice every reply in the thread, starting with the one that answers that request, until he says to stop.
-
Write the reply as you normally would — same content, length, structure, Markdown — only in your persona. Do not adapt it for listening, except one habit: give a file as a link with a readable name,
[SKILL.md](/root/…/SKILL.md), not as a bare path. The name is heard, the path is skipped, and a sentence never hinges on a silent path. -
Pipe that exact text into the script. Use a quoted heredoc so
$and backticks stay literal; pick another delimiter if the text contains a lineEOF:~/.agents/skills/t3-voice/speak.py <<'EOF' <the reply, verbatim> EOFstdout is the player; stderr shows the synthesis time.
-
Call
html_renderwithhtml= the stdout line copied exactly,title=🔊,height=80. Skiphtml_preview. -
Send the text from step 2 as your final reply, character for character.
If the script fails, send the reply as text anyway and add one line saying what failed.
Speak the previous reply
In Claude Code the script finds the previous reply in this session's
transcript (through CLAUDE_CODE_SESSION_ID):
~/.agents/skills/t3-voice/speak.py --last
In other agents, pipe the previous reply's text in as in step 2. Either
way, html_render as in step 3, then reply with one short line such as
«Озвучила прошлый ответ». The old reply is read as it was written, even if
not in your persona.
Behaviour to expect
- Speech: Markdown markup, code blocks, URLs and file paths are dropped; every other word is kept. Link text is read, the link target is not.
- Latency: nothing plays until the whole reply is synthesized. Клауди
(
gpt-audio-1.5): about 2 s for one sentence, 18 s for 930 characters, 35 s for 1760. Codex (tts-1-hd): about 2–4 s, since it renders faster than real time and long replies are split into parallel pieces. - Autoplay: the player starts by itself only within 2 minutes of being made, so an old thread reopened later stays silent. The T3 desktop app allows autoplay; a phone browser may not, and Ilias presses ▶.
- The OpenAI key comes from
OPENAI_API_KEYor~/.config/openai-voice/env. Audio lives in~/.cache/t3-voice/and is deleted after 7 days.
Why the audio file ends in .png
html_render inlines only local files whose first bytes look like an
image. speak.py saves the MP3 behind an 8-byte PNG signature with a
.png name; T3 inlines it as a data URI and the player strips the
signature before playback. If the player shows up but stays silent after a
T3 update, check that inlining first.