Skip to content

fix: stop dropping session host responses - #2259

Open
rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
slung-dazed-breaches
Open

fix: stop dropping session host responses#2259
rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
slung-dazed-breaches

Conversation

@rosetta-livekit-bot

@rosetta-livekit-bot rosetta-livekit-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Ports livekit/agents#6781.

Summary

  • raise room/TCP transport send failures and serialize concurrent sends
  • drain in-flight request handlers and queued events during shutdown
  • preserve inbound stream and outbound event ordering with single drainers
  • add RemoteSession.waitForReady parity and retry transport startup failures
  • port all 9 source regression tests
Source diff coverage
  • livekit-agents/livekit/agents/voice/remote_session.py: adapted to agents/src/voice/remote_session.ts. Python channels, asyncio locks/tasks, and second-based deadlines map to JS queues, Mutex, Task, and a 3000 ms shared deadline. All behavior is retained: explicit send failures, serialized room/TCP writes, ordered single-reader room ingestion, graceful handler draining and request-type logging, queued single-writer events (including pre-start events), and readiness retry/error propagation. The target lacked RemoteSession.waitForReady, so that source dependency was ported as public API rather than omitted.
  • tests/test_remote_session.py: adapted to agents/src/voice/remote_session.test.ts. All 9 added source test cases are ported to Vitest using JS promises, fake RTC byte streams, and logger spies while retaining the source assertions.

No source files or behaviors were marked not applicable.

Verification

  • pnpm test agents/src (115 files, 1597 passed, 5 skipped)
  • pnpm test agents/src/voice/remote_session.test.ts (29 passed)
  • pnpm --filter @livekit/agents typecheck
  • pnpm build (40 packages)
  • pnpm lint (passes with pre-existing warnings)
  • pnpm format:check
  • cue-cli text-mode runtime drive: initial greeting and subsequent exact port validated assistant framework event both resolved

The repository-wide pnpm test was also run: 2065 tests passed, with unrelated failures from a missing CEREBRAS_API_KEY, invalid/missing model assets, a Hugging Face network timeout, and existing example/plugin assertions. The complete touched-package suite above passes. pnpm --filter @livekit/agents api:check is blocked before API comparison by the existing generated export * as declaration syntax unsupported by the pinned API Extractor.


Ported from livekit/agents#6781

Original PR description

Alternative to #6665, addressing livekit/agents#6661. Diagnosis is @samanyugoyal2010's — commits are co-authored to them, happy to close in favour of theirs.

What was happening

A run_input response that never reached the client looked exactly like a hung agent: the caller blocked for its full 60s timeout while the transcript was complete and the agent log showed a clean shutdown.

Three paths dropped one silently:

  • RoomSessionTransport.send_message swallowed send failures into a warning and returned.
  • The same method returned early when the room was gone.
  • SessionHost.aclose cancelled in-flight handlers, discarding the response of a request that had already done its work.

Nothing serialized writes either, and every inbound message spawned a task to read it — which could finish out of order and deliver messages swapped.

The fix

  • Transports raise instead of dropping, and serialize sends with a lock.
  • aclose drains handlers before cancelling, and logs the request types it gave up on.
  • Reads and event writes each get one long-lived drainer instead of a task per message.

Responses stay direct — a handler awaits the transport and gets the failure — so no per-message future is needed to hand results back. Events are the only thing that needs a queue.

14 tests, each verified to fail without its change.

Caveats

@rosetta-livekit-bot
rosetta-livekit-bot Bot requested a review from a team as a code owner August 11, 2026 05:50
@changeset-bot

changeset-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 480fb6b

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

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

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.

View 2 additional findings in Devin Review.

Open in Devin Review

