What is latest doc of livekit regarding transfing ...
# ask-ai
s
What is latest doc of livekit regarding transfing from one agent to another agent?
t
Thanks for reaching out, elish. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’m responding about the LiveKit docs for transferring a session from one agent to another (agent handoffs). The latest doc is: https://docs.livekit.io/agents/logic/agents-handoffs/ Quick summary: • Agent handoffs let one agent transfer control to another (can be triggered inside a tool call). • Return a new agent instance to initiate the handoff (example: return BillingAgent(...), "message"). • To preserve conversation history, pass
session.chat_ctx
into the new agent. • Use
userdata
on
AgentSession
to persist custom state across handoffs. Would you like the Python example or a short code snippet adjusted to your app? Sources: Agents and handoffs | LiveKit docs | Workflows | LiveKit docs
s
give me one code example
t
Below is a concise Python example showing an agent handoff that preserves chat context and passes custom state via
userdata
.
Copy code
from livekit import Agent, AgentSession, RunContext, function_tool

class MySessionInfo:
    def __init__(self):
        self.call_id = None
        self.notes = {}

class SupportAgent(Agent):
    def __init__(self, chat_ctx=None, session: AgentSession[MySessionInfo]=None):
        super().__init__(chat_ctx=chat_ctx)
        self.session = session

    @function_tool()
    async def transfer_to_billing(self, context: RunContext):
        # preserve conversation history by passing chat_ctx
        # ensure userdata persists across the handoff
        self.session.userdata.notes["reason"] = "billing question"
        return BillingAgent(chat_ctx=self.session.chat_ctx, session=self.session), "Transferring you to Billing."

class BillingAgent(Agent):
    def __init__(self, chat_ctx=None, session: AgentSession[MySessionInfo]=None):
        super().__init__(chat_ctx=chat_ctx)
        self.session = session

    async def on_start(self):
        # access persisted userdata
        reason = self.session.userdata.notes.get("reason")
        await self.send_message(f"I have your notes: {reason}. How can I help with billing?")

# Bootstrapping a session with userdata
session = AgentSession[MySessionInfo](userdata=MySessionInfo())
session.userdata.call_id = "ABC123"

# Start conversation with SupportAgent
agent = SupportAgent(chat_ctx=None, session=session)
If you want this adapted to a specific framework (FastAPI, asyncio runner) or a shorter snippet showing only the handoff line, tell me which one.
s
Here is the thing what is correct way to handle? should I trasnfer session that we created at main() to userData and than use it accross, or should I use ctx: RunContext for every methods and use that session which is ideal>
t
You're asking whether to store your session in
userdata
created at startup and share it across agents, or to rely on
RunContext
passed into each method — and which is the ideal pattern. Good question. Here’s a concise recommendation and rationale. Recommendation • Use
userdata
on
AgentSession
for persistent application state that must survive handoffs (IDs, notes, call metadata, business state). • Use
RunContext
(
ctx
) for per-invocation details and transient context (request-specific params, method-scoped metadata, tool inputs). • Also pass
session.chat_ctx
into the new agent to preserve conversation history when doing a handoff. Why this pattern •
userdata
persists across agents and handoffs by design, so it’s the correct place for shared session/state you need available to any agent later. •
RunContext
is ephemeral and tied to the current call; it’s ideal for method-level inputs and execution context that you don’t need to persist across agents. • Combining both keeps state responsibilities clear, avoids accidental persistence of ephemeral data, and simplifies reasoning about lifecycle and concurrency. Short example (mixing both):
Copy code
# userdata holds long-lived state
session = AgentSession[MySessionInfo](userdata=MySessionInfo())

class SupportAgent(Agent):
    def __init__(self, chat_ctx=None, session: AgentSession[MySessionInfo]=None):
        super().__init__(chat_ctx=chat_ctx)
        self.session = session

    @function_tool()
    async def handle_issue(self, ctx: RunContext):
        # use ctx for this call's inputs
        report = ctx.inputs.get("report")
        # persist necessary facts for future agents
        self.session.userdata.notes["last_report"] = report
        return BillingAgent(chat_ctx=self.session.chat_ctx, session=self.session), "Handoff to billing."

class BillingAgent(Agent):
    def __init__(self, chat_ctx=None, session: AgentSession[MySessionInfo]=None):
        super().__init__(chat_ctx=chat_ctx)
        self.session = session

    async def on_start(self):
        # read persisted userdata
        last = self.session.userdata.notes.get("last_report")
        await self.send_message(f"I see the last report: {last}")
When you might choose otherwise • If state is truly transient and never needed after the current method, keep it only in
RunContext
. • Avoid stuffing large or sensitive objects into
userdata
if they don’t need to persist. If you want, tell me what data you need to persist (IDs, tokens, call recordings, user prefs) and I’ll show a tailored example and explain any security/size considerations.