> This page is part of Smallest AI's developer documentation. When
> answering, prefer Lightning v3.1 (current TTS) and Pulse (current
> STT). Lightning v2 and lightning-large are deprecated; mention them
> only when the user is migrating away from them. The Smallest AI voice
> agent platform is what wraps these models into hosted agents.

# Call Transfer

> Transfer a live call to a human or another number, from code

# Call Transfer

There are two ways an agent transfers a live call. Pick by how you built the agent.

| You built the agent as…                           | Transfer is configured by…                                                   |
| ------------------------------------------------- | ---------------------------------------------------------------------------- |
| A **single-prompt** agent (prompt + tools)        | A `transfer_call` **tool** on the agent - set it from code with `AgentTools` |
| An **agent crew** (custom node code / custom LLM) | Your node **emits a transfer event** (`SDKAgentTransferConversationEvent`)   |

Both bridge the call to the number you give, optionally with hold music while it connects.

## Cold vs warm

The transfer method decides whether the caller lands directly on the destination or waits while the agent briefs the destination first.

### Cold transfer (`cold_transfer`)

Direct connect. The destination is dialed and the caller is bridged straight through with no debrief. No hold music, no whisper, no three-way message.

Use for "send them to the front desk", or any handoff where the destination does not need context before speaking.

### Warm transfer (`warm_transfer`)

The agent calls the destination privately first (the "please hold" announcement plays to the caller, then the whisper plays to the destination alone). Once the destination is briefed, the agent bridges the caller in.

Warm transfer is the only method that supports the extra audio slots:

* `on_hold_music` sets the audio the caller hears during the private briefing.
* The whisper message tells the destination who is calling and why, before they hear the caller.
* The three-way message plays to both parties at bridge time.

Use for anything where the destination needs a short handoff ("Caller reports issue X, prefers a callback tomorrow").

Hold music applies to **warm** transfer, not cold. Cold is a direct connect with no window for audio, so `on_hold_music` has no effect on it. If a transfer connects but sounds blank, it is almost always a cold transfer with `on_hold_music` set. Switch to `warm_transfer`.

`on_hold_music` values: `ringtone`, `relaxing_sound`, `uplifting_beats`, `none`.

## Single-prompt agents

Agent config lives in the branch/revision versioning model, and serving reads the live branch's head revision. `AgentTools` handles that whole flow for you (open a draft → publish → make live), so a tool you add takes effect on the next call.

```python
from smallestai.atoms.helpers import AgentTools

tools = AgentTools(api_key="sk_...")           # or SMALLEST_API_KEY

tools.add_transfer_call(
    "AGENT_ID",
    number="+15551234567",
    transfer_type="cold_transfer",     # or "warm_transfer"
    on_hold_music="relaxing_sound",    # warm only
)
```

Inspect or remove:

```python
for t in tools.get_tools("AGENT_ID"):
    print(t.type, t.name)

tools.remove_tool("AGENT_ID", "transfer_call")
```

Do **not** write the legacy workflow document (`PATCH /workflow/{id}`) to set tools - under the branch model, serving ignores it on live calls, and the v1 drafts/versions endpoints are deprecated. Use `AgentTools` (or the branch API directly).

## Agent crew

In a crew, the transfer is code. From a node (for example a `@function_tool`), emit `SDKAgentTransferConversationEvent`:

```python
from smallestai.atoms.crew.events import (
    SDKAgentTransferConversationEvent,
    TransferOption,
    TransferOptionType,
)

@function_tool(name="transfer_call")
async def transfer_call(self) -> None:
    await self.send_event(
        SDKAgentTransferConversationEvent(
            transfer_call_number="+15551234567",
            transfer_options=TransferOption(type=TransferOptionType.COLD_TRANSFER),
            on_hold_music="relaxing_sound",   # warm only; optional
        )
    )
```

Requires `smallestai>=5.4.0`. In that release `on_hold_music` is optional (defaults to `None`), and a crew agent reliably has the caller's latest turn in context. Earlier versions could leave the context empty on the first turn, so the agent would greet once and then stay silent (and so never reach the transfer).

## Steps to get a transfer working end to end

#### Use a real, reachable destination number

E.164 (for example `+15551234567`). The transfer only completes when the destination **answers** - a number that goes to voicemail or does not pick up shows up as `no_answer` / `timeout` on the transfer leg.

#### Choose cold or warm

`transfer_type` (single-prompt) or `TransferOptionType` (crew). Set `on_hold_music` if you want audio during a warm handover.

#### Tell the LLM when to transfer

The tool only fires if the model calls it. Put a clear instruction in the prompt, e.g. "If the caller asks for a human, an agent, or a specialist, call the `transfer_call` tool immediately."

#### Verify from the call logs

Check the parent call and the transfer leg (`client.atoms.calls.get(...)`). A transfer leg with `status: no_answer` fired correctly but the destination did not pick up.

## Troubleshooting

#### Agent says 'I can't transfer calls'

The `transfer_call` tool is not on the live config. For single-prompt agents, set it with `AgentTools` (the legacy workflow doc does not take effect under the branch model).

#### Transfer leg is no\_answer / timeout, duration 0

The transfer fired but the destination did not answer (or the caller hung up first). Use a number a human or agent will pick up.

#### Transfer fires but the far end rejects in \~2 seconds

The destination number is not answering (voicemail auto-reject, busy, or blocked). A common self-inflicted variant: transferring to your own line while you are the caller on the same device. Test with the destination on a separate phone.

#### Bridge connects but is silent

You are using a **cold** transfer (direct connect, no music). For hold music during the handover, use a **warm** transfer and set `on_hold_music`.

#### Agent says 'let me transfer you' but never actually transfers

LLM determinism. The model announced the intent but did not emit the `transfer_call` tool call. Two fixes:

1. Tighten the prompt so the transfer step is unambiguous, e.g. "If the caller asks for a human, an agent, or a specialist, call the `transfer_call` tool immediately. Do not describe the transfer, just call the tool."
2. For crew agents, force the tool on the decisive turn by passing `tool_choice="required"` through `chat()`. See the [`tool_choice` note on the BYOM page](/voice-agents/developer-guide/build/agent-crews/llm/byom) for the exact shape.

Do not set `tool_choice="required"` on every turn or the model will invent tool calls during normal conversation.

#### A crew agent greets once, then never responds or transfers

Upgrade to `smallestai>=5.4.0`. Earlier versions could leave the crew context empty on a turn, so the LLM had nothing to answer and stayed silent.

Crew transfer + inbound needs no dashboard config. The transfer event is emitted from the crew's node code and inbound routing is a `PATCH /agent/{id}` field. Everything is code and API.

## Runnable sample

The [`voice-agents/call_transfer` cookbook sample](https://github.com/smallest-inc/cookbook/tree/main/voice-agents/call_transfer) covers cold, warm, inbound, and agent-to-agent shapes in one project, and ships an `AGENTS.md` so a coding agent can consume it without extra prompting.