fix(voice): guard AgentTask handoff run state#1961
Merged
Conversation
🦋 Changeset detectedLatest commit: 27bd059 The changes in this PR will be included in the next version bump. This PR includes changesets to release 35 packages
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 |
Ports test_handoff_from_pre_run_speech from livekit/agents#6315: a handoff triggered by an onEnter speech that predates session.run() must keep the run alive until the activity transition settles. Without the fix in this PR, run() stalls mid-handoff and the test times out. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eset Match the Python original's `if suspended_handles:` guard: a run created mid-transition has no watched handles yet (its generateReply is deferred behind the activity lock), so calling _markDoneIfNeeded on an empty handle set would resolve it before it produced any events. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
toubatbrian
approved these changes
Jul 7, 2026
toubatbrian
approved these changes
Jul 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
session.run()cannot complete during the activity transition.Testing
pnpm test -- agents/src/voice/agent.test.ts agents/src/voice/testing/run_result.test.tspnpm build:agentscue-clitext-mode runtime validation with a temporary dispatchable AgentTask worker, asserting handoff andfinishtool framework events (not committed)Ported from livekit/agents#6315
Original PR description
fix livekit/agents#6313
Problem
An agent handoff triggered by a speech that predates the active run is not watched by
session.run()'sRunResult. For example, whenAgentSession.start()runson_enterand its reply calls a tool that awaits anAgentTask, nothing watches the tasks driving that handoff. The firstsession.run()then completes as soon as its own reply finishes — while the handoff is still mid-transition (old activity paused, new activity not started) — and the nextsession.run()races the handoff and fails withRuntimeError: cannot schedule new speech, the speech scheduling is draining/pausing(a hang on 1.6.1).This can be worked around with
await session.start(agent, capture_run=True): the captured run exists beforeon_enterspeaks, so the on_enter speech is watched andstart()waits for the handoff chain to settle before returning.1.6.0 has the same bug but wins another race: the run still completes mid-handoff, but the next
session.run()'s speech lands before the old activity is paused, so it is queued on the old activity and answered by the outgoing agent.Fix
AgentTask.__await_implalready assumes the tasks driving the handoff (blocked_tasks) are watched by the active run — it unwatches them right after installing the new activity'son_enterguard. This PR adds them to the run's watch list when the handoff starts, so the run cannot complete mid-handoff; they are unwatched once the handoff settles as before, so the run does not stay blocked on the long-lived tool task.Added a regression test where the handoff is triggered from a pre-run
on_enterspeech.