Voice Handoff Pattern
Transfer voice conversations between Claude Code agents seamlessly.
Overview
Voice handoff enables transferring an active voice conversation from one agent to another. This is essential for multi-agent workflows where:
- A personal assistant routes to project foremen for focused work
- A foreman delegates to workers for specific tasks
- An agent hands back control when their work is complete
The TV Magazine Show Pattern
Think of voice handoff like a TV magazine show:
- Host (Personal Assistant): Manages the overall conversation, introduces topics
- Specialist Reporter (Project Foreman): Deep dives into specific subjects
- Viewer (User): Experiences seamless transitions between segments
The host doesn't disappear during a specialist segment - they step back and let the specialist take over, then smoothly resume when the segment ends.
Hand-off Process
Step 1: Announce the Transfer
Always tell the user what's happening before transferring:
voicemode:converse(
"Transferring you now to a foreman for the VoiceMode project.",
wait_for_response=False
)
Why announce?
- Users need context about who they'll be talking to
- Prevents confusion when a different voice responds
- Creates natural conversation flow
Step 2: Spawn the Receiving Agent
Start a new agent instance with explicit instructions to use voice. The spawning mechanism depends on your multi-agent setup (subprocess, orchestrator, etc.):
Spawn an agent for [project] with these instructions:
"The user has been transferred to you to help with [task context].
Load the voicemode skill with /voicemode:voicemode.
Then use the converse tool to greet them and ask how you can help."
Key elements in the spawn prompt:
- Context: Why the user is being transferred
- Skill loading: Explicit instruction to load voicemode
- Action: Tell them to use converse to speak
- Greeting: Have them introduce themselves
- Return path: Who to transfer back to (see below)
Handoff Context
Include metadata so the receiving agent knows how to hand back:
You were called by [originator name].
When finished, announce "Transferring you back to [originator name]"
and signal completion via [signaling method].
Recommended context to pass:
| Field | Purpose | Example |
|---|---|---|
| Originator name | For verbal announcement | "Cora", "the assistant" |
| Session ID | For direct messaging (if available) | session_abc123 |
| Signaling method | How to notify completion | "exit", "write to /tmp/done" |
This enables the receiving agent to:
- Address the originator by name when handing back
- Optionally send a direct wake-up signal
- Use the appropriate completion mechanism
Step 3: Go Quiet
After spawning the new agent, stop using the converse tool:
- Only one agent should speak at a time
- Competing for audio creates chaos
- The receiving agent now owns the conversation
# DON'T do this after handoff:
# voicemode:converse("Let me know if you need anything else!") # BAD
# DO: Stay silent and let the new agent take over
Step 4: Monitor (Optional)
If your orchestration system allows you to observe the receiving agent's interactions, you can confirm the handoff succeeded before fully going quiet.
What to look for:
- Skill loaded successfully
- First converse call made (confirms they're speaking)
- User response received (confirms audio round-trip works)
Confirming handoff via converse:
If you can observe the receiving agent's tool calls, watch for their first converse call. Once they've started speaking to the user, you know the handoff worked. This is more reliable than just watching for process startup.
If handoff fails:
If the receiving agent doesn't start speaking within a reasonable time:
- Resume the conversation yourself
- Apologize and explain there was an issue
- Either retry the handoff or continue directly
Hand-back Process
When the receiving agent's work is complete, they must:
- Announce the return to the user
- Signal the originating agent to resume
- Go quiet
Step 1: Announce the Return
Use the originator's name (passed during handoff):
voicemode:converse(
"I've finished reviewing the task. Transferring you back to Cora.",
wait_for_response=False
)
Step 2: Signal the Originating Agent
The originating agent needs to know when to resume speaking. Choose a signaling method based on your orchestration system:
| Method | How It Works | Best For |
|---|---|---|
| Process exit | Originator waits for subprocess to end | Simple spawning |
| File signal | Write to agreed-upon path (e.g., /tmp/handback-ready) |
Any setup |
| Direct message | Send to originator's session ID | Frameworks with messaging |
| Status polling | Originator polls for "completed" status | Orchestration systems |
Example: File-based signaling
# Receiving agent signals completion
write_file("/tmp/handback-ready", "done")
# Originating agent detects and resumes
while not exists("/tmp/handback-ready"):
sleep(1)
delete_file("/tmp/handback-ready")
voicemode:converse("I'm back! How did it go with the project agent?")
Example: Process-based (simplest)
# Originating agent spawns and waits
process = spawn_agent(...)
process.wait() # Blocks until receiving agent exits
voicemode:converse("Welcome back! The project agent has finished.")
Step 3: Go Quiet and Exit
After signaling, stop using converse immediately:
# Signal sent, now go quiet
# Exit or idle - originating agent takes over
Important: The receiving agent must not call converse after signaling, or audio will conflict with the resuming originator.
Complete Example
Primary Agent Initiating Handoff
# User asks to work on a project
user_request = "I want to work on the voice handoff documentation"
# Announce transfer
voicemode:converse(
"Great! Let me transfer you to a project agent for VoiceMode.",
wait_for_response=False
)
# Spawn project agent with different voice (mechanism depends on your setup)
# Key: include handoff context in the prompt
spawn_agent(
project="~/Code/voicemode",
prompt="""The user wants to work on voice handoff documentation.
HANDOFF CONTEXT:
- You were called by: Cora (the personal assistant)
- When finished: Announce "Transferring you back to Cora" then exit
Load the voicemode skill with /voicemode:voicemode.
Use converse to greet the user and ask which aspect of handoff docs they'd like to focus on.""",
env={"VOICEMODE_TTS_VOICE": "alloy"} # Different voice
)
# Go quiet while project agent works
# Wait for project agent to exit, then resume
process.wait()
voicemode:converse("Welcome back! How did it go with the project agent?")
Project Agent Greeting and Working
# Project agent's first action after loading skill
voicemode:converse(
"Hey! I'm the VoiceMode project agent. I understand you want to work on handoff documentation. Would you like to focus on the hand-off process, hand-back process, or the examples section?",
wait_for_response=True
)
# Continue conversation based on response
# ... do work ...
Project Agent Handing Back
# Work complete - use originator name from handoff context
voicemode:converse(
"I've updated the handoff documentation and committed the changes. Transferring you back to Cora.",
wait_for_response=False
)
# Exit cleanly - this signals Cora to resume
# (Cora is waiting on process.wait())
Voice Configuration
Different Voices for Different Agents
Make handoffs audible by using distinct voices:
| Agent Type | Suggested Voice | Why |
|---|---|---|
| Primary/Orchestrator | User preference (e.g., nova) |
Familiar, comfortable |
| Project Agent | alloy or onyx |
Professional, distinct |
| Task Agent | echo or fable |
Different again |
Setting Voice per Agent
Via environment variable (when spawning):
VOICEMODE_TTS_VOICE=alloy # Set before spawning agent
Via project .voicemode file:
# In project root
echo "VOICEMODE_TTS_VOICE=onyx" > .voicemode
Via converse parameter:
voicemode:converse("Hello!", voice="alloy", tts_provider="kokoro")
Voice Fallback Chain
VoiceMode uses VOICEMODE_VOICES for fallback:
# Try Kokoro's af_alloy first, fall back to OpenAI's alloy
export VOICEMODE_VOICES=af_alloy,alloy
Troubleshooting
New Agent Doesn't Speak
- Check skill loaded: Look for "Successfully loaded skill" in output
- Verify services:
voicemode:service("whisper", "status") - Check prompt: Ensure instructions say to use converse
Audio Conflicts
- Multiple agents speaking: Only one should have converse active
- Check for competing calls: Search output for "converse" calls
- Use The Conch: See multi-agent coordination
User Can't Hear New Agent
- Check TTS service:
voicemode:service("kokoro", "status") - Verify audio output: Same device as before handoff?
- Check voice setting: Is the voice available?
Handoff Feels Abrupt
- Add context: More detail in announcement
- Warm greeting: Have receiving agent introduce themselves
- Slower pace: Add brief pauses if needed
Best Practices
- Always announce: Never transfer without telling the user
- Provide context: Tell the receiving agent why the user is coming
- One speaker: Go completely quiet after handoff
- Distinct voices: Make it obvious who's speaking
- Graceful exits: Announce before handing back
- Monitor first few: Confirm handoffs work before going fully async
Related Patterns
- Call Routing Overview: All routing patterns
- Voice Proxy: Relay for agents without voice
- Multi-Agent Coordination: The Conch lock system (planned)
- Call Waiting: Handling multiple waiting agents (planned)
- Voicemail: Async message passing (planned)