steep-balloon-41261
03/22/2026, 10:59 AMtall-airline-16756
03/22/2026, 10:59 AMtall-airline-16756
03/22/2026, 10:59 AMcuddly-vr-85751
03/22/2026, 11:00 AMtall-airline-16756
03/22/2026, 11:00 AMLIVEKIT_SIP_OUTBOUND_TRUNK or SIP_OUTBOUND_TRUNK_ID).
Before I give a concrete code suggestion, two quick clarifying questions so I tailor the example to your setup:
1. Are you running a Python Agent in the LiveKit Agents Beta (yes/no)?
2. Do you want the transfer initiated from the AI agent (agent asks supervisor) or from an external trigger (e.g., UI button or supervisor request)?
If you confirm “yes” to (1) and that you want the AI agent to start the transfer, here’s a concise example of how to call WarmTransferTask from your session flow and handle the result. Paste this into the place where you decide to transfer (for example inside your session logic before you call await session.start(...) ends or when agent decides to transfer):
from livekit.agents.beta.workflows import WarmTransferTask
# Example usage inside your async agent handler when you want to warm-transfer:
result = await WarmTransferTask(
target_phone_number="<supervisor-phone-number>", # required
sip_trunk_id="<outbound-trunk-id>", # required or rely on LIVEKIT_SIP_OUTBOUND_TRUNK env var
chat_ctx=session.history, # pass conversation history/context
extra_instructions="Supervisor: caller needs billing escalation", # optional
hold_audio=None, # optional: defaults to built-in hold music
)
# result is a WarmTransferResult with info like human_agent_identity
if result and result.human_agent_identity:
# transfer accepted/connected — handle cleanup or handoff
# e.g., mark this session transferred and stop AI audio I/O
pass
else:
# declined or failed — resume as needed
pass
Helpful implementation notes
• Set LIVEKIT_SIP_OUTBOUND_TRUNK (or pass sip_trunk_id) to your outbound SIP trunk ID so the task can dial the supervisor.
• You can customize hold_audio or extra_instructions to provide richer context to the human agent.
• The task handles placing the caller on hold and merging the human agent into the call when they accept; your agent should stop its I/O and exit gracefully once the human is connected.
• Ensure your SIP provider allows outbound calling and caller-ID passthrough if you need to preserve the original caller number.
Would you like:
• A version of the example adapted to where in your pasted code it should be called (point me to the exact function/line you want to modify), or
• An example showing how to set the env var and test the transfer end-to-end with a test-number?
Tell me which and I’ll provide the tailored code snippet and next steps.
Sources: WarmTransferTask | LiveKit Documentation | Outbound Calling Script | LiveKit Documentation | Preserving Caller ID During SIP Transfers in LiveKit Agents, with interactive elementscuddly-vr-85751
03/22/2026, 12:26 PMtall-airline-16756
03/22/2026, 12:26 PMLIVEKIT_SIP_OUTBOUND_TRUNK or
◦ transfer_context["sip_trunk_id"] contains the correct outbound trunk ID.
1. Are you running this inside a telephony-enabled agent session (i.e., caller is in a telephony room and ctx.room is valid)?
2. Any logs from WarmTransferTask (if you have them) or from LiveKit server around the time of the transfer?
While you gather that, here are targeted code corrections and improvements you can apply now — these address common issues and some problems I see in your code.
1) Ensure WarmTransferTask usage matches the API
• WarmTransferTask typically accepts target_phone_number (or destination depending on version) and sip_trunk_id. In your code you pass room=ctx.room and destination=.... Confirm the correct parameter names for your installed LiveKit Agents Beta release.
• Safe approach: pass both the explicit room and target_phone_number (or destination) that the library expects. If your version expects target_phone_number, change the call to use that name.
Example change (try this if your library expects target_phone_number):
warm_transfer = WarmTransferTask(
room=ctx.room,
target_phone_number=f"tel:{destination}" if not destination.startswith("sip:") else destination,
sip_trunk_id=sip_trunk_id,
caller_id=caller_id,
)
2) Start / wait / complete flow — use start(), wait_for_answer(), then complete()
• You call await warm_transfer.start() then await warm_transfer.wait_for_answer(timeout=30) which is correct. Make sure you handle the cases where answer never arrives and log returned state after start.
Add defensive logging and state checks:
await warm_transfer.start()
# log start result/state if available
try:
await warm_transfer.wait_for_answer(timeout=30)
except asyncio.TimeoutError:
await warm_transfer.cancel()
raise ToolError("The agent did not answer within 30 seconds")
# optionally inspect warm_transfer.result or warm_transfer.state here
3) Use correct methods to bridge/complete
• You call await warm_transfer.complete() and then agent_ref.session.shutdown(). Confirm complete() is the correct method to bridge the human agent and caller in your installed version. Some versions may use warm_transfer.connect() or return a WarmTransferResult to act upon. If complete() returns a result, capture it.
Example:
transfer_result = await warm_transfer.complete()
# inspect transfer_result for success/human_agent_identity
4) Don’t call session.say after the human answers on the dial leg
• In your flow you use agent_ref.session.say(...) to speak to the human agent after wait_for_answer. That works only if the AI still has a media path to the human leg. If WarmTransferTask created a separate telephony leg for the human, speaking via the original session may not reach them. Instead:
◦ Use the transfer task’s API to send audio to the human leg (if provided), or
◦ Provide the context text as extra_instructions when creating WarmTransferTask so it appears to the human agent automatically.
So either:
• Pass context in `extra_instructions`:
warm_transfer = WarmTransferTask(
room=ctx.room,
target_phone_number=...,
sip_trunk_id=sip_trunk_id,
caller_id=caller_id,
extra_instructions=context # brief summary for human
)
or
• Use the tool methods that inject TTS into the human dial leg (if available in your version).
5) Ensure caller_id format is supported
• _get_caller_id returns either agent SIP number or original caller number. Make sure the caller_id format matches your SIP trunk/provider expectations (E.164 for PSTN, or SIP URI like sip:... for SIP destinations). If your trunk rejects the caller id, the outbound call may fail. Log the final caller_id used.
6) Properly shutdown/cleanup
• After bridge/complete, call await warm_transfer.close() or similar if provided, and then await agent_ref.session.shutdown() only after the bridge is confirmed. Avoid session.shutdown() if you still need to record or check egress — ensure graceful ordering.
7) Minor code fixes / robustness
• Use .startswith("tel:") checks consistently (you remove prefixes then later assume plain numbers). Example:
dest = destination
if dest.startswith("tel:"):
dest = dest[len("tel:"):]
elif dest.startswith("sip:"):
# keep sip: form if needed
pass
# then build target param accordingly
• Ensure transfer_call.__name__ and signatures are correct (you already set them — good).
8) Add logging to capture errors from WarmTransferTask
• Wrap start/wait/complete in try/except and log the full exception and any warm_transfer state. This will make the actual failure clear (SIP reject, network, missing trunk ID, timed out, etc).
What I can do next for you
• If you paste the exact error/stack trace or logs from when you run the transfer, I’ll point to the exact line to change.
• If you tell me which version of livekit-agents you’re using (pip show livekit-agents) I’ll give a drop-in code snippet matched to that version’s WarmTransferTask signature.
• I can produce a ready-to-paste patch of your file with the parameter-name fixes, extra_instructions usage, added logging, and robust error handling.
Which of those would you like me to do next? If you can share the runtime error or confirm the library version, I’ll provide the precise fix.
, with interactive elementscuddly-vr-85751
03/22/2026, 12:27 PMtall-airline-16756
03/22/2026, 12:27 PMcuddly-vr-85751
03/22/2026, 12:29 PM