is there any such setting on livekit as "await ctx...
# ask-ai
r
is there any such setting on livekit as "await ctx.wait_until_disconnected()" where I can wait until the data storing is done after the call ends so that room is not deleted without saving the data
t
Thanks for reaching out, Ninad. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’m responding to your question about whether LiveKit has an equivalent to
await ctx.wait_until_disconnected()
so you can wait for post-call data storage before a room is deleted. Short answer • LiveKit does not provide a
wait_until_disconnected()
method or direct equivalent. • You can reliably ensure data is saved before room deletion by using shutdown callbacks and waiting for them to finish with
ctx.aclose()
(and by controlling when you call room deletion). How to do this (recommended) 1. Register a shutdown callback to perform your async data-save logic: ◦ Use
ctx.add_shutdown_callback(your_async_save_fn)
so your saving runs during shutdown. 1. End the session properly: ◦ Call
session.shutdown(drain=True)
if you need to finish speech playback, then call
await ctx.aclose()
—
aclose()
blocks until shutdown callbacks complete. 1. Delete the room only after
aclose()
completes: ◦ Either rely on
delete_room_on_close=True
if you’re certain it runs after your saves, or set
delete_room_on_close=False
and call the delete API yourself after
ctx.aclose()
finishes. 1. Increase shutdown timeout if your saves need more time: ◦ Adjust
shutdown_process_timeout
(for example,
AgentServer(shutdown_process_timeout=30)
) so callbacks have enough time to finish. Minimal example (conceptual)
Copy code
@server.rtc_session(agent_name="my-agent")
async def entrypoint(ctx: JobContext):
    async def save_data_hook():
        await save_transcript_to_db(ctx.room.name)
        await save_call_metadata(ctx.room.name)

    ctx.add_shutdown_callback(save_data_hook)

    # ... run session ...
    session.shutdown(drain=True)
    await ctx.aclose()  # blocks until save_data_hook finishes

    # now safe to delete room
    await api_client.room.delete_room(room=ctx.room.name)
Notes and gotchas • Shutdown callbacks are executed after
session.shutdown()
and before the job terminates. • There is a default timeout for shutdown hooks (10s). If your save can take longer, increase the server’s shutdown timeout. • If you rely on automatic room deletion, make sure the deletion happens after your shutdown callbacks complete — otherwise use manual deletion after
ctx.aclose()
. If you’d like, tell me: • Which language/runtime you’re using (Python example above), and • Whether you’re relying on automatic room deletion or manual deletion. I can then provide a tailored code example or checklist for your exact setup. Sources: Job lifecycle | LiveKit Documentation
r
I am using python and await session.start( room=ctx.room, agent=UserAgent(PROMPT, chat_ctx=prior_ctx), room_input_options=RoomInputOptions(audio_enabled=True, close_on_disconnect=False), room_output_options=RoomOutputOptions(audio_enabled=True), ) from livekit.api import DeleteRoomRequest @ctx.room.on("participant_disconnected") def on_participant_disconnected(participant): print(f"[EVENT] Participant {participant.identity} disconnected") async def safe_cleanup(): # Run the simple static fallback storage await handle_disconnect_storage(session) # Safely shut down the room to exit the job print("[FAILSAFE] Cleanup complete. Deleting room to exit job.") try: await ctx.api.room.delete_room( DeleteRoomRequest(room=ctx.room.name) ) except Exception as e: print(f"[FAILSAFE ERROR] Room deletion failed: {e}") asyncio.create_task(safe_cleanup()) # ---------------------------------------- # DEBUG EVENTS # ---------------------------------------- @session.on("agent_speech_committed") def on_agent_speech(event): print("Agent spoke") @session.on("user_speech_committed") def on_user_speech(event): print("User spoke") # ---------------------------------------- # START CALL # ---------------------------------------- await session.generate_reply( instructions="Start the conversation naturally by introducing yourself and explaining reason for calling.", allow_interruptions=False, ) # Keep the entrypoint alive until the room is deleted or disconnected await ctx.wait_until_disconnected() this is my current code
t
Thanks — I see your Python code and that you’re using
await ctx.wait_until_disconnected()
at the end. I’ll address how to make sure your storage finishes before the room is deleted and point out a few improvements you can make so deletion doesn’t happen prematurely. Key points about your current approach •
wait_until_disconnected()
is not provided as a blocking mechanism to guarantee post-call saves complete. • Spawning a background task with
asyncio.create_task(safe_cleanup())
inside the
participant_disconnected
handler can work, but it may race with process shutdown or room deletion if the runtime exits before the task or its I/O finishes. • The robust pattern is to register a shutdown callback with
ctx.add_shutdown_callback()
and then wait for those callbacks to complete with
await ctx.aclose()
before deleting the room. Recommended changes (concise) 1. Register your cleanup as a shutdown callback instead of launching an independent background task: ◦ This ensures the runtime waits for the callback during agent shutdown. 1. Use
session.shutdown(drain=True)
(if needed) and then
await ctx.aclose()
to block until shutdown callbacks finish. 2. Delete the room only after
ctx.aclose()
completes (or set
delete_room_on_close=False
and delete manually after aclose). 3. If your save can take longer than the default 10s, increase the server shutdown timeout. Suggested structure (conceptual) 1. Add a shutdown callback that performs
handle_disconnect_storage(session)
(async). 2. When you want to end the session: call
session.shutdown(drain=True)
then
await ctx.aclose()
. 3. After
ctx.aclose()
returns, call
await ctx.api.room.delete_room(DeleteRoomRequest(room=ctx.room.name))
. Example outline adapted from your code (conceptual — fits your Python style)
Copy code
# register once inside entrypoint
async def save_data_hook():
    await handle_disconnect_storage(session)

