feat(agent): emit a live ToolExecutionStarted stream item when a tool call dispatches - #2247
Open
nazq wants to merge 1 commit into
Open
feat(agent): emit a live ToolExecutionStarted stream item when a tool call dispatches#2247nazq wants to merge 1 commit into
ToolExecutionStarted stream item when a tool call dispatches#2247nazq wants to merge 1 commit into
Conversation
…all dispatches
The multi-turn stream had no live tool-execution signal:
ToolExecutionCommitted and ToolResult surface only after the whole tool
batch settles, so a streaming consumer showing in-flight tool activity
had to ride the on_tool_call hook and re-merge it into its own stream.
Emit MultiTurnStreamItem::ToolExecutionStarted { tool_name,
internal_call_id } at each call's actual start moment on both the
sequential and concurrent tool paths, correlated with the ids consumers
already receive on the model ToolCall, execution-commit, and result
items. MultiTurnStreamItem is non_exhaustive, so the variant is additive
and non-breaking; every existing item keeps its position and the
atomic-after-settle batch commit is unchanged.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Fixes #2246.
Semver
MultiTurnStreamItemis#[non_exhaustive], so adding the variant is a minor, non-breakingchange: downstream
matches already carry a wildcard arm, and existing consumers see the newitem fall into it. Every pre-existing stream item keeps its exact position and payload — the new
item is purely additive to the sequence. Serialized form follows the enum's existing tagged
convention (
"type": "toolExecutionStarted").Motivation
The multi-turn stream currently has no live tool-execution signal.
ToolExecutionCommitted+ToolResultare (deliberately) surfaced only after the whole toolbatch settles, so a streaming consumer that wants to render "running tool X…" — a chat UI, an
AG-UI/SSE bridge mapping onto a
TOOL_CALL_*lifecycle, or any progress surface — has toregister an
on_tool_callhook and re-merge that side channel into its own stream, re-derivingcorrelation that the stream already carries. Hooks remain the right place for steering; this
PR gives pure observers a first-class stream item instead.
What this adds
A new
MultiTurnStreamItem::ToolExecutionStarted { tool_name, internal_call_id }, emitted ateach tool call's actual start moment — immediately before its
ToolCallhook chain runs —one per dispatched call, not per batch. It carries the same rig-generated
internal_call_idconsumers already receive on the model
ToolCallitem,ToolExecutionCommitted, and theToolResult, so the full start ⟷ commit ⟷ result lifecycle correlates with the ids theyalready track.
Ordering contract (documented on the variant)
Per tool call, keyed by
internal_call_id:StreamedAssistantContent::ToolCallitems — up front for the wholebatch, in call order (unchanged);
ToolExecutionStarted— live, as each call actually starts: call order on the sequentialpath, start order under
tool_concurrency > 1;ToolExecutionCommitted+ToolResult, incall order (unchanged, still atomic all-or-nothing).
The item reports that dispatch began, not that the body ran:
ToolCallhook returnsSkipstill surfaces a start (its hook chain ran, whichmatters for slow approval-style hooks) but no
ToolExecutionCommitted;unchanged).
Implementation
Both surfaces share
drive_tool_calls, so the change lives there and behaves identically forlocal and MCP-backed tools (both dispatch through the same registry snapshot / hook pipeline);
the blocking surface is untouched (
forward_items: falsestill builds no items).run_single_toolis awaited, so aconsumer observes it before the tool body is even constructed.
buffer_unordered): each task reports its start through an unboundedside channel the moment it dispatches (after the fail-fast
terminatingcheck, so droppedsiblings emit nothing), and the drain loop is a
stream::selectmerge of that channel withthe settled-outcome stream — starts surface live while tools are still running, and the
channel closes exactly when the last task settles (each task owns one sender clone), so the
merged drain terminates as before. Outcome collection, fail-fast semantics, and the
atomic-after-settle commit are untouched.
Tests
All in
rig-agent's existing streaming/loop test style (mock model turns, no network):stream_emits_tool_execution_started_live_while_tool_runs— liveness: the tool bodycompletes only when the consumer reacts to the start item on the stream; a driver that
surfaced the start after execution would deadlock (timeout-guarded). Also pins correlation:
the start's
internal_call_idequals the modelToolCallitem's, the commit's, and theresult's.
stream_emits_tool_execution_started_live_under_concurrency— same liveness proof on thebuffer_unorderedpath with every call gated on its own start being consumed; asserts everystart precedes the batch's commit/result items and start ids pair 1:1 with commit/result ids.
stream_emits_model_tool_calls_then_atomic_execution_items— extended taxonomy/orderingfixture now asserts the full marker sequence (model-calls → starts → commit/result pairs) at
concurrency 1 and 4.
stream_hook_skip_surfaces_result_without_execution_commit— extended: a hook-skipped callsurfaces a start and a result but no commit.
execution_commit_items_are_not_emitted_when_run_commit_fails— extended: a failed batchcommit surfaces no commit/result items, while the already-emitted live start documents the
starts-without-results terminal case.
Cassettes: not applicable (no provider behavior change).
cargo fmt --check,cargo clippy -p rig-agent --all-features --all-targets, andcargo test -p rig-agent --all-features(489 passed) are green on the workspace toolchain.