> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zeroruntime.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Warm Transfer

> Hand a live SIP call to a human agent after briefing them on the conversation so far.

Warm transfer hands a live SIP call to a human agent, such as a supervisor, after briefing them on the call. Instead of connecting the caller to the human directly, a transfer agent joins the human in a separate room, summarizes the conversation, and only then connects the caller. The human picks up already knowing who is calling and why.

## How Warm Transfer Works

1. The caller asks to speak to a person, and the agent calls `session.warm_transfer()` from a function tool.
2. An LLM summarizes the conversation so far.
3. The caller is put on hold, and a separate consultation room is created.
4. The human agent is dialed over SIP into the consultation room.
5. A transfer agent in that room briefs the human agent on the caller.
6. The caller is switched into the call with the human agent, and the agent leaves.

<Note>
  * **Recording:** when recording is enabled, only the conversations with an agent are recorded: the caller with your agent, and the transfer agent briefing the human agent. The conversation between the caller and the human agent after they are connected is not recorded.
  * **Sessions:** the call between the caller and your agent, and the briefing between the transfer agent and the human agent, are stored as separate sessions.
</Note>

## Prerequisites

The human agent is dialed through a SIP routing rule. Create one on the dashboard for the transfer, and leave its **Agent ID** (`agentId`) field **empty**, so the call goes to the human agent rather than being dispatched to an AI agent. See [Routing Rules](/telephony/call-routing/setting-up-routing-rules).

Warm transfer runs on a live SIP call, so start the session from an inbound or outbound phone call.

## Trigger a Warm Transfer

Call `session.warm_transfer()` from a function tool so the agent decides when to escalate. It waits for the whole transfer, which can take minutes, so raise the agent's `tool_timeout_seconds` (30 seconds by default) to cover it.

<CodeGroup>
  ```python title="Python" Python theme={null}
  import os
  from zeroruntime import Agent, Pipeline, SIPDestination, WarmTransferConfig, function_tool

  class CustomerServiceAgent(Agent):
      def __init__(self, pipeline: Pipeline):
          super().__init__(
              instructions=(
                  "You are a helpful customer service agent. If the caller asks for a "
                  "manager or needs a human, call the escalate_to_human tool."
              ),
              pipeline=pipeline,
              tool_timeout_seconds=400,
          )

      @function_tool
      async def escalate_to_human(self, reason: str) -> str:
          """Escalate this call to a human supervisor with a warm transfer.

          Args:
              reason: Short description of why the escalation is happening.
          """
          config = WarmTransferConfig(
              destination=SIPDestination(
                  routing_rule_id=os.getenv("WARM_TRANSFER_ROUTING_RULE_ID"),
                  sip_call_to=os.getenv("WARM_TRANSFER_TO"),      # Human agent's number (E.164)
                  sip_call_from=os.getenv("WARM_TRANSFER_FROM"),  # Caller-ID to present
              ),
          )
          result = await self.session.warm_transfer(config)
          if result.success:
              return "Connected to a supervisor."
          return "I couldn't reach a supervisor right now. Let me keep helping you."
  ```

  ```typescript title="Node JS" Node JS theme={null}
  import {
    Agent,
    Pipeline,
    SIPDestination,
    WarmTransferConfig,
    function_tool,
  } from '@zeroruntime/js-sdk';

  class CustomerServiceAgent extends Agent {
    constructor(pipeline: Pipeline) {
      super({
        instructions:
          'You are a helpful customer service agent. If the caller asks for a ' +
          'manager or needs a human, call the escalate_to_human tool.',
        pipeline,
        tool_timeout_seconds: 400,
      });
    }

    escalate_to_human = function_tool({
      name: 'escalate_to_human',
      description: 'Escalate this call to a human supervisor with a warm transfer.',
      parameters: {
        reason: { type: 'string', description: 'Short description of why the escalation is happening.' },
      },
      execute: async () => {
        const config = WarmTransferConfig({
          destination: SIPDestination({
            routing_rule_id: process.env.WARM_TRANSFER_ROUTING_RULE_ID ?? '',
            sip_call_to: process.env.WARM_TRANSFER_TO ?? '',      // Human agent's number (E.164)
            sip_call_from: process.env.WARM_TRANSFER_FROM ?? '',  // Caller-ID to present
          }),
        });
        const result = await this.session!.warm_transfer(config);
        if (result.success) return 'Connected to a supervisor.';
        return "I couldn't reach a supervisor right now. Let me keep helping you.";
      },
    });
  }
  ```
</CodeGroup>

<Tip>
  `sip_call_from` must be a number the routing rule is authorized to send. Otherwise the carrier may reject or drop the call, and the human agent never joins.
</Tip>

## Listen for Phase Events

Register a handler with `session.on_warm_transfer()` to follow the transfer. With no phase it receives every phase; pass a phase to receive only that one. The handler gets a dict with `phase`, `data`, `timestamp`, and `consultation_room_id`.

