Hi, how do i modify my ai agent to not leave the r...
# ask-ai
f
Hi, how do i modify my ai agent to not leave the room immediately when the user leaves?
t
Thanks for reaching out, muse. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
To modify your AI agent so it does not leave the room immediately when the user leaves, you can disable the automatic session closing behavior by configuring the
RoomInputOptions.close_on_disconnect
setting. Here’s how you can implement this:
Copy code
from livekit.agents import AgentSession, RoomInputOptions

async def entrypoint(ctx: JobContext):
    session = AgentSession(
        stt=deepgram.STT(),
        llm=openai.LLM(),
        tts=cartesia.TTS(),
        vad=silero.VAD.load(),
    )
    
    # Disable automatic session closing when participant disconnects
    room_options = RoomInputOptions(close_on_disconnect=False)
    
    await session.start(agent=MyAgent(), room=ctx.room, room_options=room_options)
You can also implement custom logic to handle user inactivity and control when the agent should leave the room. For example, you can configure a timeout and check for user presence:
Copy code
async def entrypoint(ctx: JobContext):
    session = AgentSession(
        vad=silero.VAD.load(),
        llm=openai.LLM(model="gpt-4o-mini"),
        stt=deepgram.STT(),
        tts=cartesia.TTS(),
        user_away_timeout=12.5,  # Configure timeout in seconds
    )

    inactivity_task: asyncio.Task | None = None

    async def user_presence_task():
        # Try to ping the user 3 times, if no answer, close the session
        for _ in range(3):
            await session.generate_reply(
                instructions=(
                    "The user has been inactive. Politely check if the user is still present."
                )
            )
            await asyncio.sleep(10)

        await asyncio.shield(session.aclose())
        ctx.delete_room()

    @session.on("user_state_changed")
    def _user_state_changed(ev: UserStateChangedEvent):
        nonlocal inactivity_task
        if ev.new_state == "away":
            inactivity_task = asyncio.create_task(user_presence_task())
            return

        # Cancel inactivity task when user becomes active again
        if inactivity_task is not None:
            inactivity_task.cancel()

    await session.start(agent=Agent(instructions="You are a helpful assistant."), room=ctx.room)
