I'm trying to understand how I continue a conversa...
# ask-ai
e
I'm trying to understand how I continue a conversation when I use the WarmTransferTask - does this task return an error if it goes to voice mail or is declined on the user's end?
t
Thanks for reaching out, Arthur. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’m responding to your question about whether
WarmTransferTask
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 Documentation
e
This is the example provided by the livekit repo
Copy code
class 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?
t
Thanks — I’m responding to your question about whether the example’s
self.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):
Copy code
@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.
e
Do you have examples of what WarmTransferResult look like if we go to voicemail or there was a decline to pick up?
t
Thanks — I’m responding to your request for concrete examples of what
WarmTransferResult
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):
Copy code
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