You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
High sub-agent fanout creates status storms that put pressure on the bounded engine op channel (32) and event channel (256). Because Op::ListSubAgents sends used .send().await, the engine or TUI event loop could stall when the receiver was not draining fast enough.
Change
Switched all ListSubAgents refresh-type send paths from blocking send().await to non-blocking try_send():
crates/tui/src/core/engine.rs — Op::ListSubAgents handler now uses tx_event.try_send(Event::AgentList{...}) with a tracing::debug! on channel-full drop.
crates/tui/src/core/engine/handle.rs — Added EngineHandle::try_send() non-blocking method that delegates to self.tx_op.try_send(op).
crates/tui/src/tui/ui.rs — Converted 3 engine_handle.send(Op::ListSubAgents).await call sites to engine_handle.try_send(Op::ListSubAgents).
Guardrails (per the issue)
Critical events unchanged: approval prompts, tool completions/results, fatal errors, cancellation, and parent completion all remain on send().await.
Only refresh-type events use try_send: ListSubAgents is a sidebar refresh that converges on the next drain cycle.
No capacity increase: channel buffer sizes unchanged.
Code Review — PR #3813: fix(tui): use nonblocking send for ListSubAgents refresh events
Summary: Correct fix for the backpressure problem. The guardrail is respected — critical event paths remain on .send().await, only the refresh ListSubAgents ops switch to try_send. No logic errors or race conditions found. Two minor observations below.
Findings
1. Closed and Full are conflated in the debug log (engine.rs:1534–1538)
TrySendError has two variants: Full (expected under load) and Closed (receiver dropped — TUI is gone). Both hit the same message. A Closed error will repeat every drain cycle until the engine itself shuts down and is harder to diagnose from logs. Consider including _e in the message or branching on the variant:
This test is purely synchronous — try_send doesn't need a runtime. Using #[test] / fn is cheaper and clearer about the non-async nature. Not a blocker, but worth tidying.
What's correct
All three ListSubAgents call sites in ui.rs are correctly converted; no other Op variants are touched.
The EngineHandle::try_send wrapper is clean and appropriately thin.
The first test (engine_handle_try_send_does_not_block_when_op_channel_is_full) correctly exercises the handle's method and uses a synchronous #[test] as it should.
Critical paths (approval, tool completions, fatal errors, cancellation) are verified untouched by inspection of the diff.
The one-refresh-per-drain-batch invariant (subagent_list_refresh_requested flag) is preserved under try_send.
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
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.
Closes #3802 (child of #3800).
Problem
High sub-agent fanout creates status storms that put pressure on the bounded engine op channel (32) and event channel (256). Because
Op::ListSubAgentssends used.send().await, the engine or TUI event loop could stall when the receiver was not draining fast enough.Change
Switched all
ListSubAgentsrefresh-type send paths from blockingsend().awaitto non-blockingtry_send():crates/tui/src/core/engine.rs—Op::ListSubAgentshandler now usestx_event.try_send(Event::AgentList{...})with atracing::debug!on channel-full drop.crates/tui/src/core/engine/handle.rs— AddedEngineHandle::try_send()non-blocking method that delegates toself.tx_op.try_send(op).crates/tui/src/tui/ui.rs— Converted 3engine_handle.send(Op::ListSubAgents).awaitcall sites toengine_handle.try_send(Op::ListSubAgents).Guardrails (per the issue)
send().await.ListSubAgentsis a sidebar refresh that converges on the next drain cycle.Tests
engine_handle_try_send_does_not_block_when_op_channel_is_fulllist_subagents_event_try_send_does_not_block_when_event_channel_full