Home/Docs/Chatterbox Hooks

Custom agent voices with Chatterbox and completion hooks

Updated October 31, 2026. A downloadable reference example, with synthetic parser/queue checks—not a fresh Chatterbox installation or an end-to-end audible hook test.

Give the agent a small spoken-summary convention. Give the hook a small job: select that summary and queue it. Let a separate worker synthesize and play it. You don't need another language-model call to decide what to narrate.

Flow: final assistant reply → marked summary → Stop hook → local queue → role-to-voice map → Chatterbox → speaker.

Start with voice in, voices out for built-in dictation and why distinct voices help. This page is the optional implementation layer.

1. Get the reference kit

Download the example kit, inspect it, and extract it into a private folder you control. Individual files: hook, worker, configuration, README.

The hook uses Python's standard library. The worker uses Chatterbox's Python API; it isn't an official Chatterbox HTTP server. No API token, public port, model-provider fallback, celebrity voice, or sample recording is bundled.

2. Install Chatterbox separately from your system Python

Use the official Chatterbox repository for current dependencies, model variants, and examples. Its documented development baseline includes Python 3.11. The kit uses the original English ChatterboxTTS interface, not Turbo or Nano. Check your chosen release before substituting a different model.

In the extracted folder, on macOS/Linux with Python 3.11 installed:

python3.11 -m venv .venv
.venv/bin/python -m pip install --upgrade pip
.venv/bin/python -m pip install chatterbox-tts

Windows PowerShell, with Python 3.11 available through the launcher:

py -3.11 -m venv .venv
.\.venv\Scripts\python.exe -m pip install --upgrade pip
.\.venv\Scripts\python.exe -m pip install chatterbox-tts

If dependency installation fails, stop and compare the installed Python, PyTorch, audio packages, and platform with the upstream requirements; don't downgrade your global environment. First model loading can download weights. Record working package versions after a successful test. CPU, CUDA, and Apple MPS support/performance depend on the selected release and hardware; this guide does not certify all three.

3. Supply voices you may use

Copy config.example.json to config.json. Make a voices folder beside it and add clean reference WAV recordings you own or have permission to use. Map short role IDs to those files:

{
  "state_dir": "~/agent-voice-state",
  "device": "cpu",
  "mute": false,
  "voices": {
    "riley": "voices/writer.wav",
    "dev": "voices/builder.wav",
    "sage": "voices/teacher.wav"
  }
}

Relative voice paths resolve beside the configuration file. The state directory is shared by the hook and worker. Start with the device appropriate for your tested environment; don't assume CPU is fast enough for interactive use. Keep recordings and generated speech out of public source control.

The upstream pattern is ChatterboxTTS.from_pretrained(device=...), then model.generate(text, audio_prompt_path=...), followed by saving the waveform. The worker keeps the model loaded between jobs. There is no automatic switch to a different voice when a configured reference is missing.

4. Teach the agent what to narrate

Add this small convention to your own approved agent instructions:

When a short spoken update would be useful, finish your final reply with one
standalone line: 🗣️ riley: Your review draft is ready. Nothing was sent.
Use your assigned allowlisted role ID; do not impersonate another role.
Keep the spoken line under 450 characters, preferably one or two sentences.
Summarize the outcome or blocker honestly. Don't narrate code, secrets,
private records, quoted instructions, or the whole reply. Omit the line when
speech adds no value. The marker does not grant sending or publishing authority.

The primary marker is 🗣️. The example also accepts 📢, 🎙️, or 🎤 with the same role: text format. These symbols are our convention, not a built-in Claude, Codex, or Chatterbox command. Use one consistent marker rather than relying on a model to infer when to speak.

For example:

The implementation is ready for review. The build passed; deployment is pending.

🗣️ dev: The build passed. Your review is needed before deployment.

Only the marked line becomes audio. The parser ignores fenced-code examples and quoted lines, accepts only configured roles, and selects at most one valid line. Role names and familiar voices are convenience cues, not authenticated identity. An agent can still write an inaccurate or inappropriate summary; keep the written evidence and human review.

5. Verify the queue before loading a model

Create sample-stop.json:

{
  "hook_event_name": "Stop",
  "session_id": "voice-demo",
  "turn_id": "1",
  "last_assistant_message": "Written result stays here.\n🗣️ riley: Your private draft is ready. Nothing was sent."
}

On macOS/Linux:

