Move background queuing logic into dedicated container#174
Conversation
e0ebe24 to
ad9d63e
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| # After all sources are done, scan the buffer for the first error. | ||
| for entry in self._buffer: | ||
| if isinstance(entry, _Error): | ||
| raise entry.exc |
There was a problem hiding this comment.
wait() doesn't remove error from buffer, causing double-raise
Medium Severity
The wait() method scans _buffer with a for loop and raises on the first _Error found, but never removes that _Error from the buffer. After wait() raises, the same error persists in the buffer and will be raised again by subsequent get() or get_nowait() calls. This is exactly the concern raised in the PR discussion — the error needs to be consumed or cleared after being raised to avoid duplicate error propagation.
| async def wait(self) -> None: | ||
| """Wait for all source tasks to complete. Raises if any source failed.""" | ||
| if self._sources: | ||
| await asyncio.gather(*list(self._sources), return_exceptions=True) |
There was a problem hiding this comment.
Cancelling wait() cancels source tasks via gather
Medium Severity
The wait() method uses asyncio.gather() to await source tasks. If wait() is cancelled, gather() propagates the cancellation to all source tasks, destroying the in-flight background work. The old code used asyncio.shield() to protect background tasks from cancellation. This regression means cancelling cleanup() (e.g., during CallEnded handling) now kills background tool execution instead of letting it complete.


What does this PR do?
The event queuing logic for background tasks in LlmAgent was getting a bit unwieldy, so I pulled it into a seperate class.
Also: cleaned up some dead code
Type of change
Testing
Unit tests
Checklist
make formatNote
Medium Risk
Refactors background tool event delivery and cancellation behavior in
LlmAgent, which is concurrency-sensitive and could affect loopback timing or dropped events if edge cases are missed.Overview
Extracts background tool event buffering into a new
BackgroundQueuethat runs each async source as its own task, preserves event ordering (including queued errors), and is cancellation-safe for consumers.Updates
LlmAgentto replace the priorasyncio.Queue+ chained_background_task+_maybe_await_background_event()logic withBackgroundQueue(subscribe/get/get_nowait/is_active/wait) and waits for background sources incleanup().Removes dead
History._append_local_with_event_idcode and adds focused unit tests forBackgroundQueue, plus tightens an existing cancellation regression test to wait deterministically for background completion viaagent._background_queue.wait().Written by Cursor Bugbot for commit ad9d63e. This will update automatically on new commits. Configure here.