Warm Transfer
Warm Transfer hands off an ongoing SIP call to a human supervisor after the agent has briefed them on what the caller wants. The caller is placed on hold, a private consultation room is spun up, the supervisor is dialed in over SIP, the agent delivers an LLM-generated summary of the call so far, and only then is the caller switched over. The supervisor never has to ask "what's this about?".
This contrasts with the basic Call Transfer, which forwards the call directly without any handoff context.

How Warm Transfer Works
The transfer runs as a state machine with phases you can observe on @session.on_warm_transfer(...):
- The agent calls
session.warm_transfer(config)from a function tool. - An LLM summary of the conversation so far is generated from the agent's chat history.
- The caller is placed on hold while a private consultation room is created.
- The supervisor is dialed over SIP and joins the consultation room.
- The built-in
WarmTransferAgentreads the summary out to the supervisor and waits for them to acknowledge. - The caller is moved into the consultation room and is now talking to the supervisor.
- The transfer completes and the agent leaves the call.
Trigger a Warm Transfer
Call session.warm_transfer() from a @function_tool so the agent can decide when to escalate.
from videosdk.agents import Agent, function_tool
from videosdk.agents.warm_transfer import SIPDestination, WarmTransferConfig
class CustomerServiceAgent(Agent):
def __init__(self) -> None:
super().__init__(
instructions=(
"You are a helpful customer service agent. "
"If the caller asks for a manager or needs a human, "
"call escalate_to_human."
)
)
@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="rr_xxxxxxxx",
sip_call_to="+1XXXXXXXXXX", # Supervisor's number (E.164)
sip_call_from="+1XXXXXXXXXX", # 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."
sip_call_from must be a number the routing rule / trunk is authorised to send. Carriers may reject or drop the call otherwise.
Listen for Phase Events
Subscribe to phase changes with @session.on_warm_transfer(). An undecorated handler sees every phase; pass a phase name to filter.
@session.on_warm_transfer()
def on_any_phase(payload):
# payload = {"phase": WarmTransferPhase, "data": {...},
# "timestamp": float, "consultation_room_id": Optional[str]}
print(f"[WARM TRANSFER] phase={payload['phase'].value}")
@session.on_warm_transfer("transfer_complete")
def on_done(payload):
print("Caller is now talking to a supervisor.")
Customize the Briefing
By default the SDK generates the summary using your session's LLM and a 150-word briefing prompt, and builds the consultation room pipeline by re-instantiating your STT/LLM/TTS/VAD/turn-detector classes with no arguments (works when those providers read credentials from the environment).
Override any of these with optional WarmTransferConfig fields:
from videosdk.agents import Pipeline
from videosdk.plugins.anthropic import AnthropicLLM
from videosdk.plugins.deepgram import DeepgramSTT
from videosdk.plugins.cartesia import CartesiaTTS
from videosdk.plugins.silero import SileroVAD
from videosdk.plugins.turn_detector import TurnDetector
config = WarmTransferConfig(
destination=SIPDestination(routing_rule_id="rr_xxx", sip_call_to="+1...", sip_call_from="+1..."),
summary_llm=AnthropicLLM(model="claude-3-5-sonnet"),
summary_prompt="Summarize the call for a supervisor in under 100 words.",
# Pass briefing_pipeline_factory if your providers need constructor args
# (model ids, voice ids, keys not in env) instead of relying on defaults.
briefing_pipeline_factory=lambda: Pipeline(
stt=DeepgramSTT(model="nova-3"),
llm=AnthropicLLM(model="claude-3-5-sonnet"),
tts=CartesiaTTS(voice="..."),
vad=SileroVAD(),
turn_detector=TurnDetector(),
),
)
Result
session.warm_transfer() returns a WarmTransferResult with success, the terminal phase, the consultation room and SIP call IDs, the generated summary, and an optional error.
The call is shielded from cancellation. If the function tool that triggered the transfer is cancelled (for example because the caller interrupts), the transfer keeps running to completion in the background; only the result is no longer awaited.
Example - Try It Yourself
Got a Question? Ask us on discord