ctx.add_shutdown_callback(save_data_hook)

# ... other setup and session usage ...

# when the session should end:
session.shutdown(drain=True)
await ctx.aclose()  # waits for save_data_hook to finish

# now safe to delete room
try:
    await ctx.api.room.delete_room(DeleteRoomRequest(room=ctx.room.name))
except Exception as e:
    print(f"[FAILSAFE ERROR] Room deletion failed: {e}")
Notes and practical tips • Keep your
participant_disconnected
handler for logging or starting graceful steps, but avoid relying solely on creating tasks there for critical saves. • If you must trigger save from the event handler, call a function that sets state and let the shutdown callback perform the actual persistence, or ensure the created task is awaited by the shutdown sequence. • Increase
shutdown_process_timeout
if your saves frequently take longer than the default 10 seconds. If you want, I can: • Convert your exact code into a ready-to-run snippet with
ctx.add_shutdown_callback()
integrated. • Show how to keep automatic room deletion disabled and perform manual deletion after
ctx.aclose()
. Which would you prefer?
r
Convert your exact code into a ready-to-run snippet with
ctx.add_shutdown_callback()
integrated.
t
Thanks — I’ve converted your code into a ready-to-run Python snippet that integrates
ctx.add_shutdown_callback()
so your post-call storage runs as part of the shutdown sequence and completes before the room is deleted. Notes: • This keeps your existing flow and handlers but registers the async save as a shutdown callback. • We call
session.shutdown(drain=True)
and then
await ctx.aclose()
to wait for shutdown callbacks to finish. • Manual room deletion happens after
ctx.aclose()
returns. If you prefer automatic deletion, set
delete_room_on_close=True
but ensure your saves complete first. • If your save can take longer than the default 10s, increase
shutdown_process_timeout
when creating the server. Ready-to-run example (Python):
Copy code
import asyncio
from livekit.api import DeleteRoomRequest
from livekit.server import AgentServer
from livekit.types import UserAgent, RoomInputOptions, RoomOutputOptions

# replace with your actual prompt and prior_ctx variables
PROMPT = "Hello, I'm the agent."
prior_ctx = None

server = AgentServer(shutdown_process_timeout=30)  # increase timeout if needed

@server.rtc_session(agent_name="my-agent")
async def entrypoint(ctx: "JobContext"):
    session = ctx.session  # your session object

    # Shutdown callback that will run during agent shutdown.
    async def save_data_hook():
        try:
            # perform your async storage using the session/context
            await handle_disconnect_storage(session)
            print("[SHUTDOWN] Data saved successfully.")
        except Exception as e:
            print(f"[SHUTDOWN ERROR] Failed to save data: {e}")

    # Register shutdown callback so the runtime waits for it during shutdown
    ctx.add_shutdown_callback(save_data_hook)

    # Event handlers (logging + non-critical tasks)
    @ctx.room.on("participant_disconnected")
    def on_participant_disconnected(participant):
        print(f"[EVENT] Participant {participant.identity} disconnected")
        # Avoid launching critical storage here; let shutdown callback do it.
        # If you want to kick off early work, set flags or enqueue work for the shutdown hook.

    @session.on("agent_speech_committed")
    def on_agent_speech(event):
        print("Agent spoke")

    @session.on("user_speech_committed")
    def on_user_speech(event):
        print("User spoke")

    # Start the session (your original call)
    await session.start(
        room=ctx.room,
        agent=UserAgent(PROMPT, chat_ctx=prior_ctx),
        room_input_options=RoomInputOptions(audio_enabled=True, close_on_disconnect=False),
        room_output_options=RoomOutputOptions(audio_enabled=True),
    )

    # Kick off initial reply
    await session.generate_reply(
        instructions="Start the conversation naturally by introducing yourself and explaining reason for calling.",
        allow_interruptions=False,
    )

    # Wait for normal disconnection (optional).
    # This does not guarantee post-call saves — shutdown callbacks do.
    try:
        await ctx.wait_until_disconnected()
    except Exception:
        # if wait_until_disconnected isn't present/usable in your runtime, ignore
        pass

    # Initiate a clean shutdown: finish speech playback then run shutdown callbacks
    session.shutdown(drain=True)
    await ctx.aclose()  # blocks until save_data_hook completes

    # Now safe to delete the room (manual deletion)
    print("[FAILSAFE] Cleanup complete. Deleting room to exit job.")
    try:
        await ctx.api.room.delete_room(DeleteRoomRequest(room=ctx.room.name))
    except Exception as e:
        print(f"[FAILSAFE ERROR] Room deletion failed: {e}")

# Example stub for the storage function you referenced
async def handle_disconnect_storage(session):
    # Insert your actual storage logic here.
    # For demonstration, simulate async work:
    await asyncio.sleep(1)
    print("[STORE] handle_disconnect_storage completed.")

# Run server (depends on how you normally start the AgentServer)
if __name__ == "__main__":
    server.run()
If you’d like, I can: • Inline your existing
handle_disconnect_storage
implementation in the snippet, • Show a smaller variant that disables automatic room deletion and waits on
aclose()
only, • Or adapt the snippet to a different shutdown timeout value. Which would you prefer?