stale-vase-84095
01/02/2026, 2:13 AMtall-airline-16756
01/02/2026, 2:13 AMtall-airline-16756
01/02/2026, 2:13 AMsession.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 docsstale-vase-84095
01/02/2026, 2:14 AMtall-airline-16756
01/02/2026, 2:14 AMuserdata.
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.stale-vase-84095
01/02/2026, 2:18 AMtall-airline-16756
01/02/2026, 2:18 AMuserdata 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):
# 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.