early-butcher-66889
02/28/2026, 9:30 PMtall-airline-16756
02/28/2026, 9:30 PMtall-airline-16756
02/28/2026, 9:30 PMWarmTransferTask returns an error when a transfer goes to voicemail or is declined, and how to continue the conversation afterward.
Short answer
• No — WarmTransferTask does not return an error when the called party declines the transfer or the call goes to voicemail. The task completes normally and provides a result indicating the final disposition.
What actually happens
• If the called human declines the transfer, they can invoke the decline_transfer tool. That returns control to the original workflow; the task completes without throwing an error.
• If the call goes to voicemail, the human agent can invoke the voicemail_detected tool to signal that outcome. The task also completes normally in this case.
What you receive from the task
• The task returns a WarmTransferResult that includes human_agent_identity (the identity of the human who was contacted). This is provided regardless of whether they answered, declined, or it hit voicemail.
How to continue the conversation
• Listen for the WarmTransferResult and inspect the context and any tool usages (decline_transfer, voicemail_detected) to determine the outcome.
• Based on that outcome, decide the next step in your workflow, for example:
◦ If decline_transfer was used: resume automated handling or attempt another transfer/agent.
◦ If voicemail_detected was used: play a leave-a-message prompt, schedule a callback, or continue with an alternate flow.
◦ If the human accepted the transfer: proceed to transfer the conversation to them.
If you’d like, tell me which continuation behavior you want (e.g., retry transfer, auto-message voicemail, schedule callback) and I’ll outline the exact steps or sample logic to implement it.
Sources: WarmTransferTask | LiveKit Documentationearly-butcher-66889
02/28/2026, 9:32 PMclass SupportAgent(Agent):
def __init__(self) -> None:
super().__init__(instructions=INSTRUCTIONS)
async def on_enter(self):
self.session.generate_reply()
@function_tool
async def transfer_to_human(self) -> None:
"""Called when the user asks to speak to a human agent. This will put the user on
hold while the supervisor is connected.
Ensure that the user has confirmed that they wanted to be transferred. Do not start transfer
until the user has confirmed.
Examples on when the tool should be called:
----
- User: Can I speak to your supervisor?
- Assistant: Yes of course.
----
- Assistant: I'm unable to help with that, would you like to speak to a human agent?
- User: Yes please.
----
"""
<http://logger.info|logger.info>("tool called to transfer to human")
await self.session.say(
"Please hold while I connect you to a human agent.", allow_interruptions=False
)
try:
assert SIP_TRUNK_ID is not None
assert SUPERVISOR_PHONE_NUMBER is not None
result = await WarmTransferTask(
target_phone_number=SUPERVISOR_PHONE_NUMBER,
sip_trunk_id=SIP_TRUNK_ID,
sip_number=SIP_NUMBER,
chat_ctx=self.chat_ctx,
# add extra instructions for summarization
# you can also customize the entire instructions by overriding the `get_instructions` method
extra_instructions=SUMMARY_INSTRUCTIONS,
)
except ToolError as e:
logger.error(f"failed to transfer to supervisor with tool error: {e}")
raise e
except Exception as e:
logger.exception("failed to transfer to supervisor")
raise ToolError(f"failed to transfer to supervisor with error: {e}") from e
<http://logger.info|logger.info>(
"transfer to supervisor successful",
extra={"supervisor_identity": result.human_agent_identity},
)
await self.session.say(
"you are on the line with my supervisor. I'll be hanging up now.",
allow_interruptions=False,
)
self.session.shutdown()
server = AgentServer()
Doe this mean that it would shut down the session even if the transfer failed? What if I wanted to program in an extra check?tall-airline-16756
02/28/2026, 9:32 PMself.session.shutdown() will run when the WarmTransferTask fails, and how to add an extra check to handle failures gracefully.
Short answer
• In the example, self.session.shutdown() only runs when WarmTransferTask completes without raising an exception. If WarmTransferTask raises a ToolError or other exception, that exception is re-raised inside the except blocks and the shutdown line is not reached.
• If you want extra checks (for example, to confirm the supervisor actually answered vs. declined/voicemail), you should inspect the returned WarmTransferResult and/or catch errors explicitly and branch before calling shutdown().
What to change in the example
1. Catch errors separately and handle them without re-raising if you want to continue the session.
2. Inspect WarmTransferResult for disposition (check for tool usages such as decline_transfer or voicemail_detected) and decide whether to shutdown or resume the bot flow.
3. Optionally add a boolean/status field or check human_agent_identity to verify the transfer succeeded.
Example pattern
• Wrap the call and branch on:
◦ Exceptions (network/tool errors): handle and resume or retry.
◦ Successful return: inspect the result for declined/voicemail indicators and act accordingly.
Sample code sketch (adapt to your codebase types and APIs):
@function_tool
async def transfer_to_human(self) -> None:
await self.session.say("Please hold while I connect you to a human agent.", allow_interruptions=False)
try:
result = await WarmTransferTask(
target_phone_number=SUPERVISOR_PHONE_NUMBER,
sip_trunk_id=SIP_TRUNK_ID,
sip_number=SIP_NUMBER,
chat_ctx=self.chat_ctx,
extra_instructions=SUMMARY_INSTRUCTIONS,
)
except ToolError as e:
# Tool-level failure: log, notify user, and continue the session instead of shutting down
logger.error(f"transfer tool error: {e}")
await self.session.say("Sorry, I couldn't connect to a human right now. Would you like me to try again or leave a message?", allow_interruptions=True)
return # keep session running
except Exception as e:
# Unexpected failure: log, notify user, and keep session alive or escalate within your flow
logger.exception("unexpected error during transfer")
await self.session.say("An unexpected error occurred while trying to connect you. How would you like to proceed?", allow_interruptions=True)
return
# At this point WarmTransferTask completed and returned a WarmTransferResult
<http://logger.info|logger.info>("transfer completed", extra={"supervisor_identity": result.human_agent_identity})
# Inspect result/context/tools to determine disposition
# Example checks (adjust to the actual result fields/tool signaling you receive):
if getattr(result, "declined", False):
await self.session.say("The supervisor declined the transfer. Would you like me to try another agent or leave a message?", allow_interruptions=True)
return # keep session running
if getattr(result, "voicemail", False):
await self.session.say("It looks like we reached voicemail. Would you like to leave a message or request a callback?", allow_interruptions=True)
return # keep session running
# If accepted, confirm and then end the bot's session
await self.session.say("You are on the line with my supervisor. I'll be hanging up now.", allow_interruptions=False)
self.session.shutdown()
Next steps I can help with
• Show exactly which fields or tool signals to check on WarmTransferResult in your environment.
• Provide a ready-to-drop-in snippet that matches your actual WarmTransferResult shape and tool signaling (for declined vs. voicemail).
• Help design retry, callback, or fallback flows you'd like the bot to offer after a failed transfer.
Which follow-up would you like? If you want the exact field names to check, tell me which SDK / version you're using or paste the WarmTransferResult definition and I’ll tailor the snippet.early-butcher-66889
02/28/2026, 9:35 PMtall-airline-16756
02/28/2026, 9:36 PMWarmTransferResult looks like when a transfer goes to voicemail or the called party declines.
Short answer
• WarmTransferResult contains one field: human_agent_identity.
• The provided sources do not specify the exact value of human_agent_identity for voicemail or declined-call cases (for example, whether it is None, an empty string, or a special marker is not documented).
• Voicemail detection is handled by an LLM tool (detected_answering_machine) and declines are signalled by the decline_transfer tool; those tool usages are how you should detect those outcomes, rather than relying solely on human_agent_identity.
What I recommend you do
• Inspect the returned WarmTransferResult.human_agent_identity in your runtime logs to see the concrete values your deployment produces for answered / declined / voicemail cases.
• Also listen for tool signals in the transfer flow:
◦ voicemail_detected (via the answering-machine detection tool) — treat this as voicemail.
◦ decline_transfer — treat this as an explicit decline by the human agent.
• Use both sources of truth:
◦ Check WarmTransferResult for the contact identity.
◦ Check tool usage / context for voicemail_detected or decline_transfer to determine disposition.
Example handling pattern (conceptual)
1. Call WarmTransferTask.
2. If the task raises a ToolError or other exception: handle retry / notify user / keep session alive.
3. If it returns a `WarmTransferResult`:
◦ Log result.human_agent_identity.
◦ If the session/context shows decline_transfer was used → treat as declined, prompt user to retry or leave a message.
◦ If voicemail_detected was used → treat as voicemail, offer to leave a message or request a callback.
◦ Otherwise assume the human accepted the transfer and end the bot session.
Small sample sketch (adjust to your SDK types and how you receive tool signals):
result = await WarmTransferTask(...)
<http://logger.info|logger.info>("warm transfer result", extra={"human_agent_identity": result.human_agent_identity})
# Pseudocode: check tool signals in your chat context/session
if self.chat_ctx.tool_used("decline_transfer"):
# handle decline
await self.session.say("Supervisor declined. Would you like me to try another agent or leave a message?")
elif self.chat_ctx.tool_used("voicemail_detected"):
# handle voicemail
await self.session.say("We reached voicemail. Would you like to leave a message or request a callback?")
else:
# assume accepted
await self.session.say("You are on the line with my supervisor. I'll be hanging up now.")
self.session.shutdown()
If you’d like, I can:
• Provide a ready-to-drop-in snippet tailored to your SDK version if you paste the WarmTransferResult class definition or the SDK version you're using.
• Give a test checklist and exact logging lines to run so you can capture what human_agent_identity contains in voicemail/decline cases. Which would you prefer? Sources: WarmTransferTask | LiveKit Documentation | Agents telephony integration | LiveKit Documentation