Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/agents/voice/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,10 @@ async def _stream_audio(
)
logger.error("Error streaming audio: %s", e)

# Signal completion for whole session because of error
await local_queue.put(VoiceStreamEventLifecycle(event="session_ended"))
await self._add_error(e)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve ordered audio before surfacing TTS errors

When a later TTS segment fails faster than an earlier segment, this puts the terminal error directly on the public queue instead of going through _ordered_tasks/_dispatch_audio. Because stream() stops as soon as it reads VoiceStreamEventError, consumers can miss already-started earlier audio and lifecycle events whenever concurrent segment synthesis completes out of order; route the terminal error through the ordered path, or otherwise wait for earlier local queues before publishing it.

AGENTS.md reference: AGENTS.md:L109-L110

Useful? React with 👍 / 👎.

await local_queue.put(None)
if self.text_generation_task is not None and not self.text_generation_task.done():
self.text_generation_task.cancel()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Let the producer finish its failure cleanup

When a TTS segment fails while the streamed multi-turn producer is between turns or waiting on transcription/workflow, this cancellation makes process_turns() enter its finally to close the STT session, but stream() has already received the public error and then _cleanup_tasks() can cancel the same producer again while transcription_session.close()/output._done() is awaited. That second cancellation can interrupt cleanup, leaving the STT connection or trace unfinished; let the producer run its finally to completion, or shield that cleanup, instead of canceling it from the TTS task.

AGENTS.md reference: AGENTS.md:L110-L110

Useful? React with 👍 / 👎.

raise e

async def _add_text(self, text: str):
Expand Down Expand Up @@ -292,8 +294,11 @@ def _cleanup_tasks(self):
self.text_generation_task.cancel()

def _check_errors(self):
for task in self._tasks:
if task.done():
all_tasks = list(self._tasks)
if self.text_generation_task is not None:
all_tasks.append(self.text_generation_task)
for task in all_tasks:
if task.done() and not task.cancelled():
if task.exception():
self._stored_exception = task.exception()
break
Comment on lines +300 to 304

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Retrieve the producer exception before exiting

When a TTS segment fails during a pipeline run, the segment task and text_generation_task can both complete with exceptions because the producer re-raises the gather failure. This loop still scans the segment tasks first and breaks on the first exception, so it never calls exception() on text_generation_task; the original “Task exception was never retrieved” warning remains for that failure path. Continue retrieving all done task exceptions while only preserving the first stored exception.

AGENTS.md reference: AGENTS.md:L109-L110

Useful? React with 👍 / 👎.

Expand Down