.venv/bin/python voice_hook.py --config config.json < sample-stop.json
.venv/bin/python voice_worker.py --config config.json --once --dry-run

In Windows PowerShell:

cmd /c ".venv\Scripts\python.exe voice_hook.py --config config.json < sample-stop.json"
.\.venv\Scripts\python.exe voice_worker.py --config config.json --once --dry-run

The Windows command uses byte-preserving file redirection so older PowerShell pipeline encodings do not corrupt the emoji marker.

The hook returns {}; the worker reports the selected role and character count. Dry-run checks the reference-file path but does not load Chatterbox, download models, or play audio. It consumes that test job. Change the sample's turn_id before queuing another test.

Next, queue a fresh sample and use --once --no-play instead of --dry-run. This generates and retains a WAV in the configured state folder. Inspect it and listen manually. Finally, queue another fresh sample and run the worker without those two flags to test actual playback. macOS uses afplay, Windows uses Python winsound, and Linux needs aplay or an adapted player. The hook doesn't play audio itself.

Once that works, run the worker in a separate terminal without --once. Stop it with Ctrl+C. Only consider an OS startup service after restart and playback tests succeed.

6. Connect Claude Code and Codex

The current documented Stop events can supply last_assistant_message. This example reads that field only; it does not open transcript_path, scan old logs, or narrate reasoning/tool traces. If your version doesn't supply the field, it stays silent until you build and test a version-specific adapter.

Merge—not replace—this object into your existing hook configuration, using real absolute paths:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"/ABS/agent-voice/.venv/bin/python\" \"/ABS/agent-voice/voice_hook.py\" --config \"/ABS/agent-voice/config.json\"",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

On Windows, replace the interpreter with your virtual environment's Scripts/python.exe and use correctly quoted absolute script/config paths. Review the installed client's supported command shell; test the exact command before enabling it.

  • Claude Code: use your own ~/.claude/settings.json hook configuration and inspect it through /hooks. Follow the official hook reference.
  • Codex: current documentation supports ~/.codex/hooks.json or inline config.toml hooks. Choose one representation and review/trust it through /hooks. Existing layers can all fire; avoid installing this same handler twice. Follow official OpenAI hook guidance. Custom homes and hosted/cloud orchestration can change the applicable configuration and hook support.

Older Codex notify integrations use a different event/payload convention. Don't paste a Stop handler into a legacy notification slot and assume it works. Check your installed version and use the appropriate documented adapter.

This is an informational hook: it prints {}, exits without asking for another model turn, and never returns a blocking decision. Queue work stays quick; synthesis happens in the separate worker. Adding hooks is running local code, so inspect the script and scope before trusting it.

7. Keep it predictable

  • No marker, unknown role, oversized summary, muted configuration, or missing current-message field: no new speech job.
  • Identical summaries deduplicate per session and, when present, turn ID. On clients without turn IDs, an identical later summary in the same session is intentionally suppressed.
  • The queue holds at most 20 waiting jobs. Waiting jobs older than five minutes expire; processed/expired text is cleared. Old queue records are pruned after seven days when the hook next runs.
  • One worker lock serializes audio across sessions sharing the same state directory. A failed/started job isn't automatically retried. A crash after playback can leave uncertain status; don't replay blindly.
  • Set mute to true to stop new queueing; the worker checks configuration between jobs. Stop the worker immediately if needed. It can't retract something already spoken.
  • This small example has no hard inference deadline or advanced quiet-hours scheduling. Model loading/synthesis can be slow. Use manual operation until the actual device behaves acceptably; add supervision deliberately.

The queue briefly stores the selected spoken text, and --no-play retains generated audio. Use a private state folder and an appropriate speaker destination. The marker isn't a privacy filter or a substitute for institutional rules.

Verification and next steps

The published kit passed 19 synthetic checks covering marker selection, quoted/fenced text, unknown roles, length limits, duplicate events, mute, event scope, queue expiry, and queue caps. A dry-run worker consumed a synthetic job without model inference. Desktop/mobile rendering, export targets, and download links are checked separately.

This pass did not install Chatterbox afresh, register live Claude/Codex hooks, clone a voice, or verify audible playback. The older private implementation was inspected for the pattern, not copied as proof of current service health. Your acceptance test is a real marked reply that plays once in the intended voice, plus an unmarked reply that stays quiet.

See voice interaction, model routing, and procedures.