Skip to content

fix(voice): keep the session host open for finalize_simulation after the agent ends the call - #2793

Open
u9g wants to merge 1 commit into
mainfrom
fix/simulation-finalize-after-end-call
Open

u9g wants to merge 1 commit into
mainfrom
fix/simulation-finalize-after-end-call

Conversation

@u9g

@u9g u9g commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Problem

When an agent ends the call itself during a simulation, its onSimulationEnd verdict is silently dropped.

The simulator asks for the agent's verdict with finalize_simulation only after the run is over and it has judged the transcript, which takes a few seconds of LLM calls. When the agent hangs up (for example with EndCallTool), AgentSession closes its SessionHost right after the Close event, and EndCallTool's default deleteRoom = true then deletes the room. By the time the simulator sends finalize_simulation, nothing is listening, and the run is reported using only the simulator's own verdict.

Fix

This is the same change as livekit/agents#7986. A session that the agent closes itself (CloseReason.USER_INITIATED) under a simulation keeps its SessionHost serving requests until the simulator finalizes, for at most 10s. Job shutdown awaits session.close() before it disconnects and runs shutdown callbacks, so deleteRoom now waits for the verdict too.

  • Hold ends early on finalize_simulation. onSimulationEnd runs and its verdict is returned.
  • Hold ends early on run_input. The simulator is still driving turns. The host doesn't answer, so the simulator sees the agent leave and records agent_ended, as it does today.
  • No hold outside a simulation, or for other close reasons. A job shutdown or participant disconnect isn't held.

The wire protocol is unchanged.

Verification

  • New tests in remote_session.test.ts mirror the Python ones: the hold ends on finalize, run_input releases it without a reply, it gives up after 10s, and it's a no-op outside a simulation.
  • vitest run agents/src/voice agents/src/job passes (1494 tests), as do pnpm typecheck and pnpm api:check.
  • An end-to-end before/after run against production LiveKit Cloud is on the Python PR.

…the agent ends the call

Under a simulation the simulator asks for the agent's onSimulationEnd
verdict over the session channel only after the run ends. When the agent
ends the call itself (EndCallTool -> session.shutdown(), USER_INITIATED),
AgentSession close tore down the SessionHost right away and the job then
deleted the room, so finalize_simulation failed with "room session
transport is closed" and the verdict was dropped.

On a USER_INITIATED close under a simulation, SessionHost now keeps
serving until finalize_simulation is answered, a run_input arrives (left
unanswered, which is how the simulator learns the call ended), or 10s
pass. Mirrors livekit/agents.
@u9g
u9g requested a review from a team as a code owner October 20, 2026 13:54
@changeset-bot

changeset-bot Bot commented Oct 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6265c57

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 41 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-anthropic Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-azure Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-did Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-krisp Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-meta Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-protoface Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-speechify Patch
@livekit/agents-plugin-synthesia Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

Devin Review

Comment on lines +2309 to +2312
if (reason === CloseReason.USER_INITIATED) {
// only an agent ending its own session leaves the simulator yet to finalize; a
// job shutdown has to stay within the supervisor's shutdownProcessTimeout
await this.sessionHost?._holdForSimulationFinalize();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Room deletion drops simulation verdict

With deleteRoomOnClose enabled, RoomIO deletes the room before _holdForSimulationFinalize begins. The simulator cannot deliver finalizeSimulation, so the agent's verdict is lost.

Learn more

A session's Close listeners run before the new hold. RoomIO.onAgentSessionClose starts room deletion directly when deleteRoomOnClose is enabled. The host uses that room's byte-stream handler to receive finalization, so waiting afterward cannot preserve the channel. The end-call tool uses a job shutdown callback instead, which runs after the session closes.

Example: A simulated agent starts with inputOptions: { deleteRoomOnClose: true } and calls session.shutdown(). The Close listener deletes the room while the host waits for finalization; the later request cannot reach the host and the verdict is omitted.

Recommended fix: Defer RoomIO's deletion until after the simulation hold and response, while retaining existing deletion behavior for other close reasons. Test with a real AgentSession and RoomIO using deleteRoomOnClose: true.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +2309 to +2312
if (reason === CloseReason.USER_INITIATED) {
// only an agent ending its own session leaves the simulator yet to finalize; a
// job shutdown has to stay within the supervisor's shutdownProcessTimeout
await this.sessionHost?._holdForSimulationFinalize();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 External shutdown stalls simulated jobs

When a simulated job shuts down externally, finalizeSession calls session.close(), which chooses USER_INITIATED. The hold then waits ten seconds without finalization, delaying room disconnect and shutdown callbacks.

Learn more

The shutdown path finalizeSession calls session.close() regardless of why the job stopped. AgentSession.close schedules closing with USER_INITIATED if no close is already in progress. The new reason check thus treats an externally requested job shutdown as an agent hangup under a simulation. With no request to release the hold, cleanup runs only after its 10-second timeout.

Example: A worker terminates a simulated job while the agent session remains active. Job cleanup calls session.close(), and the room stays connected for another ten seconds instead of disconnecting immediately.

Recommended fix: Pass the actual job shutdown reason into session cleanup or distinguish an agent-initiated close from the cleanup call before entering the hold. Add a lifecycle test for external job shutdown with a real session and host.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant