wide-flower-24692
02/17/2026, 9:34 AMtall-airline-16756
02/17/2026, 9:35 AMtall-airline-16756
02/17/2026, 9:35 AMend_call method is trying to log self.participant.identity, but self.participant is None, which triggers the AttributeError. I’ll help you fix it.
Summary of the problem
• The crash happens because self.participant was never set (or was cleared) before end_call uses self.participant.identity.
• Any code path that assumes self.participant exists will raise this error when it is None.
What you can do (recommended fixes)
1. Ensure the participant is assigned during initialization
◦ When you construct the agent, store the participant (or a safe reference) on the instance so tool methods can use it:
class MyAgent(Agent):
def __init__(self, job_context: JobContext):
super().__init__(instructions="...")
self.job_context = job_context
self.participant = job_context.room.local_participant
• Make sure the place that creates the agent passes the JobContext/room so local_participant is available.
1. Add a defensive null-check in end_call
◦ Always verify the participant exists before accessing `identity`:
@function_tool
async def end_call(self, context: RunContext):
if self.participant is None:
logger.warning("No participant assigned to agent; skipping end_call")
return "Cannot end call: no participant"
<http://logger.info|logger.info>(f"Ending the call for {self.participant.identity}")
# continue with normal shutdown logic
• This prevents the exception and makes failures explicit in logs.
1. If the participant may be set later (dynamic assignment)
◦ Either delay calling end_call until after assignment, or have end_call accept the participant as a parameter rather than relying on an instance attribute:
async def end_call(self, participant):
if participant is None:
logger.warning("end_call called without participant")
return
<http://logger.info|logger.info>(f"Ending the call for {participant.identity}")
1. Verify initialization path in your entrypoint
◦ Confirm your entrypoint or session startup actually sets up the agent with the job context / room:
@server.rtc_session()
async def entrypoint(ctx: JobContext):
agent = MyAgent(job_context=ctx)
session = AgentSession(...)
await session.start(agent=agent, room=ctx.room)
Quick checklist to debug now
• Where is the agent instantiated? Confirm that code receives and stores job_context.room.local_participant.
• Search for any code that might clear or overwrite self.participant later.
• Add logging at agent init and just before end_call to print whether self.participant is None and, if set, its identity.
If you’d like, paste the agent’s init and the code that calls/starts the agent (entrypoint/session.start) and I’ll point out exactly where to set the participant or where to add the null-check.
Sources: How to set a fixed participant for agent audio processing | Company directory phone assistant | LiveKit Documentation | SIP Lifecycle Management Agent | LiveKit Documentationwide-flower-24692
02/17/2026, 9:36 AMtall-airline-16756
02/17/2026, 9:36 AMend_call tries to reference self.participant.identity but self.participant is None.
What to check and how to fix it
• Ensure the agent stores the participant when the session starts
◦ Where you call await session.start(room=ctx.room, agent=agent, ...) the session has access to ctx.room. Your agent must save that participant reference (for example, room.local_participant) into self.participant during initialization or soon after session start.
◦ If your agent class is OutboundCaller (the code you pass to session.start), add something like this in its initializer or in a session-start handler:
class OutboundCaller(Agent):
def __init__(self, name, appointment_time, dial_info, job_context=None):
super().__init__(instructions="...")
self.name = name
self.appointment_time = appointment_time
self.dial_info = dial_info
self.job_context = job_context
self.participant = None # default
async def on_session_start(self, room):
# Called when session starts; make sure you set participant here
self.participant = getattr(room, "local_participant", None)
• If your framework doesn't call on_session_start, set agent.participant = ctx.room.local_participant immediately before/after calling session.start.
• Add a defensive check in end_call so the method doesn't crash if the participant isn't set:
@function_tool
async def end_call(self, context: RunContext):
if self.participant is None:
logger.warning("end_call invoked but no participant is set")
return "Cannot end call: no participant"
<http://logger.info|logger.info>(f"Ending the call for {self.participant.identity}")
# remaining end_call logic...
• If participant is assigned dynamically, pass it explicitly to the method instead of relying on an instance attribute:
await agent.end_call(participant=ctx.room.local_participant)
Quick debugging steps you can add now
• Add logging right after session.start to confirm participant is set:
<http://logger.info|logger.info>(f"Room: {ctx.room.name}, local_participant: {getattr(ctx.room, 'local_participant', None)}")
<http://logger.info|logger.info>(f"Agent.participant after start: {agent.participant}")
• Search your code for any place that might overwrite self.participant = None or reassign agent unexpectedly.
• Reproduce the error and capture a short trace showing where end_call is invoked from and the agent's state at that moment.
One targeted question to narrow this down
• Is OutboundCaller your agent class, and do you have a place where the agent receives `ctx`/`room` so you can set self.participant? If so, paste the __init__ of OutboundCaller and the code that constructs/starts the session and I’ll point exactly where to set the participant and add the safest checks.
If you share those small snippets (agent class init and session start), I’ll give the precise patch you can apply.wide-flower-24692
02/17/2026, 9:41 AMtall-airline-16756
02/17/2026, 9:41 AMagent.set_participant(participant) and everything is fine. The chat flow doesn't create or attach a participant, so methods that expect self.participant (like end_call) can fail because self.participant remains None.
What I'm responding to
• You said the outbound flow works (you set participant after ctx.wait_for_participant(...)) but the chat flow “is not work” — I interpret that as the agent in chat never receives a participant reference and later raises AttributeError when end_call or other tools try to use self.participant.identity.
What to change (two safe options)
• Option A — Attach a participant for chat sessions (recommended if your agent logic expects a participant)
a. If a chat session should use a room participant (for example, the bot’s local participant), set it from ctx.room right after session start:
await session.start(room=ctx.room, agent=agent, ...)
# Immediately set participant for chat sessions:
agent.set_participant(getattr(ctx.room, "local_participant", None))
1. Or inside your agent class, implement a session-start handler that sets self.participant from the room passed to the session:
class OutboundCaller(Agent):
async def on_session_start(self, room):
self.participant = getattr(room, "local_participant", None)
1. Add logging to confirm:
<http://logger.info|logger.info>(f"Agent participant after start: {agent.participant}")
• Option B — Make agent methods defensive / accept participant explicitly (safer if chat sessions don’t have SIP participants)
a. Add a guard in end_call and similar tools:
@function_tool
async def end_call(self, context: RunContext):
if self.participant is None:
logger.warning("end_call called but no participant is set")
return "Cannot end call: no participant"
<http://logger.info|logger.info>(f"Ending the call for {self.participant.identity}")
# ...
1. Or change methods to accept a participant argument so the caller supplies it when available:
await agent.end_call(participant=getattr(ctx.room, "local_participant", None))
Why this addresses the difference between outbound and chat
• Outbound flow explicitly creates and waits for a SIP participant; you then set that participant on the agent.
• Chat flow never creates/waits for a participant, so nothing is assigned. Either assign a participant for chat (if appropriate), or change your code to not rely on self.participant for chat-only interactions.
Quick debugging checklist
• After await session.start(...), add:
◦ logger.info(f"ctx.room: {ctx.room.name}, local_participant: {getattr(ctx.room, 'local_participant', None)}")
◦ logger.info(f"agent.participant: {agent.participant}")
• Re-run chat flow and paste the logs for those two lines if agent.participant is still None.
• If you get an AttributeError, paste the full traceback (file and line number where end_call is called) so I can point to the exact call site to guard.
One targeted question to confirm next steps
• For chat sessions, should the agent act as a room participant (i.e., local_participant), or are chat sessions purely LLM-driven without any participant object? If it should act as a participant, I’ll give the exact one-line patch to set agent.participant after session start.
If you paste the OutboundCaller class (its init, set_participant method, and end_call) or the logs after adding the two info lines, I’ll provide the exact code patch to fix the crash.