By default, the room will automatically close when the last non-agent participant leaves. However, with the above configurations, you can control this behavior and decide when the agent should leave the room.
f
@session.on("user_state_changed") def _user_state_changed(ev: UserStateChangedEvent): logger.info(f"User state changed: {ev.new_state}") print(f"User state changed: {ev.new_state}") nonlocal inactivity_task if ev.new_state == "away": inactivity_task = asyncio.create_task(user_presence_task()) return # Cancel inactivity task when user becomes active again if inactivity_task is not None: inactivity_task.cancel() 2025-07-25 222753,155 - INFO voice-agent - User state changed: listening {"pid": 47827, "job_id": "AJ_M6La3a7d3u29"} 2025-07-25 222803,199 - DEBUG livekit.agents - stream closed {"participant": "museum-visitor", "source": "SOURCE_MICROPHONE", "pid": 47827, "job_id": "AJ_M6La3a7d3u29"} User state changed: away 2025-07-25 222816,349 - INFO voice-agent - User state changed: away {"pid": 47827, "job_id": "AJ_M6La3a7d3u29"} 2025-07-25 222816,616 - INFO voice-agent - Waiting for user response... {"pid": 47827, "job_id": "AJ_M6La3a7d3u29"} 2025-07-25 222823,492 - INFO livekit.agents - process exiting {"reason": "", "pid": 47827, "job_id": "AJ_M6La3a7d3u29"} 2025-07-25 222823,490 - WARNING livekit - livekit:rtc engine453livekit:rtc_engine - received session close: "signal client closed: \"stream closed\"" UnknownReason Resume {"pid": 47827, "job_id": "AJ_M6La3a7d3u29"} 2025-07-25 222823,491 - DEBUG livekit.agents - shutting down job task {"reason": "", "user_initiated": false, "pid": 47827, "job_id": "AJ_M6La3a7d3u29"} 2025-07-25 222823,500 - DEBUG livekit.agents - session closed {"reason": "job_shutdown", "error": null, "pid": 47827, "job_id": "AJ_M6La3a7d3u29"} 2025-07-25 222823,500 - DEBUG livekit.agents - http_session(): closing the httpclient ctx {"pid": 47827, "job_id": "AJ_M6La3a7d3u29"} This is what i get. It leaves the room almost immediately
t
Hey there! I totally understand the frustration of your AI agent unexpectedly leaving the room. Let me break down what might be happening in a friendly way. 1. Room Closure Due to No Participants Think of it like this: if everyone leaves the room, the system automatically closes it down. Even if you've set
close_on_disconnect=False
, the server has its own rules. So if you're the last one in the room, the agent might wave goodbye. 2. Other Session Close Reasons There are a few other reasons your agent might suddenly exit: • You might have ended the conversation. • Someone might have programmatically closed the session. • The room got deleted (oops!). • An unexpected error popped up. 3. Different Disconnect Reasons Sometimes, if the disconnection reason isn't on the standard list, your
close_on_disconnect
setting might not work as expected. It's like a special exception to the rule. Proper Configuration To keep your agent hanging around, make sure you're setting things up right:
Copy code
input_options = RoomInputOptions(close_on_disconnect=False)
await session.start(agent=MyAgent(), room=ctx.room, input_options=input_options)
Just a heads up - this setting isn't a magic solution. It helps prevent disconnection in some cases, but the server and other factors can still cause your agent to leave. Need more help? I'm here for you! Let's figure this out together. Sources: livekit-agents/livekit/agents/voice/room_io/room_io.py | Anatomy of an Agent | LiveKit Docs | Events and error handling | LiveKit Docs
f
async def entrypoint(ctx: agents.JobContext): logger.info(f"connecting to room {ctx.room.name}") await ctx.connect(auto_subscribe=AutoSubscribe.SUBSCRIBE_ALL) # Determine config path based on command line arguments or environment variable config_path = "./config/default_config.yaml" # Check for -rag flag in sys.argv or USE_RAG environment variable if ( "-rag" in sys.argv or "--rag" in sys.argv or os.getenv("USE_RAG", "").lower() in ["true", "1", "yes"] ): config_path = "./config/default_config_rag.yaml" logger.info("Using RAG configuration") # Wait for the first participant to connect participant = await ctx.wait_for_participant() logger.info(f"starting voice assistant for participant: {participant.identity}") logger.info(f"participant attributes: {participant.attributes}") dict_str = participant.attributes.get("personalized_config", "{}") # turn the string into a dictionary if isinstance(dict_str, str): dict_str = ast.literal_eval(dict_str) participant_attributes = { "language": dict_str.get("language"), "speaker_preset": dict_str.get("profile"), } agent_config = AgentConfigLoader( logger, personalized_config=participant_attributes, default_config_path=config_path ) session = AgentSession( stt=agent_config.get_stt_service(), llm=agent_config.get_llm_service(), tts=agent_config.get_tts_service(), vad=silero.VAD.load(), turn_detection="vad", ) @session.on("user_state_changed") def _user_state_changed(ev: UserStateChangedEvent): logger.info(f"User state changed: {ev.new_state}") await session.start( room=ctx.room, agent=Assistant(), room_input_options=RoomInputOptions( # LiveKit Cloud enhanced noise cancellation # - If self-hosting, omit this parameter # - For telephony applications, use
BVCTelephony
for best results noise_cancellation=noise_cancellation.BVC(), close_on_disconnect=False, ), ) await ctx.connect() await session.say(agent_config.get_intro_sentence(), allow_interruptions=True) And i get this: 2025-07-25 224155,702 - INFO voice-agent - User state changed: speaking {"pid": 55868, "job_id": "AJ_VQyoKUpA2omj"} 2025-07-25 224200,686 - DEBUG livekit.agents - received user transcript {"user_transcript": "Entering the museum. Uh, which exhibit can I see today?", "language": "en-US", "pid": 55868, "job_id": "AJ_VQyoKUpA2omj"} 2025-07-25 224200,882 - INFO voice-agent - User state changed: listening {"pid": 55868, "job_id": "AJ_VQyoKUpA2omj"} 2025-07-25 224215,984 - DEBUG livekit.agents - stream closed {"participant": "museum-visitor", "source": "SOURCE_MICROPHONE", "pid": 55868, "job_id": "AJ_VQyoKUpA2omj"} 2025-07-25 224238,623 - INFO livekit.agents - process exiting {"reason": "", "pid": 55868, "job_id": "AJ_VQyoKUpA2omj"} 2025-07-25 224238,622 - DEBUG livekit.agents - shutting down job task {"reason": "", "user_initiated": false, "pid": 55868, "job_id": "AJ_VQyoKUpA2omj"} 2025-07-25 224238,626 - WARNING livekit.agents - rotate_segment called while previous segment is still being rotated {"pid": 55868, "job_id": "AJ_VQyoKUpA2omj"} 2025-07-25 224238,630 - DEBUG livekit.agents - session closed {"reason": "job_shutdown", "error": null, "pid": 55868, "job_id": "AJ_VQyoKUpA2omj"} 2025-07-25 224238,630 - DEBUG livekit.agents - http_session(): closing the httpclient ctx {"pid": 55868, "job_id": "AJ_VQyoKUpA2omj"}
t
Hey there! I noticed you're seeing an AI agent session close with
job_shutdown
. No need to worry - this is actually a normal part of how LiveKit manages its processes. Think of it like a gentle system reset happening behind the scenes. What's Going On:Worker Shutdown: Sometimes your worker process needs a quick refresh, like during updates or system maintenance. • Resource Cleanup: The system is just tidying up, making sure everything's running smoothly. • Graceful Termination: It's basically the system's way of saying, "Okay, time to wrap things up nicely!" Here's a Quick Fix: Want to handle this smoothly? You can add a simple listener in your agent code:
Copy code
@session.on("close")
def on_close(ev: CloseEvent):
    print(f"Agent Session closed, reason: {ev.reason}")
    if ev.reason == CloseReason.JOB_SHUTDOWN:
        # Handle job shutdown specific logic
        pass
