How to Implement Outbound Calling with LiveKit Agents: A Complete Guide
LiveKit Agents implement outbound calling by creating SIP participants through JobContext.add_sip_participant() and managing call flows with transfer_sip_participant() or the high-level WarmTransferTask workflow.
The livekit/agents repository provides a comprehensive framework for building voice AI agents that can initiate phone calls, transfer callers to human supervisors, and orchestrate complex telephony workflows. To implement outbound calling effectively, you need to understand the SIP participant management APIs in livekit-agents/livekit/agents/job.py and the warm transfer workflow in the beta workflows module.
Understanding the Core Components for Outbound Calling
Outbound calling in LiveKit Agents relies on two primary architectural components: the JobContext class for low-level SIP operations and the WarmTransferTask for high-level workflow orchestration.
JobContext and SIP Participant Management
The JobContext class, defined in livekit-agents/livekit/agents/job.py, exposes methods that interface directly with LiveKit's SIP service. When you implement outbound calling, you interact with two critical methods:
add_sip_participant()(lines 28-38): Creates a new SIP participant by dialing out through a configured trunktransfer_sip_participant()(lines 71-84): Moves an existing SIP participant to a new destination
These methods handle the RTP stream establishment and SIP signaling required for telephony integration.
Creating Outbound Calls with add_sip_participant
To initiate an outbound call, use the add_sip_participant() method within your agent's job context. This method requires a valid outbound SIP trunk ID configured in your LiveKit project and a destination phone number or SIP URI.
# Inside an async function with a JobContext instance called `ctx`
sip_participant = await ctx.add_sip_participant(
call_to="+18005551234", # Destination phone number or sip:<user>@<host>
trunk_id="ST_abc123", # Outbound SIP trunk created in LiveKit
participant_identity="outbound-call-1",
)
Once executed, the call appears as a RemoteParticipant in the current room. You can interact with this participant using standard room IO functions, including text-to-speech and speech-to-text pipelines.
Transferring Calls with transfer_sip_participant
After establishing an outbound call, you may need to redirect the participant to another destination. The transfer_sip_participant() method supports both cold transfers (immediate hand-off) and warm transfers (supervised hand-off).
Cold Transfers
A cold transfer immediately moves the call without intermediary interaction. Set play_dialtone=True to provide audio feedback during the transfer process.
await ctx.transfer_sip_participant(
participant=sip_participant, # RemoteParticipant or participant identity string
transfer_to="+18009998877", # New destination
play_dialtone=True,
)
Implementing Warm Transfers with WarmTransferTask
For complex scenarios requiring human supervisor intervention, use the WarmTransferTask class located in livekit-agents/livekit/agents/beta/workflows/warm_transfer.py (lines 61-84). This high-level workflow orchestrates the complete warm transfer process:
- Dials a human supervisor using
add_sip_participant - Plays hold music or summary audio while connecting
- Exposes a
connect_to_callertool to the supervisor - Merges the calls when the supervisor invokes the tool
from livekit.agents.beta.workflows import WarmTransferTask
result = await WarmTransferTask(
target_phone_number="+18005550000", # Supervisor number
sip_trunk_id=os.getenv("LIVEKIT_SIP_OUTBOUND_TRUNK"),
sip_number=os.getenv("LIVEKIT_SIP_NUMBER"), # Caller-ID shown to supervisor
chat_ctx=self.chat_ctx, # Pass current LLM chat context for summarization
)
# `result.human_agent_identity` contains the SIP participant ID of the supervisor
The task automatically handles the SIP participant management internally, using the same JobContext methods described earlier.
Complete Implementation Example
The examples/warm-transfer/warm_transfer.py file (lines 21-74) demonstrates a production-ready implementation. This example creates a support agent that can transfer callers to human supervisors using the warm transfer workflow.
class SupportAgent(Agent):
@function_tool
async def transfer_to_human(self) -> None:
await self.session.say("Please hold while I connect you to a human agent.")
result = await WarmTransferTask(
target_phone_number=os.getenv("LIVEKIT_SUPERVISOR_PHONE_NUMBER"),
sip_trunk_id=os.getenv("LIVEKIT_SIP_OUTBOUND_TRUNK"),
sip_number=os.getenv("LIVEKIT_SIP_NUMBER"),
chat_ctx=self.chat_ctx,
)
await self.session.say(
"You are now connected to my supervisor.", allow_interruptions=False
)
self.session.shutdown()
To run this example, configure the following environment variables:
LIVEKIT_SUPERVISOR_PHONE_NUMBER: The phone number to dial for human supportLIVEKIT_SIP_OUTBOUND_TRUNK: Your LiveKit SIP outbound trunk IDLIVEKIT_SIP_NUMBER: The caller ID number displayed to the callee
Summary
- Outbound calling in LiveKit Agents centers on the
JobContextclass inlivekit-agents/livekit/agents/job.py, which providesadd_sip_participant()for creating calls andtransfer_sip_participant()for redirecting them. - Cold transfers immediately move callers to new destinations using the transfer method with optional dial-tone playback.
- Warm transfers leverage the
WarmTransferTaskworkflow inlivekit-agents/livekit/agents/beta/workflows/warm_transfer.pyto dial supervisors, play hold audio, and merge calls via theconnect_to_callertool. - Production implementations should reference the
examples/warm-transfer/warm_transfer.pyexample for environment configuration and agent structure.
Frequently Asked Questions
What is the difference between cold and warm transfers in LiveKit?
A cold transfer uses JobContext.transfer_sip_participant() to immediately redirect an active SIP participant to a new phone number without intermediary interaction. A warm transfer uses the WarmTransferTask workflow to first dial a human supervisor, allow the supervisor to review a summary of the conversation, and then merge the calls only when the supervisor explicitly invokes the connect_to_caller tool.
How do I configure SIP trunking for outbound calling?
You must create an outbound SIP trunk in your LiveKit Cloud or self-hosted LiveKit server dashboard. Obtain the trunk ID (formatted as ST_xxxxxx) and pass it as the trunk_id parameter to JobContext.add_sip_participant() or as sip_trunk_id to WarmTransferTask. Additionally, set the sip_number parameter to control the caller ID displayed to the destination party.
Can I implement outbound calling without using WarmTransferTask?
Yes. You can implement outbound calling using only the low-level APIs in JobContext. Call add_sip_participant() to create the outbound call, then use standard room IO methods to interact with the resulting participant. If you need to redirect the call later, invoke transfer_sip_participant(). The WarmTransferTask is optional and provides convenience for complex supervisor hand-off workflows.
What environment variables are required for the warm transfer example?
The examples/warm-transfer/warm_transfer.py script requires three environment variables: LIVEKIT_SUPERVISOR_PHONE_NUMBER (the destination number for the human supervisor), LIVEKIT_SIP_OUTBOUND_TRUNK (your LiveKit SIP outbound trunk ID), and LIVEKIT_SIP_NUMBER (the caller ID number shown to the supervisor). These variables configure the SIP routing and identity for outbound calls initiated by the agent.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →