I'd like to transfer a call to a human expert (not...
# ask-ai
c
I'd like to transfer a call to a human expert (not an AI supervisor). Cold trasnfer (Refer protocol doesn't work with my phone provider). doesn't work, can I use warm transfer call fro that? I don't nee an another agent in the new room, I just want the participant to talk to the human supervisor
✅ 1
t
Thanks for reaching out, Yousri. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I hear you: cold transfer via SIP REFER won’t work with your provider, and you want the caller connected to a human supervisor (no AI or extra agent left in the final call). Yes — use a warm transfer. What happens and your options: • Warm transfer will place the caller on hold, create a brief private consultation to pass context, dial the human supervisor via your outbound SIP trunk, then move the supervisor into the caller’s room and disconnect the agent. The final call is human-to-human with no AI/agent left in the room. • To preserve the caller’s phone number for the supervisor, set the
sip_number
to the original caller ID when creating the outbound SIP leg; your SIP trunk must allow caller-ID passthrough. If you prefer to avoid the brief internal consultation flow, you can instead directly dial the supervisor into the caller’s room using
CreateSIPParticipant
(no warm-transfer orchestration, full manual control). Quick clarifying question so I can give exact steps for your setup: • Do you want the recommended prebuilt WarmTransferTask flow, or do you want the direct
CreateSIPParticipant
approach? Sources: Agents telephony integration | LiveKit Documentation | WarmTransferTask | LiveKit Documentation | The Handoff Pattern for Voice Agents That Replaces IVR Menus | LiveKit | The Human-in-the-Loop (HITL) Pattern for Voice Agents | LiveKit | Preserving Caller ID During SIP Transfers in LiveKit Agents, with interactive elements
c
I want the warm transfer flow. I've tried it using the livekit python examples. I have no error but the waiting music never stops, and I cannot hear the supervisor on the phone when he picks it up. This is the code I used: sync def dial_transfer(self, phone_number: str): """Dial a transfer target via SIP trunk and wait for them to join.""" trunk_id = self.room_metadata.get("trunk_id") result = await WarmTransferTask( sip_call_to=phone_number, sip_trunk_id=trunk_id, sip_number="+33600000000", ) logger.info( "transfer to supervisor successful", extra={"supervisor_identity": result.human_agent_identity}, )
t
Thanks — I hear you: you used the warm transfer flow, no errors are thrown, but hold music never stops and the supervisor’s audio isn’t heard. A few targeted checks and quick diagnostics will narrow this down. Can you confirm the following so I can give precise next steps? 1. SIP trunk details • Which SIP trunk provider and
sip_trunk_id
are you using? • Does the trunk require verified caller ID or restrict ANI passthrough? 1. WarmTransferTask call params & code • Are you passing
chat_ctx
or
hold_audio
anywhere else? • Please paste the exact
WarmTransferTask(...)
call you ran (you showed a snippet; confirm it’s identical to the example you used). 1. Signs from the call side • Does the supervisor’s phone show the incoming call and answer normally? • Any inbound SIP logs from the trunk when the supervisor answers (SIP 200 OK / ACK)? 1. Media / network checks • Do your SIP trunk and LiveKit deployment allow RTP to flow (no blocked RTP ports, correct NAT settings)? • Can you confirm you can make a normal outbound SIP call via this trunk (not via WarmTransfer) and hear two-way audio? While you gather that, try these immediate troubleshooting steps: • Set
hold_audio=None
(or a short test clip) to verify the code is actually stopping hold playback when the supervisor connects. • Verify the trunk is configured to allow audio bridging (some trunks only signal but don’t forward media unless media-relay is enabled). • Test dialing the supervisor with a direct
CreateSIPParticipant
call into the same room to see whether audio works without the WarmTransfer orchestration. If you share the answers and any SIP logs (INVITE/200/ACK) or the slightly fuller code around how you call
WarmTransferTask
, I’ll give exact checks and code changes to fix the hold-music and one-way audio behavior. Sources: WarmTransferTask | LiveKit Documentation | The Handoff Pattern for Voice Agents That Replaces IVR Menus | LiveKit | The Human-in-the-Loop (HITL) Pattern for Voice Agents | LiveKit | Preserving Caller ID During SIP Transfers in LiveKit Agents, with interactive elements
c
1. That's the exact arguments I pass to WarmTransferTask
t
Thanks — got it: you’re passing those exact arguments to
WarmTransferTask
. Next steps to diagnose the stuck hold-music / one-way audio issue — please provide any of the following you can: • SIP trunk provider name and the
sip_trunk_id
you configured. • Whether your trunk requires verified caller ID or blocks ANI passthrough. • SIP signaling logs for the supervisor leg (INVITE, 200 OK, ACK) from your trunk or SBC. • RTP/media confirmation: can you share whether RTP packets are seen between LiveKit and your trunk when the supervisor answers? (pcap or RTP counters help.) • Whether a direct outbound SIP call using the same trunk (not WarmTransfer) results in two-way audio. While you gather that, try these quick checks and tell me results: 1. Re-run with
hold_audio=None
(or a short clip) to see if hold stops when the supervisor answers. 2. Test
CreateSIPParticipant
directly into the caller’s room to confirm audio bridging outside WarmTransfer. 3. Confirm your trunk/SBC allows media relay (not just SIP signaling) and that RTP ports are open (no NAT/firewall blocking). Send the logs/output from one WarmTransfer attempt (signaling + any agent/task logs). With those I’ll point to the specific config or code change to stop the hold music and restore two-way audio. , with interactive elements
c
Now, the problem I have is that warm transfer will create a room. How can I differentiate this room from the room that are created after an inbound call. I don't want my inbound agent to be dispatched to the new room created after warm trasnfer
t
Thanks — you want to make sure your inbound agent is not dispatched to the private consultation room WarmTransferTask creates. You can prevent that by controlling dispatch based on room origin/metadata and using explicit dispatch. Two recommended approaches: • Use explicit agent dispatch (set
agent_name
for your inbound agents): when
agent_name
is set, automatic dispatch is disabled and agents only join rooms you explicitly assign. This prevents inbound agents from being sent to any internal consultation rooms. • Filter dispatch by room naming/metadata or SIP dispatch rules: ◦ Inbound-call rooms usually follow your inbound naming pattern (e.g.,
call-...
). WarmTransferTask creates an internal consultation room with a different/internal name, so make your dispatch rules match your inbound room naming pattern only. ◦ Use
SIPDispatchRule
/
room_config.agents
to specify which agents should join rooms created from inbound calls. ◦ If you attach room metadata or participant metadata when creating inbound rooms, have your dispatch logic check that metadata (or
JobContext.room
) and only dispatch inbound agents when the metadata marks the room as an inbound-call room. Quick concrete steps 1. Set inbound agents with
agent_name
so they aren’t auto-dispatched. 2. Create dispatch logic or SIP dispatch rules that match only your inbound-room name pattern or check a metadata flag like
origin: inbound_sip
. 3. Let WarmTransferTask create its consultation room (it won’t match your inbound pattern), or explicitly ignore rooms without the inbound metadata in your dispatch code. If you want, tell me how you currently dispatch agents (agent_name use, SIP rules, or token-based dispatch) and I’ll give the exact rule or code snippet to implement this. Sources: The Handoff Pattern for Voice Agents That Replaces IVR Menus | LiveKit | Agent dispatch | LiveKit Documentation | Avoiding Room ID Reuse When Re-Creating Rooms With the Same Name | sip | livekit/livekit_composite | DeepWiki, with interactive elements
c
I already have specific name per agent. How do you specify the inbound room name pattern creation
t
Good — since your agents already have specific names, the simplest reliable approach is to make inbound-call rooms follow a predictable name pattern and use that pattern in your dispatch rules so they ignore warm-transfer consultation rooms. How to do that: 1) Configure inbound room naming via your SIP dispatch rule • When creating a SIP dispatch rule, set
room_name
or
room_name_template
so inbound calls always create rooms with a known prefix/suffix (e.g.,
call-{call_id}
or
inbound-{timestamp}-{uuid}
). • Example (conceptual JSON for a SIPDispatchRule):
Copy code
{
  "match": {
    "from": "+1555123..."
  },
  "room_config": {
    "room_name_template": "inbound-call-{call_sid}",
    "agents": [
      "your-inbound-agent-name"
    ]
  }
}
• With a template like
inbound-call-{call_sid}
, every inbound call room will start with
inbound-call-...
. 2) Make your agent dispatch logic match that pattern • Only auto-dispatch agents when the created room name matches your inbound pattern (e.g., starts with
inbound-call-
) or when a room metadata flag like
origin: inbound_sip
is present. • If you use token-based or API dispatch, check
JobContext.room
or the room name during assignment and skip dispatch for rooms that don’t match. 3) Keep WarmTransferTask rooms isolated • WarmTransferTask creates internal consultation rooms with system/internal names that won’t match your inbound pattern. By dispatching only rooms that match the inbound template (or have inbound metadata), your inbound agents won’t be sent to those consultation rooms. If you’d like, tell me: • the exact room name template you want to use (example:
inbound-call-{call_sid}
), and • whether you dispatch via SIP rules or a custom dispatcher, and I’ll give a precise JSON snippet or code check you can drop into your dispatcher. , with interactive elements
c
This looks a bit fragile, if anyone in the team creates a SIP Dipsatch Rule without the correct pattern it will mess up the dispatch. Is there a less error prone, more fundamental, way of differencitaing consultation rooms from room created after an inbound call
t
You're right — name patterns are fragile. Use explicit metadata + explicit dispatch so consultation rooms are fundamentally different. Recommended approach (minimal, robust): 1) Tag inbound rooms at creation • When your SIP dispatch rule or room-creation API creates an inbound call room, include metadata: e.g., room.metadata = {"type":"inbound_call"}. 2) Ensure WarmTransferTask consultation rooms are tagged (or left untagged) • WarmTransferTask creates internal consultation rooms. If you control creation, set metadata: {"type":"consultation"}; otherwise treat rooms without inbound metadata as non-inbound. 3) Use explicit agent dispatch (already have agent names) • Only dispatch inbound agents when the room metadata
type == "inbound_call"
. • Don’t rely on default auto-dispatch; require
RoomConfiguration
entry or an explicit API call to assign agents. Agent-side check (conceptual):
Copy code
# on agent startup for a room/job
if room.metadata.get("type") != "inbound_call":
    # skip/join no-op; do not handle this room
    return
