What would you like?
When the model emits multiple tool calls in a single turn, execute them concurrently instead of awaiting each one sequentially. While tools are in flight, render an indicator per tool slot (pending, streaming, done) so the user sees exactly what is happening.
Motivation
The AI SDK already lets the model issue parallel tool calls — we receive them as a batch in pendingToolCalls. Today agent-stream.ts iterates that batch with await tool.execute(...) one call at a time, so throughput is bottlenecked on the slowest call. A turn with three reads serializes three round-trips for no reason. Claude Code and OpenCode both fan these out. Parallel execution also pairs with issue #233 (single-input tools) — when we drop array batching, the model's preferred way to do multi-item work is emitting N parallel calls.
The TUI is already mostly ready: tool output events carry toolCallId, chat state has toolRowIdByCallId: Map<string, string>, and each tool has its own contentByCallId buffer. The renderer does in-place updates per tool row.
Proposed approach
Replace the sequential for (const tc of pendingToolCalls) loop with Promise.allSettled to fan out tool execution concurrently. Preserve result order in toolResultParts via positional indexing so the message order the model sees stays stable. Add a per-row status (pending | streaming | done) to the tool row state and render an indicator next to each tool so the user can see what is in flight.
Scope
agent-stream.ts tool-execution loop
- Per-row status field in tool output state (
tool-output-render.ts, chat-message-handler-stream.ts)
- Indicator glyph/render in the chat transcript
- Tests for concurrent success, mixed success/failure, ordering stability, and cancellation behaviour
- Verify no races on shared
streamController.enqueue, consecutive-failure counter, and discovery-budget count under concurrency
What would you like?
When the model emits multiple tool calls in a single turn, execute them concurrently instead of awaiting each one sequentially. While tools are in flight, render an indicator per tool slot (pending, streaming, done) so the user sees exactly what is happening.
Motivation
The AI SDK already lets the model issue parallel tool calls — we receive them as a batch in
pendingToolCalls. Todayagent-stream.tsiterates that batch withawait tool.execute(...)one call at a time, so throughput is bottlenecked on the slowest call. A turn with three reads serializes three round-trips for no reason. Claude Code and OpenCode both fan these out. Parallel execution also pairs with issue #233 (single-input tools) — when we drop array batching, the model's preferred way to do multi-item work is emitting N parallel calls.The TUI is already mostly ready: tool output events carry
toolCallId, chat state hastoolRowIdByCallId: Map<string, string>, and each tool has its owncontentByCallIdbuffer. The renderer does in-place updates per tool row.Proposed approach
Replace the sequential
for (const tc of pendingToolCalls)loop withPromise.allSettledto fan out tool execution concurrently. Preserve result order intoolResultPartsvia positional indexing so the message order the model sees stays stable. Add a per-row status (pending | streaming | done) to the tool row state and render an indicator next to each tool so the user can see what is in flight.Scope
agent-stream.tstool-execution looptool-output-render.ts,chat-message-handler-stream.ts)streamController.enqueue, consecutive-failure counter, and discovery-budget count under concurrency