<CodeGroup>
  ```python title="Python" Python theme={null}
  from zeroruntime import WarmTransferPhase

  @session.on_warm_transfer()
  def on_any_phase(payload):
      print(f"[warm transfer] {payload['phase'].value}")

  @session.on_warm_transfer(WarmTransferPhase.TRANSFER_COMPLETE)
  def on_done(payload):
      print("Caller is now talking to a supervisor.")
  ```

  ```typescript title="Node JS" Node JS theme={null}
  import { WarmTransferPhase } from '@zeroruntime/js-sdk';

  session.on_warm_transfer(null, (payload) => {
    console.log(`[warm transfer] ${payload.phase}`);
  });

  session.on_warm_transfer(WarmTransferPhase.TRANSFER_COMPLETE, () => {
    console.log('Caller is now talking to a supervisor.');
  });
  ```
</CodeGroup>

The phases, in order:

| Phase | Meaning |
| :- | :- |
| `started` | The transfer began. |
| `summary_generating`, `summary_ready` | The briefing summary is being written, then done. |
| `caller_on_hold` | The caller is on hold. |
| `consultation_room_created` | The consultation room exists. |
| `supervisor_dialed`, `supervisor_joined` | The human agent is being dialed, then joins. |
| `briefing_started`, `briefing_complete` | The transfer agent briefs the human agent. |
| `call_switched` | The caller is connected to the human agent. |
| `transfer_complete` | The transfer finished and the agent left. |
| `transfer_cancelled`, `transfer_failed` | The transfer ended early. |

## Customize the Briefing

Only `destination` is required. The other `WarmTransferConfig` fields tune the briefing:

| Field | Default | Description |
| :- | :- | :- |
| `summary_llm` | The session's LLM | A separate model for the summary. A fast, cheap model works well, since the caller is on hold while it runs. |
| `summary_prompt` | Built-in 150-word briefing | Overrides the summary prompt. |
| `briefing_pipeline` | The call's provider classes, created with no arguments | The pipeline the transfer agent runs on. Pass one if your providers need model ids, voice ids, or keys not in the environment. |
| `supervisor_join_timeout` | `120` | Seconds to wait for the human agent to pick up. |
| `briefing_timeout` | `180` | Seconds allowed for the briefing. |
| `room` | The call's settings | Recording and observability for the consultation room. A `Room` overrides only the fields you set. Python only. |
| `hold_message` | None | Said to the caller as they go on hold. Python only. |
| `failure_message` | None | Said to the caller if the transfer fails. Python only. |

```python title="main.py" theme={null}
from zeroruntime import Pipeline
from zeroruntime.plugins import CartesiaTTS, DeepgramSTT, GoogleLLM, SileroVAD
from zeroruntime.inference import TurnDetector

config = WarmTransferConfig(
    destination=SIPDestination(routing_rule_id="rr_xxx", sip_call_to="+1...", sip_call_from="+1..."),
    summary_llm=GoogleLLM(),
    summary_prompt="Summarize the call for a supervisor in under 100 words.",
    briefing_pipeline=Pipeline(
        stt=DeepgramSTT(),
        llm=GoogleLLM(),
        tts=CartesiaTTS(),
        vad=SileroVAD(),
        turn_detector=TurnDetector(),
    ),
    hold_message="Please hold while I connect you to a supervisor.",
)
```

## Result

`session.warm_transfer()` returns a `WarmTransferResult`:

| Field | Description |
| :- | :- |
| `success` | Whether the caller ended up with the human agent. |
| `phase` | The phase the transfer reached. On a failure, where it stopped. |
| `consultation_room_id` | The consultation room, if one was created. |
| `supervisor_call_id` | The outbound SIP call to the human agent. |
| `switched_call_id` | The caller's call after being connected to the human agent. |
| `summary` | The generated briefing, even if the transfer failed. |
| `error` | Why the transfer failed, if it did. |

A transfer that fails, for example because nobody answers, returns with `success` set to false instead of raising. If no result arrives in time, `warm_transfer()` raises a timeout error. By default it waits `supervisor_join_timeout + briefing_timeout` plus 60 seconds; pass `timeout` to change that.

<Note>
  See [Handoff vs Transfer](/runtime-sdk/build/context-management/agent-handoffs#handoff-vs-transfer) for the difference between warm transfer, call transfer, and agent handoff.
</Note>

## What's Next

<CardGroup cols={2}>
  <Card title="Call Transfer" icon="phone-arrow-right" href="/runtime-sdk/build/telephony/call-transfer">
    Forward a call to another number without a briefing.
  </Card>

  <Card title="DTMF Events" icon="hashtag" href="/runtime-sdk/build/telephony/dtmf">
    Capture caller key presses.
  </Card>
</CardGroup>

## References

* [Warm Transfer (Example)](https://github.com/ZeroRuntimeAI/zeroruntime-python-examples/blob/main/telephony/warm_transfer.py)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.