Summary
Since PR #719 (#628), an Agent is one run of a PreparedAgent: begin_run creates the run's RunContext, with its id and cancel token, before anything streams. StreamingAgent::stream on that Agent then misbehaves in two ways.
- It ignores the id it is given. The run keeps the id
begin_run gave it, and a different request_id passed to stream is only logged at debug. build_streaming_agent begins the run with config.request_id, which defaults to "". Built without one and streamed under "req_123", as the streaming.rs usage example does, the hook key, MCP in-flight tracking and approvals are all keyed by "". cancel_and_close_mcp("req_123") matches nothing, and two agents built that way share "" and can release each other's approvals.
- A second
stream reuses the run. The first AgentRun's drop guard cancels the run's token, so the second stream stops at its first hook. It also has no observer, since the event receiver went to the first stream, and it inherits whatever ids are left in the run's tool-call queue. Two concurrent calls on one Arc<dyn StreamingAgent> mix up tool_call_ids.
Before PR #719, each stream call made its own RunContext under the id it was given. No server path hits either case today: chat completions (and the standalone CLI through it), A2A and Slack all build per request with the id they stream under, and stream once. This is library API behavior.
Reported in the review of PR #719: #719 (comment) (items 1 and 2).
Reproduction
- Build with
build_streaming_agent(&config, None), with config.request_id unset.
- Call
agent.stream(query, vec![], RunOptions::default(), "req_123").await.
- The run's id is
"": an approval it raises carries request_id: "", and agent.cancel_and_close_mcp("req_123", …) cancels nothing.
- Drop that
AgentRun and call stream again on the same agent: the new stream ends at once, cancelled, and its events reach no observer.
Additional Context
Proposed fix, in crates/aura/src/builder.rs (impl StreamingAgent for Agent):
- A
request_id other than the run's returns a run whose stream yields one StreamError naming both ids, and runs nothing. The streaming.rs module example builds with the id it streams under.
- A second
StreamingAgent::stream on the same Agent fails the same way, tracked by a flag on Agent.
- Only the trait method. The orchestrator re-streams one coordinator
Agent through the inherent stream_chat_with_depth on transient retries (planning_stream_with_transient_retry). Park-mode workers use stream_chat_with_timeout with RunOptions::default(), which makes its own token. Neither is refused.
- Tests in
builder.rs's prepared_runs:
- a mismatched id yields the error and runs nothing;
- a second
stream yields the error and leaves the first untouched;
- the coordinator retry path still streams twice.
This is a stopgap. Dropping request_id from stream belongs to #780, whose runtime mints the run's id once and hands it to begin_run. #780 also makes the second-stream case moot for server paths, since the runtime streams each run once.
Searched Issues
Code of Conduct
Summary
Since PR #719 (#628), an
Agentis one run of aPreparedAgent:begin_runcreates the run'sRunContext, with its id and cancel token, before anything streams.StreamingAgent::streamon thatAgentthen misbehaves in two ways.begin_rungave it, and a differentrequest_idpassed tostreamis only logged at debug.build_streaming_agentbegins the run withconfig.request_id, which defaults to"". Built without one and streamed under"req_123", as thestreaming.rsusage example does, the hook key, MCP in-flight tracking and approvals are all keyed by"".cancel_and_close_mcp("req_123")matches nothing, and two agents built that way share""and can release each other's approvals.streamreuses the run. The firstAgentRun's drop guard cancels the run's token, so the second stream stops at its first hook. It also has no observer, since the event receiver went to the first stream, and it inherits whatever ids are left in the run's tool-call queue. Two concurrent calls on oneArc<dyn StreamingAgent>mix uptool_call_ids.Before PR #719, each
streamcall made its ownRunContextunder the id it was given. No server path hits either case today: chat completions (and the standalone CLI through it), A2A and Slack all build per request with the id they stream under, and stream once. This is library API behavior.Reported in the review of PR #719: #719 (comment) (items 1 and 2).
Reproduction
build_streaming_agent(&config, None), withconfig.request_idunset.agent.stream(query, vec![], RunOptions::default(), "req_123").await."": an approval it raises carriesrequest_id: "", andagent.cancel_and_close_mcp("req_123", …)cancels nothing.AgentRunand callstreamagain on the same agent: the new stream ends at once, cancelled, and its events reach no observer.Additional Context
Proposed fix, in
crates/aura/src/builder.rs(impl StreamingAgent for Agent):request_idother than the run's returns a run whose stream yields oneStreamErrornaming both ids, and runs nothing. Thestreaming.rsmodule example builds with the id it streams under.StreamingAgent::streamon the sameAgentfails the same way, tracked by a flag onAgent.Agentthrough the inherentstream_chat_with_depthon transient retries (planning_stream_with_transient_retry). Park-mode workers usestream_chat_with_timeoutwithRunOptions::default(), which makes its own token. Neither is refused.builder.rs'sprepared_runs:streamyields the error and leaves the first untouched;This is a stopgap. Dropping
request_idfromstreambelongs to #780, whose runtime mints the run's id once and hands it tobegin_run. #780 also makes the second-stream case moot for server paths, since the runtime streams each run once.Searched Issues
Code of Conduct