Skip to content

Reusable Realtime Session across Handoffs & Agent Tasks#1193

Open
toubatbrian wants to merge 6 commits intobrian/reuse-sttfrom
brian/reuse-realtime
Open

Reusable Realtime Session across Handoffs & Agent Tasks#1193
toubatbrian wants to merge 6 commits intobrian/reuse-sttfrom
brian/reuse-realtime

Conversation

@toubatbrian
Copy link
Copy Markdown
Contributor

No description provided.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 1, 2026

🦋 Changeset detected

Latest commit: dd61a2a

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

This PR includes changesets to release 22 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero 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

@toubatbrian toubatbrian changed the base branch from main to brian/reuse-stt April 1, 2026 22:45
@chatgpt-codex-connector
Copy link
Copy Markdown

💡 Codex Review

if (signal) {
const abortPromise = waitForAbort(signal);
while (true) {
const result = await Promise.race([reader.read(), abortPromise]);

P1 Badge Handle pre-aborted signals before entering read loop

readStream races reader.read() against waitForAbort(signal), but waitForAbort only resolves on a future abort event. If the signal is already aborted when readStream starts (which can happen when cancellation wins a task startup race during pause/close), the abort branch never resolves and the generator keeps waiting on stream reads instead of exiting promptly, so cancellation can hang until the source ends.


taskFn: (abortController: AbortController) =>
this.realtimeSayTask(handle, text, {}, abortController),

P2 Badge Preserve addToChatCtx behavior in realtime say path

The new realtime say branch drops the addToChatCtx option by calling realtimeSayTask without passing it. Unlike the TTS path, realtime generation always inserts an assistant message into chat context, so session.say(..., { addToChatCtx: false }) no longer works for realtime-without-TTS configurations and will unexpectedly mutate conversation history.


if (this.detachRequested) {
this.detachRequested = false;
return;

P1 Badge Make detach state pump-local during source handoff

detachRequested is shared across all pump instances. During rapid detach+reattach, a newly attached pump can reach finally first (for example, if the new source ends immediately), clear this flag, and then the old detached pump will run the normal close path and close the shared writable side. That can terminate the newly attached stream unexpectedly and drop post-handoff audio.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

View 13 additional findings in Devin Review.

Open in Devin Review

Comment on lines +2890 to +2893
} catch (e) {
this.logger.error('failed to say text: %s', String(e));
this.agentSession._updateAgentState('listening');
return;
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.

🟡 realtimeSayTask error handler unconditionally sets agent state to 'listening' ignoring queued speeches

When this.realtimeSession.say() throws in realtimeSayTask, the catch block at line 2892 unconditionally calls this.agentSession._updateAgentState('listening'). This doesn't check whether there are other speeches queued or currently playing, unlike the established pattern in onPipelineReplyDone (agents/src/voice/agent_activity.ts:1577-1581) which checks !this.speechQueue.peek() && (!this._currentSpeech || this._currentSpeech.done()) before transitioning to 'listening'.

If say() is called and fails while other speeches are queued (e.g., from a concurrent generateReply), the agent state would briefly and incorrectly transition to 'listening' before the next queued speech corrects it to 'speaking'.

Suggested change
} catch (e) {
this.logger.error('failed to say text: %s', String(e));
this.agentSession._updateAgentState('listening');
return;
} catch (e) {
this.logger.error('failed to say text: %s', String(e));
this.onPipelineReplyDone();
return;
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.

1 participant