session = await init_agent_session(ctx=ctx) @sessi...
# ask-ai
c
session = await init_agent_session(ctx=ctx) @session.on("agent_state_changed") def on_agent_state_changed(event: AgentStateChangedEvent): print(f"agent state changed from {event.old_state} to {event.new_state}") if(condition): lkapi = api.LiveKitAPI() disconn_task = asyncio.create_task( lkapi.room.delete_room( api.DeleteRoomRequest(room=ctx.room.name) ) ) await session.start( agent=CustomAgent(on_enter_message=message, for_disconnection=True), room=ctx.room, ) in some calls i only get the print statement for when the agent changes state from initialising to listening. then the agent speaks a message but i do not get the log. The agent finishes the message but i also do not get the state change event for listening. What can be the issue?
✅ 1
t
Thanks for reaching out, Kushagra. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’m sorry you’re seeing inconsistent state-change logs. You’re asking why you sometimes get the transition from
initializing
→
listening
, the agent speaks, but you don’t see the later
agent_state_changed
events (for
speaking
→
listening
or similar). I’ll walk through the most likely causes and give focused troubleshooting steps so we can narrow this down. Likely causes to check • Exceptions inside the event handler: If your
on_agent_state_changed
callback raises an exception (synchronous or asynchronous), it can interrupt event processing and stop further callbacks from running. • Side-effects in the handler that affect the session/room: Deleting the room or otherwise closing the session while the agent is speaking can prevent later state transitions / events from being emitted or delivered. • Fire-and-forget tasks not awaited or not observed: Creating background tasks (like
asyncio.create_task(lkapi.room.delete_room(...))
) without proper error handling can hide exceptions from those operations and lead to race conditions with the agent lifecycle. • TTS / audio playback timing: If the agent’s speaking finishes and the room is removed immediately after (or during) playback, events for the subsequent state may not reach your handler. Quick checks you can run now 1. Add robust error logging inside the handler so any exception is visible: ◦ Wrap the handler body in try/except and log exceptions with stack traces. ◦ Log the entire
event
object on every call (old_state, new_state, timestamp if available). 1. Temporarily remove the room deletion call to see if the missing events stop: ◦ Comment out the
lkapi.room.delete_room(...)
call and confirm whether you then consistently receive the post-speech state changes. 1. Avoid deleting the room synchronously from the handler: ◦ If you must delete the room, delay the delete until after the agent session fully completes (for example, schedule deletion after observing
new_state == "idle"
or after a short await). 1. Observe the background task’s exceptions: ◦ When you create
disconn_task = asyncio.create_task(...)
, add a done callback or await it eventually to surface exceptions: ▪︎
disconn_task.add_done_callback(lambda t: log(t.exception()))
1. Add more granular logs around lifecycle boundaries: ◦ Log when you call
session.start(...)
, when
CustomAgent
is created/entered, and when TTS playback starts/ends (if accessible). 1. Run a reproducible minimal test: ◦ Create a minimal flow that only starts the session, triggers a single speak, and logs all state events. Confirm whether the issue reproduces in isolation. Safe code-pattern suggestion • Do not perform destructive operations (room deletion, session close) directly inside the
agent_state_changed
handler while the agent may still be speaking. • Wait for a clear terminal/idle state before deleting the room: a. Observe
new_state == "idle"
(or whichever final state your flow uses). b. Then call
lkapi.room.delete_room(...)
(and handle/await the response). Questions to help me narrow this down • Do you see any exceptions or error logs when the missing events occur? If so, please paste them. • Does removing/commenting out the
delete_room
call make the missing events go away? • Which agent states do you observe consistently (list them in order when you do see full events)? • Are you running this in a single-threaded asyncio loop or in a framework that manages tasks differently (e.g., an ASGI server)? If you share the logs from a single failing run (including any exception tracebacks and the printed events around the speaking period), I’ll use those to point to the exact race or exception and suggest a minimal code change. Sources: Turns overview | LiveKit Documentation | Agent session | LiveKit Documentation, with interactive elements
c
but it is working in some cases. in some cases i get all the logs properly
t
Thanks — that extra detail (that the issue is intermittent) helps a lot. I’m responding to your note that some sessions show every state change while others stop after the agent speaks. Short summary of likely causes • Race between your room deletion and the agent lifecycle: deleting the room while the agent is speaking can interrupt later transitions or prevent events from being delivered. • Exceptions or unhandled errors inside the
agent_state_changed
handler or in background tasks you create (e.g., the
disconn_task
) can silently stop further event handling. • Transient connectivity/disconnection or media/TTS timing issues that occasionally cause state transitions to be dropped or delayed. • The handler not logging every event (logging race) — the event may be emitted but your log/print doesn’t run or finish before the process changes state or room is removed. Focused troubleshooting steps (quick to try) 1. Add exception-safe logging inside the handler ◦ Wrap the handler body in try/except and log full tracebacks and the entire
event
object on every call: ▪︎ This will show if an exception is thrown and stops further events. 1. Temporarily disable room deletion in the handler ◦ Comment out the
lkapi.room.delete_room(...)
call and see whether the missing events stop occurring. If the problem disappears, you’ve got a race with deletion. 1. If you must delete the room, wait for a terminal/idle state first ◦ Only call delete after you observe a safe final state, for example: ▪︎ Wait for
new_state == "idle"
OR schedule deletion after a short delay once
speaking
finishes. 1. Surface errors from background tasks ◦ Add a done callback or await the
disconn_task
at some point so exceptions aren’t swallowed: ▪︎
disconn_task.add_done_callback(lambda t: logger.error(t.exception()))
1. Reproduce with a minimal test ◦ Run a small script that starts a session, triggers a single speak, and logs every
agent_state_changed
event. Repeat until you either reproduce the failure or confirm stability. 1. Enable Agent Observability (if available) ◦ Observability captures synchronized traces (audio, transcripts, turn boundaries) for individual sessions. Replay a session where events are missing and compare it to a clean session to spot timing or disconnection differences. Safe code pattern suggestion • Avoid performing destructive operations (room deletion or immediate session teardown) from inside the state-change handler while the agent may still be speaking. • Prefer this sequence: a. Observe the final safe state (for example,
idle
). b. Then call
lkapi.room.delete_room(...)
and handle its result. Questions to narrow this down • When the logs are missing, do you see any exceptions or other error messages elsewhere in your process logs? Paste any traces if available. • Does disabling/commenting out the
delete_room
call eliminate the missing-event behavior? • Are you running this inside a long-running asyncio loop (single process) or inside an environment that may cancel tasks (e.g., a worker that restarts)? • Do you have Agent Observability enabled so you can replay one failing session? If you can share a short log snippet from a failing run (prints from the handler plus any related errors) I’ll look for exact signs of a race or exception and recommend the smallest code change to fix it. Sources: Turns overview | LiveKit Documentation | Agent session | LiveKit Documentation | Streamline troubleshooting with Agent Observability | LiveKit | Streamline troubleshooting with Agent Observability | LiveKit | Job lifecycle | LiveKit Documentation, with interactive elements
c
yes the issue still persists even if i remove the code to disconnect the room
t
Thanks — I appreciate the extra context and your patience. You’ve confirmed the problem is intermittent and continues even after removing the room-deletion code, so let’s dig deeper and narrow this down. What I’m responding to: your note that missing state-change events still happen after you removed the room deletion. Immediate next checks (please try these and tell me what you find) 1. Add defensive logging inside the event handler ◦ Wrap the handler in try/except and log the full traceback, plus the whole
event
object every time it runs: ▪︎ This will reveal any exceptions that might be stopping subsequent event handling. 1. Confirm the handler always runs to completion ◦ Make the handler extremely simple temporarily: ▪︎ Just print/log
old_state
,
new_state
, and a high-resolution timestamp. ▪︎ Run multiple sessions to see if the event emission itself is missing or if your handler sometimes fails. 1. Capture process-level errors and unhandled asyncio exceptions ◦ Ensure you have an unhandled-exception hook or logger for asyncio tasks: ▪︎ Add
loop.set_exception_handler(...)
to log any uncaught exceptions. ▪︎ Add done callbacks to any created tasks so exceptions aren’t swallowed: ▪︎
task.add_done_callback(lambda t: logger.error(t.exception()))
1. Run a minimal reproducible test ◦ Create a tiny script that: i. Starts the session. ii. Registers only the
agent_state_changed
handler. iii. Triggers a single agent speak. iv. Logs every state change. ◦ Run that repeatedly to see whether you can reproduce the intermittent drop in isolation. 1. Check for media/TTS timing or playback issues ◦ If the agent’s TTS finishes rapidly or there are audio pipeline glitches, the transition events might overlap or be delayed. ◦ If you can, log TTS start/end boundaries or subscribe to any playback-complete events available. 1. Verify the runtime environment ◦ Tell me if you run in: ▪︎ Plain asyncio single-process script ▪︎ An ASGI server or task runner that may cancel tasks ▪︎ Container/orchestration that may restart processes ◦ Environments that cancel tasks or recycle workers can drop events intermittently. 1. Enable Agent Observability for replay (if available) ◦ Observability gives synchronized session traces (audio, transcripts, turn boundaries). Replay a failing session and a successful one side-by-side to spot timing or connection differences. What to share with me to diagnose faster • A short log snippet from a failing run containing: ◦ Printed
old_state
and
new_state
lines before/after the missing period. ◦ Any exception tracebacks or process-level error logs. • The minimal test script (if you made one) or the small part of your handler code with added logging. • Which environment you run in (script, ASGI, container, etc.), and Python version. Why these checks matter • The intermittent nature typically points to a race, swallowed exception, or the runtime cancelling work. Seeing the raw logs and environment will let me point to the exact likely cause and the tiniest change to fix it. If you run the minimal test and paste one failing log (handler prints plus any python exception traces), I’ll analyze it and give a precise code change or configuration tweak. , with interactive elements