# proceed with inbound handling
SIP dispatch rule (conceptual) — create inbound rooms with metadata:
Copy code
{
  "match": {
    "trunk": "your-inbound-trunk"
  },
  "room_config": {
    "room_name_template": "inbound-{call_id}",
    "room_metadata": {
      "type": "inbound_call"
    },
    "agents": [
      "inbound_agent_name"
    ]
  }
}
Why this is safer: • Metadata is data-driven and resistant to human naming mistakes. • Explicit dispatch prevents agents from being auto-assigned to ephemeral consultation rooms. • You can add further guards (check participant types, presence of SIP caller) if desired. If you want, tell me whether you control SIPDispatchRule creation (yes/no) and whether you can set room metadata there — I’ll produce the exact JSON or agent-check snippet to drop into your dispatcher. Sources: Job lifecycle | LiveKit Documentation | Agent dispatch | LiveKit Documentation | WarmTransferTask | LiveKit Documentation | The Human-in-the-Loop (HITL) Pattern for Voice Agents | LiveKit | sip | livekit/livekit_composite | DeepWiki, with interactive elements
c
Explain how can I tag consultation room, I just want to pass them metadata. I don't see a parameter in WarmTrasnferTask
t
Thanks — good question. WarmTransferTask does not expose a parameter to set metadata on the internal consultation room. Two reliable options to add metadata (so your dispatcher can ignore consultation rooms): Option A — Add metadata after the consultation room is created • Listen for the WarmTransferTask lifecycle/event that provides the consultation room ID (or watch room-created events). • Call the Update Room API (or server SDK equivalent) with that room ID to set metadata: e.g.,
room.metadata = {"type":"consultation"}
. • Your agent dispatcher then checks room.metadata and skips rooms where
type != "inbound_call"
. Option B — Implement the warm-transfer flow yourself (full control) • Create the consultation room yourself with metadata via CreateRoom. • Dial the supervisor with CreateSIPParticipant into that consultation room. • After briefing, MoveParticipant the supervisor into the caller’s room and remove agents. • This gives you metadata at creation time and avoids post-creation updates. Which approach works best depends on whether you can observe the consultation room ID from your WarmTransferTask events: • If you can get the room ID from task events, Option A is simplest. • If WarmTransferTask’s events don’t expose the room ID in your setup, use Option B (custom flow). Tell me if you can access the consultation room ID from your task events (or share how you receive WarmTransferTask events), and I’ll give the exact API call or code snippet to set room metadata. , with interactive elements
c
If you can get the room ID from task events, can you check if this is possible?
t
Short answer: WarmTransferTask does NOT expose the consultation room ID or let you pass metadata into the internal consultation room. You cannot tag that room directly from WarmTransferTask. What you can do (two reliable options): 1) Post-create tag (if you can observe the consultation room ID) • If your runtime receives an event that includes the consultation room ID when WarmTransferTask runs, call the Rooms API / SDK to update that room’s metadata immediately: ◦ GET/identify the consultation room ID from the event ◦ PATCH or UpdateRoom: set
room.metadata = {"type":"consultation"}
• Dispatcher: refuse rooms unless
room.metadata.type == "inbound_call"
. 2) Full custom warm-transfer flow (recommended if you need guaranteed metadata) • Create the consultation room yourself (CreateRoom) and set metadata at creation. • Dial the supervisor into that consultation room with
CreateSIPParticipant
. • After briefing, use
MoveParticipant
to move the supervisor into the caller’s room and disconnect agents. • This gives you full control over metadata, names, and dispatch behavior. Which to pick: • If you can see the consultation room ID from task events, Option 1 is simplest. • If WarmTransferTask events don’t expose the ID (they don’t, by design), use Option 2. If you want, I can: • Provide the exact Rooms API call to set metadata for Option 1 (if you can supply the event you receive), or • Give a concrete Python example for the custom flow (CreateRoom → CreateSIPParticipant → MoveParticipant) to implement Option 2. Which would you like? Sources: The Human-in-the-Loop (HITL) Pattern for Voice Agents | LiveKit | The Human-in-the-Loop (HITL) Pattern for Voice Agents | LiveKit | WarmTransferTask | LiveKit Documentation | Prebuilt tasks | LiveKit Documentation | Agent Builder | LiveKit Documentation, with interactive elements