Description
After upgrading to a version that includes #4730, we're seeing a RuntimeError when a SpeechHandle gets interrupted during an AgentTask execution. The __await_impl finally block tries to restore speech_handle.allow_interruptions without checking if the handle is already interrupted.
Error
RuntimeError: Cannot set allow_interruptions to False, the SpeechHandle is already interrupted
Traceback (most recent call last):
File ".venv/lib/python3.14/site-packages/livekit/agents/voice/generation.py", line 609, in _traceable_fnc_tool
val = await function_callable()
...
File ".venv/lib/python3.14/site-packages/livekit/agents/voice/agent.py", line 832, in __await_impl
speech_handle.allow_interruptions = old_allow_interruptions
File ".venv/lib/python3.14/site-packages/livekit/agents/voice/speech_handle.py", line 128, in allow_interruptions
raise RuntimeError(
"Cannot set allow_interruptions to False, the SpeechHandle is already interrupted"
)
Context
We await prebuilt AgentTask subclasses (e.g. GetEmailTask, GetListTask) inside a function tool. The SDK correctly sets up inline_task=True on the tool's asyncio task via _set_activity_task_info in generation.py.
The issue is in __await_impl's finally block which unconditionally restores allow_interruptions:
# agent.py, __await_impl finally block
if speech_handle:
speech_handle.allow_interruptions = old_allow_interruptions
If the user interrupts during the task (or the handle is force-interrupted via cancel() or the new _schedule_speech interrupt paths added in #4730), the handle is already in an interrupted state and the setter raises RuntimeError.
Likely cause
PR #4730 added several new paths that force-interrupt speech handles:
AgentTask.cancel() calls self._activity.interrupt(force=True)
_schedule_speech now calls speech.interrupt(force=True) before raising when scheduling is paused/draining
_aclose_impl loops through active AgentTasks and calls cancel()
These new interruption paths can leave a speech handle in an interrupted state when __await_impl's finally block runs, but the finally block doesn't guard against this.
Suggested fix
Guard the restoration in the finally block:
if speech_handle and not speech_handle.interrupted:
speech_handle.allow_interruptions = old_allow_interruptions
Versions
- livekit-agents: 1.4.3
- Python: 3.14
Description
After upgrading to a version that includes #4730, we're seeing a
RuntimeErrorwhen aSpeechHandlegets interrupted during anAgentTaskexecution. The__await_implfinally block tries to restorespeech_handle.allow_interruptionswithout checking if the handle is already interrupted.Error
Context
We await prebuilt
AgentTasksubclasses (e.g.GetEmailTask,GetListTask) inside a function tool. The SDK correctly sets upinline_task=Trueon the tool's asyncio task via_set_activity_task_infoingeneration.py.The issue is in
__await_impl's finally block which unconditionally restoresallow_interruptions:If the user interrupts during the task (or the handle is force-interrupted via
cancel()or the new_schedule_speechinterrupt paths added in #4730), the handle is already in an interrupted state and the setter raisesRuntimeError.Likely cause
PR #4730 added several new paths that force-interrupt speech handles:
AgentTask.cancel()callsself._activity.interrupt(force=True)_schedule_speechnow callsspeech.interrupt(force=True)before raising when scheduling is paused/draining_aclose_implloops through active AgentTasks and callscancel()These new interruption paths can leave a speech handle in an interrupted state when
__await_impl's finally block runs, but the finally block doesn't guard against this.Suggested fix
Guard the restoration in the finally block:
Versions