Comment on lines 365 to +368
override async sendMessage(msg: pb.AgentSessionMessage): Promise<void> {
const socket = this.socket;
if (this.closed || socket === null) return;

const data = msg.toBinary();
const header = Buffer.allocUnsafe(TCP_HEADER_SIZE);
header.writeUInt32BE(data.length, 0);
const flushed = socket.write(Buffer.concat([header, data]));
if (this.closed || this.socket === null) {
throw new Error('tcp session transport is closed');
}

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.

🟡 Console audio messages sent without error handling can now crash the process during shutdown

Outgoing session messages are now rejected instead of silently dropped once the connection is gone (throw new Error('tcp session transport is closed') at agents/src/voice/remote_session.ts:366-368), but the console audio path still fires these sends without handling a failure, so a shutdown-time send turns into an unhandled rejection.
Impact: A console/agent run can log spurious errors or terminate abruptly while shutting down instead of exiting cleanly.

Fire-and-forget senders in console_io are not adapted to the new throwing contract

Previously both transports returned silently when closed/disconnected, so every caller was safe. Now TcpSessionTransport.sendMessage (agents/src/voice/remote_session.ts:365-375) and RoomSessionTransport.sendMessage (agents/src/voice/remote_session.ts:210-219) reject.

Unhandled callers that were not updated:

  • agents/src/voice/console_io.ts:124void this.transport.sendMessage(...) in TcpAudioOutput.flush()
  • agents/src/voice/console_io.ts:149void this.transport.sendMessage(...) in TcpAudioOutput.clearBuffer()

The console entrypoint runs in the main process and installs no unhandledRejection handler (unlike agents/src/ipc/job_proc_lazy_main.ts:266), so a rejection there follows Node's default abort-on-unhandled-rejection behavior. AgentSession.close() closes the session host — and therefore the shared TCP transport — at agents/src/voice/agent_session.ts:1805, while console.ts closes the same transport again in its finally block; any flush/clear emitted after that point rejects.

Additionally TcpAudioOutput.captureFrame (agents/src/voice/console_io.ts:114) awaits the send, so a closed transport now propagates an error up the audio output pipeline instead of being ignored.

Prompt for agents
The transports in agents/src/voice/remote_session.ts now reject on send failure (RoomSessionTransport.sendMessage and TcpSessionTransport.sendMessage throw 'room/tcp session transport is closed' and wrap stream errors) instead of silently returning as before. Callers that previously relied on the silent no-op were not updated: agents/src/voice/console_io.ts uses `void this.transport.sendMessage(...)` in TcpAudioOutput.flush() and TcpAudioOutput.clearBuffer(), which now produces unhandled promise rejections when the transport is already closed (the console entrypoint in agents/src/console.ts has no process-level unhandledRejection handler). TcpAudioOutput.captureFrame also awaits the send and will now propagate errors up the audio output pipeline during teardown. Audit all call sites of SessionTransport.sendMessage and attach appropriate catch/log handling (or a small helper) so shutdown-time sends degrade gracefully.
Open in Devin Review

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

return response;
}

async waitForReady(timeout = 5000, retryInterval = 500): Promise<void> {

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.

🟡 New public readiness method is added without documentation

A new publicly exported method is added (waitForReady at agents/src/voice/remote_session.ts:1495) with no TypeDoc comment, so its behavior and parameters are missing from the generated API docs.
Impact: Users of the published package get an undocumented public API entry.

Repository rule

CONTRIBUTING.md requires: "If writing new methods/interfaces/enums/classes, document them. This project uses TypeDoc for automatic API documentation generation, and every new addition has to be properly documented." RemoteSession is exported from agents/src/voice/index.ts, so waitForReady(timeout = 5000, retryInterval = 500) is part of the public API surface and needs a doc comment describing the millisecond units, retry behavior, and thrown errors.

Suggested change
async waitForReady(timeout = 5000, retryInterval = 500): Promise<void> {
/**
* Wait until the remote session host answers a ping, retrying transport failures.
*
* @param timeout - Overall deadline in milliseconds
* @param retryInterval - Per-attempt timeout / delay between retries in milliseconds
* @throws The last transport error, or a timeout error if the deadline elapses.
*/
async waitForReady(timeout = 5000, retryInterval = 500): Promise<void> {
Open in 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.

0 participants