dazzling-truck-32153
12/17/2025, 12:10 AMtall-airline-16756
12/17/2025, 12:10 AMtall-airline-16756
12/17/2025, 12:10 AMemptyTimeout when creating the room to delay auto-closure (example: empty_timeout=300 keeps it open 5 minutes).
• Detect the disconnection in your agent and restart or reconnect the agent if you need continuous presence.
If you tell me whether you control the client joining logic or only the agent, I can suggest exact changes or sample reconnection code.
Sources: Rooms, participants, and tracks | LiveKit docs | Job lifecycle | LiveKit docs | Managing rooms | LiveKit docsdazzling-truck-32153
12/17/2025, 1:46 AMtall-airline-16756
12/17/2025, 1:46 AMemptyTimeout when creating the room? If so, what value?
4. Do you have reconnect logic in the agent/client when the room closes? If yes, describe what it does (or share the relevant snippet).
5. Share timestamps or a short excerpt of logs around the disconnect (before and after) if possible.
With those details I’ll suggest targeted fixes (keep-alive approaches, emptyTimeout tuning, or reconnect handling).dazzling-truck-32153
12/17/2025, 1:53 AMdazzling-truck-32153
12/17/2025, 1:53 AMtall-airline-16756
12/17/2025, 1:53 AMtall-airline-16756
12/17/2025, 1:54 AM2025-12-17T01:52:10.882051+00:00. Are they still present and connected? If they briefly disconnected (network blip, tab closed, permission error), the room will close.
1. Inspect client-side disconnect/reconnect behavior for the human participant
◦ Look for browser/network errors, WebRTC ICE failures, or app-level sign-outs around that timestamp. These commonly cause an abrupt leave even when audio is mid-sentence.
1. Verify agent vs Standard participant types
◦ Ensure the person the agent is speaking with is a Standard participant (not an Agent). Agents alone won’t keep the room alive.
1. Check room empty_timeout semantics (you already set it to 300)
◦ empty_timeout=300 delays closure for up to 5 minutes AFTER the room becomes empty. It does not prevent immediate closure if the last Standard participant disconnects. So if the human left at 015210, the room will still enter the empty state and agents may be shut down depending on your agent handling.
1. Add or confirm reconnect/rejoin logic for both sides
◦ On human client: implement automatic reconnect attempts on network drop and surface errors so temporary blips don’t cause a permanent leave.
◦ On agent side: detect room disconnected and attempt to rejoin or restart the worker so the agent can resume when the participant returns.
1. Capture richer logs around the event
◦ Share the human client logs (join/leave/ICE/errors) from ±30s of the timestamp. Also check server-side participant-removed events. Those logs will show whether the human actually left or experienced a transient failure.
If you want, next I can:
• Show a minimal reconnect pattern for the browser client (auto-retry on disconnect).
• Show how to detect room disconnected in your agent and automatically restart the agent session.
Which would you like me to provide first — the client reconnect snippet or agent restart handling?tall-airline-16756
12/17/2025, 1:55 AMtall-airline-16756
12/17/2025, 1:57 AMsession.start(...) and any long-running async work so the job logs failures instead of silently exiting.
• Register a shutdown callback to log and optionally delete the room or notify the client:
◦ Log reason and stack trace
◦ Optionally call room deletion if you want the room to end when the agent exits
• Implement automatic agent restart/redeploy: on agent exit, restart the job so it can rejoin.
Client-side mitigation (so the participant isn’t left confused)
• Listen for participantLeft events and show UI that the agent disconnected; offer to wait/auto-reconnect or trigger redeploy:
room.on('participantLeft', (p) => {
if (p.kind === 'AGENT') {
// notify user and attempt recovery
}
});
• Optionally provide a retry button that requests a new agent instance and joins it to the room.
If you want, I can:
• Review the agent logs if you paste the error excerpt around the timestamp.
• Show a minimal try/catch + shutdown-hook snippet for your agent code to ensure errors are logged and the job restarts gracefully.
Which would you like me to do next?
Sources: Job lifecycle | LiveKit docs | Agent server | LiveKit docs | Deploying to custom environments | LiveKit docs | Agent deployment CLI reference | LiveKit docs | Connecting to LiveKit | LiveKit docstall-airline-16756
12/17/2025, 1:58 AMsession.start(...)? (Yes/No)
4. Are you seeing any host-level events around the same time (container restart, OOM kill, platform restart)?
Immediate actions to add (apply now — minimal changes)
• Add a top-level try/catch around your agent entry and log the full exception + stack trace before calling any shutdown.
• Add a shutdown callback that logs the shutdown reason and stack and emits a heartbeat every 10–15s so you can see when the agent last reported alive.
• If deployed in a container, enable container stdout/stderr capturing and check the host for OOM/killed events.
Example patterns I can provide for your language:
• A minimal try/catch + shutdown-hook to log errors and call ctx.shutdown(reason) (I can show Python or Node snippet — which do you want?).
• A simple heartbeat loop that posts a timestamp to logs every 10s so you can correlate disappearance with last heartbeat.
Client-side mitigation (short-term)
• Detect participantLeft for AGENT and show a reconnect prompt or automatically request a new agent instance.
• Optionally delete the room in the agent shutdown hook so the human isn’t left on a ghost call.
Which SDK/language should I give the concrete code snippets for (Python or Node)? I’ll send a minimal try/catch + shutdown-hook and a heartbeat snippet you can drop into your agent.dazzling-truck-32153
12/17/2025, 1:59 AMtall-airline-16756
12/17/2025, 1:59 AMimport traceback
import logging
logger = logging.getLogger("agent")
logger.setLevel(<http://logging.INFO|logging.INFO>)
async def run_agent(session, agent, room):
try:
await session.start(agent=agent, room=room)
except Exception as e:
logger.error("Agent session crashed: %s", e)
logger.error(traceback.format_exc())
# decide: re-raise, ctx.shutdown(reason) or attempt restart
raise
2) Add a shutdown callback that logs the reason and attempts cleanup
def add_shutdown_hook(ctx):
def on_shutdown(reason=None):
<http://logger.info|logger.info>("Shutdown callback fired. reason=%s", reason)
# any cleanup: close files, notify monitoring, delete room if desired
ctx.add_shutdown_callback(lambda: on_shutdown(getattr(ctx, "reason", None)))
3) Add a heartbeat to detect silent exits (logs every 10s)
import asyncio
async def heartbeat_task(stop_event):
while not stop_event.is_set():
<http://logger.info|logger.info>("heartbeat alive")
await asyncio.sleep(10)
Start it alongside your agent and cancel on shutdown so you can see the last heartbeat timestamp.
4) Enable verbose SDK/HTTP logging
• Ensure your Python logging captures stdout/stderr from the process and set LiveKit/logging to DEBUG so network/ICE and SDK internals are recorded.
5) Check host/container events
• If running in container/cloud, check system logs for OOM kills, container restarts, or platform lifecycle events around the timestamps where the agent disappears.
6) Client-side mitigation
• Have the client listen for participantLeft for AGENT and show a message or request a new agent when detected.
If you want, I can:
• Provide a combined minimal example that wires these pieces into your existing agent entry (drop-in).
• Show how to call ctx.shutdown("reason") properly from the exception handler.
Which would you prefer?tall-airline-16756
12/17/2025, 2:02 AMtransfer_caller / start_briefing_agent) to:
• Capture full tracebacks on errors
• Emit heartbeats so you can see last alive time
• Register a shutdown callback that logs reason and optionally deletes the consult room
• Ensure the agent restarts or surfaces errors instead of silently exiting
Paste this into your agent job entrypoint (or adapt into your existing entry function). It’s minimal and safe to run alongside your current functions.
import asyncio
import logging
import traceback
from livekit import agents, rtc
logging.basicConfig(level=<http://logging.INFO|logging.INFO>)
logger = logging.getLogger("agent")
async def safe_start(session, agent, room, consult_room_name):
try:
await session.start(agent=agent, room=room)
except Exception as e:
logger.error("Unhandled exception in session.start(): %s", e)
logger.error(traceback.format_exc())
# Optionally: call ctx.shutdown("session_error") if you have ctx
raise
async def heartbeat(stop_event, room_name):
while not stop_event.is_set():
<http://logger.info|logger.info>("heartbeat: agent alive in %s", room_name)
try:
await asyncio.wait_for(stop_event.wait(), timeout=10.0)
except asyncio.TimeoutError:
continue
async def entrypoint(ctx: agents.JobContext):
room = ctx.room
room_name = room.name
<http://logger.info|logger.info>("Agent starting in room=%s", room_name)
stop_event = asyncio.Event()
# Shutdown hook - logs reason and attempts optional cleanup
async def on_shutdown():
reason = getattr(ctx, "reason", None)
<http://logger.info|logger.info>("Shutdown callback fired. reason=%s", reason)
# Optional: delete consult room so participant isn't left on ghost call
# from livekit import api
# await api.RoomServiceClient(...).delete_room(room_name)
stop_event.set()
ctx.add_shutdown_callback(on_shutdown)
hb_task = asyncio.create_task(heartbeat(stop_event, room_name))
try:
await ctx.connect() # ensure agent is connected to the room
<http://logger.info|logger.info>("Agent connected to room %s", room_name)
# Wait for a Standard participant to appear (safeguard)
participant = await ctx.wait_for_participant()
<http://logger.info|logger.info>("Participant joined: %s (kind=%s)", participant.identity, participant.kind)
# --- Run your transfer logic here ---
# e.g. await transfer_caller(participant.identity, caller_room_name, chat_ctx)
# If you call into your existing functions, wrap them with try/except and log.
# Example: call the session start safely if you create `session` earlier
# await safe_start(session, agent, room, consult_room_name)
# Block until shutdown is requested
await stop_event.wait()
except Exception as e:
logger.error("Exception in agent entrypoint: %s", e, exc_info=True)
finally:
# Cleanup
hb_task.cancel()
try:
await hb_task
except asyncio.CancelledError:
pass
<http://logger.info|logger.info>("Agent entrypoint finished for room=%s", room_name)
if __name__ == "__main__":
worker = agents.Worker(entrypoint_fnc=entrypoint, worker_type=agents.WorkerType.ROOM)
agents.cli.run_app(worker)
Quick next steps I recommend you do now:
1. Drop this into the agent job and deploy it.
2. Reproduce the issue and check logs for:
◦ Last heartbeat timestamp (shows when agent stopped)
◦ Shutdown callback log (shows reason if any)
◦ Full exception traceback (if crash)
1. If the agent still disappears with no shutdown log or traceback, check host/container logs for OOM kills or restarts.
If you want, I can:
• Integrate this template with your exact transfer_caller / start_briefing_agent flow (paste those functions and I’ll return a combined file).
• Provide a small snippet to delete the consult room in the shutdown hook so the participant isn’t left on a ghost call.
Which would you like next?
Sources: Job lifecycle | LiveKit docs | Deploying to custom environments | LiveKit docs | examples/drive-thru | livekit/livekit_composite | DeepWikidazzling-truck-32153
12/17/2025, 2:03 AMtall-airline-16756
12/17/2025, 2:03 AM