# 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:

1. **Host (Personal Assistant)**: Manages the overall conversation, introduces topics
2. **Specialist Reporter (Project Foreman)**: Deep dives into specific subjects
3. **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:

```python
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 | "the assistant", "the orchestrator" |
| 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:
1. Address the originator by name when handing back
2. Optionally send a direct wake-up signal
3. 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

```python
# 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:
1. Announce the return to the user
2. Signal the originating agent to resume
3. Go quiet

### Step 1: Announce the Return

Use the originator's name (passed during handoff):

```python
voicemode:converse(
    "I've finished reviewing the task. Transferring you back to the assistant.",
    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**
```python
# 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)**
```python
# 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:

```python
# 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

```python
# 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: the personal assistant
    - When finished: Announce "Transferring you back to the assistant" 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

```python
# 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

```python
# Work complete - use originator name from handoff context
voicemode:converse(
    "I've updated the handoff documentation and committed the changes. Transferring you back to the assistant.",
    wait_for_response=False
)

# Exit cleanly - this signals the assistant to resume
# (the assistant 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):**
```bash
VOICEMODE_TTS_VOICE=alloy  # Set before spawning agent
```

**Via project .voicemode file:**
```bash
# In project root
echo "VOICEMODE_TTS_VOICE=onyx" > .voicemode
```

**Via converse parameter:**
```python
voicemode:converse("Hello!", voice="alloy", tts_provider="kokoro")
```

### Voice Fallback Chain

VoiceMode uses `VOICEMODE_VOICES` for fallback:
```bash
# Try Kokoro's af_alloy first, fall back to OpenAI's alloy
export VOICEMODE_VOICES=af_alloy,alloy
```

## Troubleshooting

### New Agent Doesn't Speak

1. **Check skill loaded**: Look for "Successfully loaded skill" in output
2. **Verify services**: `voicemode:service("whisper", "status")`
3. **Check prompt**: Ensure instructions say to use converse

### Audio Conflicts

1. **Multiple agents speaking**: Only one should have converse active
2. **Check for competing calls**: Search output for "converse" calls
3. **Use The Conch**: See [multi-agent coordination](./coordination.md)

### User Can't Hear New Agent

1. **Check TTS service**: `voicemode:service("kokoro", "status")`
2. **Verify audio output**: Same device as before handoff?
3. **Check voice setting**: Is the voice available?

### Handoff Feels Abrupt

1. **Add context**: More detail in announcement
2. **Warm greeting**: Have receiving agent introduce themselves
3. **Slower pace**: Add brief pauses if needed

## Best Practices

1. **Always announce**: Never transfer without telling the user
2. **Provide context**: Tell the receiving agent why the user is coming
3. **One speaker**: Go completely quiet after handoff
4. **Distinct voices**: Make it obvious who's speaking
5. **Graceful exits**: Announce before handing back
6. **Monitor first few**: Confirm handoffs work before going fully async

## Related Patterns

- **[Call Routing Overview](./README.md)**: All routing patterns
- **[Voice Proxy](./proxy.md)**: Relay for agents without voice
- **[Multi-Agent Coordination](./coordination.md)**: The Conch lock system (planned)
- **[Call Waiting](./call-waiting.md)**: Handling multiple waiting agents (planned)
- **[Voicemail](./voicemail.md)**: Async message passing (planned)

