fancy-easter-44489
03/02/2026, 10:14 AMtall-airline-16756
03/02/2026, 10:15 AMtall-airline-16756
03/02/2026, 10:15 AMfancy-easter-44489
03/02/2026, 10:16 AMtall-airline-16756
03/02/2026, 10:17 AM<http://logger.info|logger.info>(f"Resolved trunk_id: '{trunk_id}' (type: {type(trunk_id)})")
• If trunk_id is empty, the merge may fail when the second leg is created. Ensure the trunk is an outbound SIP trunk and has outbound permissions.
• Ensure you pass a proper ChatContext, not raw session history
◦ WarmTransferTask expects a ChatContext object. Passing self.session.history (a raw list) can cause unexpected behavior. Use self.chat_ctx or construct a `ChatContext`:
from livekit.agents.llm import ChatContext
chat_ctx = ChatContext(messages=list(self.session.history))
result = await WarmTransferTask(..., chat_ctx=chat_ctx)
• Or simply: chat_ctx=self.chat_ctx if available.
• Prevent the room/session from being deleted while transfer runs
◦ You set self.on_hold = True which is good, but confirm no other inactivity watchdog or background task still deletes the room. Cancel or pause any inactivity timer during the transfer so the room stays alive until merge completes. Add clear begin/end logs around the await so you can see if control ever returns:
<http://logger.info|logger.info>("BEGIN WarmTransferTask")
result = await WarmTransferTask(...)
<http://logger.info|logger.info>("END WarmTransferTask")
• Confirm the human agent actually answers (not voicemail) and the second leg is established
◦ Log the WarmTransferTask result and check for fields like human_agent_identity. If that is absent, the second leg never connected and merge won’t occur:
<http://logger.info|logger.info>(f"WarmTransferTask result: {result}")
if not getattr(result, "human_agent_identity", None):
logger.warning("Second leg didn't connect — merge aborted")
• Check trunk capabilities and SIP behavior that affect merges
◦ The trunk must allow outbound bridging and support session progression needed for a media merge (SIP REFER / re-INVITE / session updates depending on flow). If the provider or trunk blocks these, the merge can fail even though dialing worked.
• Watch for runtime/job termination or resource limits
◦ If your agent process is killed (timeout, memory, or container restart) during the transfer, the merge will never complete. If your END log never appears, investigate job timeouts or OOMs on the agent host.
Suggested minimal changes to test quickly
1. Add logs to confirm trunk_id, entry/exit of WarmTransferTask, and the Task result.
2. Switch chat_ctx=self.session.history → chat_ctx=self.chat_ctx (or build a ChatContext) and retry.
3. Temporarily disable any inactivity detector while the transfer runs to rule out premature room deletion.
4. If possible, test the trunk by making a standalone SIP outbound call (outside WarmTransferTask) and confirm bridging behavior.
If you still see disconnects, please share:
• The log output around the WarmTransferTask call (BEGIN/END/result) — sanitized if needed.
• The trunk_id value (or confirm it was non-empty and the trunk type).
• Whether the human agent actually answers or goes to voicemail.
I’ll help interpret the logs and next steps once you share them. If you’d like, I can also walk through a manual test flow (creating a SIP participant then attempting a merge) to isolate whether dialing or merging is failing.
Sources: Preserving Caller ID During SIP Transfers in LiveKit Agents | sip | livekit/livekit_composite | DeepWiki | examples/frontdesk | livekit/livekit_composite | DeepWiki | Agents telephony integration | LiveKit Documentation | WarmTransferTask | LiveKit Documentationfancy-easter-44489
03/02/2026, 10:32 AMtall-airline-16756
03/02/2026, 10:32 AMself.chat_ctx to WarmTransferTask, or self.session.history? WarmTransferTask expects a ChatContext.
◦ If you’re using self.session.history, try replacing it with self.chat_ctx or build a ChatContext and retry.
1. Add minimal entry/exit/result logging around the call and paste the logs here
◦ Add these logs exactly as shown and run a failing warm transfer so we can see whether the task finishes or is interrupted:
<http://logger.info|logger.info>("BEGIN WarmTransferTask")
result = await WarmTransferTask(
target_phone_number=phone,
sip_trunk_id=trunk_id,
chat_ctx=self.chat_ctx, # or constructed ChatContext
)
<http://logger.info|logger.info>(f"END WarmTransferTask, result: {result}")
• Share the log lines for BEGIN, any intermediate logs, and END/result (sanitize any sensitive data).
1. Check whether the human answers vs. voicemail
◦ In the logs from step 2, does the Task return a human_agent_identity or other indicator that the person answered? If not, the second leg is not establishing and merge will abort.
1. Verify the room isn’t being deleted while transfer runs
◦ Temporarily disable any inactivity watcher or room cleanup that might run while self.on_hold is True.
◦ Add logs around your inactivity detector to show it does not trigger during the transfer.
1. Confirm the agent process stays alive for the duration
◦ Check your host/application logs for process restarts, timeouts, or OOM events during the transfer. If your END log never appears, your agent process may have been killed.
If you prefer, share these specific items next:
• The log output lines for BEGIN/END/result from above (sanitized).
• The snippet showing which object you pass as chat_ctx.
• Any room events around the time of transfer (participant_left, room_deleted) if available.
What I’ll look for when you share logs
• Whether WarmTransferTask returned successfully or raised an exception.
• If it returned, whether it includes a human agent identity or success flag.
• Any signs that the room was deleted or the agent process ended mid-transfer.
Once you post the logs and confirm which chat_ctx you used, I’ll point to the exact next step (e.g., adjust trunk settings, change how we pass chat context, or try a manual SIP participant merge to isolate the failing step).fancy-easter-44489
03/02/2026, 10:33 AMtall-airline-16756
03/02/2026, 10:33 AMfancy-easter-44489
03/02/2026, 10:33 AMtall-airline-16756
03/02/2026, 10:34 AMsession.close(), participant.disconnect(), unpublish_track(), or self.session.end()).
2. Logs around the time of the merge attempt (sanitized):
◦ Lines showing bot stop/unpublish, BEGIN WarmTransferTask, any participant_left events, and any room_deleted or session.close calls.
1. Do you have an inactivity watcher or explicit cleanup that deletes the room when certain flags change (for example, on_hold logic or “if only bot present -> delete room”)? If yes, paste that logic or describe it.
2. Is the bot the same process that runs the WarmTransferTask job (i.e., does the bot process own the job/session lifecycle)?
Immediate checks and quick fixes to try (in priority order)
1. Confirm you are only removing the bot participant, not closing the whole session
◦ Replace any call like session.close() or self.session.end() with the participant-specific removal:
▪︎ Unpublish or stop the bot’s audio track, then call the bot participant’s disconnect/unpublish API (e.g., participant.unpublish_track(...) or participant.disconnect()), not the room/session close.
◦ If you must close a session object, ensure it is the bot participant’s session, not the whole room/session object shared by the caller/human agent.
1. Ensure the room deletion/inactivity logic is paused during transfer
◦ When you set self.on_hold = True, also pause any background cleanup that deletes rooms based on participant counts or silence.
◦ Add a guard: if WarmTransferTask is in progress, skip room deletion:
if warm_transfer_in_progress:
skip_cleanup()
• Add logging in the cleanup to show why it deleted the room (participant count, inactivity timeout, etc.).
1. Remove the bot’s audio without leaving the room immediately
◦ Unpublish or mute the bot’s audio track first so callers don’t hear the bot:
▪︎ Unpublish: remove the playback track from the bot participant.
▪︎ Mute: mark the bot participant as muted to prevent audio transmission.
◦ After confirming the agent is fully joined and the merge succeeded, remove the bot participant.
1. Add a short delay and checks before deleting the bot participant
◦ After WarmTransferTask reports the agent joined (or after you call merge), wait ~300–700 ms and verify the agent’s participant is present and publishing audio, then remove/unpublish the bot.
Example safe flow
1. Bot finishes TTS and stops/unpublishes playback track.
2. Set a flag: warm_transfer_in_progress = True and pause cleanup.
3. Call WarmTransferTask(...) to dial and merge the agent.
4. On success (check result contains agent identity), confirm agent participant exists and has audio tracks.
5. Unpublish/mute and then disconnect the bot participant.
6. Clear warm_transfer_in_progress so cleanup resumes.
Diagnostics to capture (please share)
• The exact code you call when the bot “leaves” (show the function call).
• Logs for these events: bot stop/unpublish, BEGIN WarmTransferTask, WarmTransferTask result, participant_left for bot, room_deleted or session closed.
• The room participant list right before the bot leaves (who’s present and their published tracks).
If you share the snippet where you remove the bot (or the relevant cleanup handler), I’ll point out the precise change to make so you only remove the bot participant and not the session. If you share the logs, I’ll highlight the exact event that triggers room deletion and the minimal code change to prevent it.