Is there a hook that lets me know if the agent has...
# ask-ai
r
Is there a hook that lets me know if the agent has disconnected? Like on_participant_disconnected
t
Thanks for reaching out, Rithvik. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
You're asking if there's a hook to know when an agent disconnects — yes. Use the Room's participant-disconnected event and check the participant's
kind
for
AGENT
. JavaScript:
Copy code
room.on('participantDisconnected', (participant) => {
  if (participant.kind === 'AGENT') {
    console.log('Agent disconnected:', participant.identity);
    // handle agent disconnect
  }
});
Python:
Copy code
@room.on("participant_disconnected")
def on_participant_disconnected(participant):
    if participant.kind == proto.ParticipantInfo.Kind.AGENT:
        print(f"Agent disconnected: {participant.identity}")
        # handle agent disconnect
Go:
Copy code
room.OnParticipantDisconnected(func(p *livekit.RemoteParticipant) {
    if p.Kind() == livekit.ParticipantInfo_AGENT {
        log.Printf("Agent disconnected: %s", p.Identity())
        // handle agent disconnect
    }
})
If you want, I can show how to also listen for
agent_state_changed
or handle reconnect vs. graceful leave—which would you prefer? Sources: Rooms, participants, and tracks | LiveKit docs | Events and error handling | LiveKit docs
r
def *on_participant_disconnected*(_participant_: RemoteParticipant): LOGGER.info( "Participant %s disconnected, deleting room", participant.identity ) async def *delete_room*(_ctx_: agents.JobContext) -> None: await ctx.api.room.delete_room( DeleteRoomRequest( room=ctx.job.room.name, ) ) asyncio.create_task(delete_room(self.ctx)) This is my current code
t
Thanks — this is a good start. A few concise suggestions to make it more robust and ensure it only deletes the room for agent disconnections and avoids unhandled errors: 1. Check participant kind before deleting:
Copy code
def on_participant_disconnected(participant: RemoteParticipant):
    if participant.kind != proto.ParticipantInfo.Kind.AGENT:
        return
    <http://LOGGER.info|LOGGER.info>("Agent %s disconnected, deleting room", participant.identity)
    asyncio.create_task(delete_room(self.ctx))
1. Make
delete_room
resilient to errors and idempotent:
Copy code
async def delete_room(ctx: agents.JobContext) -> None:
    try:
        await ctx.api.room.delete_room(DeleteRoomRequest(room=ctx.job.room.name))
        <http://LOGGER.info|LOGGER.info>("Room %s deleted", ctx.job.room.name)
    except Exception as e:
        LOGGER.exception("Failed deleting room %s: %s", ctx.job.room.name, e)
1. Consider race conditions / multiple disconnect events: • If multiple participants could trigger deletion, detect if the room already deleted or check a guard flag on the job/context before creating the task. Would you like a version that handles reconnect vs. leave (graceful vs. network dropout) or a guard example to prevent duplicate delete attempts?
r
Does this hook also include when the livekit agent leaves or just the remote sip participant?
t
The
participant_disconnected
hook fires for any participant who leaves the room — that includes LiveKit agents and remote SIP participants. Differentiate them by checking
participant.kind
(agent participants have kind
AGENT
). If you need more granularity about an agent's local state (e.g., agent-side detected disconnect vs. network blip), you can also listen for
agent_state_changed
on the agent side. Would you like a short code snippet that ignores non-agent disconnects and debounces duplicate delete attempts?
r
What is the agent_state_changed hook?
t
You're asking what the
agent_state_changed
hook is — it’s an AgentSession event that notifies you when an agent's internal state changes (e.g.,
initializing
,
listening
,
thinking
,
speaking
). What it provides: • Emitted by the
AgentSession
as an
AgentStateChangedEvent
. • Contains
old_state
and
new_state
. •
new_state
is mirrored in the participant attribute
lk.agent.state
for frontend use. Why use it: • Update UI (listening/processing/speaking indicators). • Coordinate audio playback or logging. • Trigger backend logic when the agent begins/stops speaking or starts thinking. Examples: Python:
Copy code
from livekit.agents import AgentStateChangedEvent

@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 event.new_state == "speaking":
        # handle start speaking
Node/TS:
Copy code
session.on(voice.AgentSessionEventTypes.AgentStateChanged, (event) => {
  console.log(`Agent state changed from ${event.oldState} to ${event.newState}`);
  if (event.newState === 'speaking') {
    // handle start speaking
  }
});
Notes for disconnect detection: •
agent_state_changed
tracks runtime behavior, not full session end. • To detect full disconnects, also listen for
AgentSession
close
and room
participant_disconnected
(check
participant.kind === 'AGENT'
). Sources: Events and error handling | LiveKit docs | Web and mobile frontends | LiveKit docs
r
What triggers add_shutdown_callback?
t
You're asking what triggers
add_shutdown_callback
— it runs when the agent session is shutting down. Triggers include: • The session is ended explicitly via
session.shutdown()
or
ctx.shutdown()
. • The room closes because the last non-agent participant leaves. • The agent server receives a termination signal (SIGTERM/SIGINT/SIGQUIT) and finishes draining active sessions. Register the callback with `ctx.add_shutdown_callback(fn)`; it will run during the session's post-shutdown cleanup (useful for saving state, logging, or releasing resources). Sources: Distributed Setup | LiveKit docs | Agent server | LiveKit docs | Job lifecycle | LiveKit docs | Observability data hooks | LiveKit docs