inside participant disconnect event I am deleting ...
# ask-ai
f
inside participant disconnect event I am deleting room. However, agent might be running a long tool, How know about it from participant disconnect event ?
t
Thanks for reaching out, Dmitry. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — you’re asking how to tell, inside the participant disconnect handler, whether the agent is still running a long-running tool so you can avoid deleting the room prematurely. I can help — a couple quick questions first so I give the right code pattern: 1. Are you using a LiveKit Agent session (the Agents framework) or a custom agent process tied to the room? 2. Which language/runtime are you using (Node, Python, Go, etc.)? If you’re using LiveKit Agents, the safe approaches are: • Check the agent’s state on disconnect. If the agent is
thinking
(actively processing), delay deletion until the agent transitions out of
thinking
. Agents emit state-change events you can listen to. • Track tool executions explicitly. Emit/set a small “tool-running” flag when you start a long-running tool and clear it when the tool finishes (or on the `function_tools_executed`/tool-finished event). On participant disconnect, skip deletion while the flag is set. • Use a shutdown/cleanup hook that runs after the agent finishes (for example, an
on_shutdown
or
add_shutdown_callback
) to perform room deletion once processing completes. • If you need immediate cleanup but want to preserve in-progress work, schedule deletion with a short delay and cancel that deletion if the agent finishes before the timer elapses. If you want, tell me your runtime and whether you use the Agents framework and I’ll provide a small code example showing: • checking agent state in the disconnect handler, and • a simple tool-running flag pattern you can use. Sources: Events and error handling | LiveKit docs | Job lifecycle | LiveKit docs | Tool definition and use | LiveKit docs
f
1. livekit agent 2. python runtime
t
Thanks — since you’re using LiveKit Agents with Python, here’s a concise, practical pattern you can use to avoid deleting the room while an agent is running a long tool. Key ideas • Check the agent session state (look for
thinking
) in the
participant_disconnected
handler. • Maintain an explicit
tool_running
flag (set when a long task starts, cleared when it finishes or when its completion event fires). • Use a shutdown/cleanup callback or wait for the agent to finish before calling
delete_room
. • Optionally schedule a timed fallback delete (with cancellation) to protect against stuck tasks. Minimal example (Python, Agents framework)
Copy code
# conceptual example — adapt to your code structure and APIs

class AgentController:
    def __init__(self, session, room):
        self.session = session    # agent session object
        self.room = room
        self.tool_running = False
        self.shutdown_scheduled = None

        # register handlers
        room.on("participant_disconnected", self.on_participant_disconnected)
        session.on("agent_state_changed", self.on_agent_state_changed)
        session.on("function_tools_executed", self.on_tool_finished)

    async def start_long_tool(self, *args):
        self.tool_running = True
        try:
            # run the long-running tool
            await run_long_tool(*args)
        finally:
            # ensure flag cleared even on error
            self.tool_running = False
            # if a room deletion was scheduled, cancel or perform it here
            await self.maybe_finalize_room()

    async def on_tool_finished(self, event):
        # event fired when tool completes (if available)
        self.tool_running = False
        await self.maybe_finalize_room()

    async def on_agent_state_changed(self, state_event):
        # optional: react to `thinking` -> `idle` transitions
        if state_event.new_state != "thinking":
            await self.maybe_finalize_room()

    async def on_participant_disconnected(self, participant):
        # run when a participant leaves
        # If the agent is actively processing, delay deletion.
        if self.session.current_state == "thinking" or self.tool_running:
            # schedule a fallback deletion after timeout (e.g. 30s)
            if not self.shutdown_scheduled:
                self.shutdown_scheduled = asyncio.create_task(self._fallback_delete(30))
            return

        # safe to delete immediately
        await self.room.delete_room()

    async def maybe_finalize_room(self):
        # called after tools finish or agent becomes idle
        if self.shutdown_scheduled:
            # cancel fallback if agent finished early
            self.shutdown_scheduled.cancel()
            self.shutdown_scheduled = None

        # if no participants remain, it's safe to delete
        if not self.room.participants:
            await self.room.delete_room()

    async def _fallback_delete(self, timeout_seconds):
        try:
            await asyncio.sleep(timeout_seconds)
            # re-check before deleting
            if not (self.session.current_state == "thinking" or self.tool_running):
                await self.room.delete_room()
        except asyncio.CancelledError:
            return
Notes and best practices • The Agents framework emits state-change and tool-executed events — use them rather than relying only on timing. • Always clear the
tool_running
flag in finally blocks to avoid stuck state. • A short fallback timeout (e.g., 20–60s) prevents orphaned rooms if something crashes. • Make your deletion idempotent and tolerant of race conditions (check room existence/participants before deleting). If you’d like, I can: • produce a version tailored to your exact session/room API names, or • show how to wire this into your agent’s
add_shutdown_callback
instead of a scheduled task. Which would help more?
f
def setup_participant_disconnect_handler(self, job_ctx): async def delete_room(participant: rtc.Participant) -> None: logger.info(f"Participant {participant.identity} disconnected - deleting room") try: await job_ctx.api.room.delete_room( api.DeleteRoomRequest(room=job_ctx.room.name) ) logger.info("Room deleted successfully after participant disconnect") except Exception as e: logger.error(f"Error deleting room: {e}") def on_participant_disconnected(participant: rtc.Participant) -> None: """Synchronous wrapper for the async participant disconnect handler""" asyncio.create_task(delete_room(participant)) job_ctx.room.on("participant_disconnected", on_participant_disconnected) return on_participant_disconnected ctx.add_shutdown_callback(lambda: shutdown_hook(ctx, worker_metadata, session)) agent.setup_participant_disconnect_handler(ctx) Do I need both ?
t
Short answer: Yes—keep both. They serve different purposes and together make teardown safe and reliable. Why: • The participant disconnect handler is the trigger to end the session and call
delete_room()
when the human leaves. • The shutdown callback runs after the room/session teardown to perform final cleanup (save state, logs, DB updates, notify services). How to use them together safely (Python, Agents): • In
participant_disconnected
, do not unconditionally force-delete if the agent is still processing. Check
session.current_state == "thinking"
or a
tool_running
flag; if processing, schedule a fallback delete (short timeout) and return. • When tools finish or agent state becomes idle, call a
maybe_finalize_room()
that cancels the fallback and calls
delete_room()
if no participants remain. • Register
ctx.add_shutdown_callback(...)
for post-delete cleanup (persist results, release resources). Your posted pattern is fine: keep the async
delete_room
task on participant disconnect and also register
add_shutdown_callback
for shutdown work. If you want, I can provide a compact, integrated version of your exact code wired to check
session.current_state
and a
tool_running
flag. Which would you prefer? Sources: Job lifecycle | LiveKit docs