This little snippet will help you keep track of what's happening and let you add any custom cleanup steps you might need. Got questions? I'm here to help! Just shoot me a message if you want to dig deeper into this. Sources: tests/test_ipc.py
f
Yes, i'd like the ai agent to stay in the room for at least a minute before leaving
t
Hey there! Want to keep your AI agent hanging out in the room for a bit? No worries, I've got some super simple ways to do that. Think of it like setting a friendly timer for your digital buddy.
Copy code
import asyncio
from livekit.agents import AgentSession, JobContext

async def entrypoint(ctx: JobContext):
    session = AgentSession(
        # your session configuration
    )
    
    # Start a task to disconnect after 1 minute
    async def disconnect_after_duration():
        await asyncio.sleep(60)  # 1 minute
        await session.aclose()
        ctx.delete_room()
    
    # Start the disconnect task
    asyncio.create_task(disconnect_after_duration())
    
    # Start the agent session
    await session.start(agent=Agent(instructions="Your instructions"), room=ctx.room)
Cool Alternative Ways:Using Room Disconnect: Want a straightforward way to say goodbye after a minute? • Using Agent Shutdown: Another neat trick is using `ctx.shutdown()`: These methods are like setting a friendly countdown for your AI agent. Super easy, right? If you need any more help or want to chat through the details, I'm totally here for you! 😊 Sources: AI Agents Framework | livekit/livekit_composite | DeepWiki | Job lifecycle | LiveKit Docs | livekit-protocol/src/livekit.rs | examples/voice_agents/inactive_user.py