microsoft / microsoft/agent-framework
.NET: [Bug]: a repeated CallId or RequestId in an agent stream fails the whole workflow run
@peibekwe is already working on this.
Since Aug 31, 2026.
- Dominant language
- Python
- Stars
- 13.6k
- Forks
- 2.3k
- Avg merge
- 2d 45m
- Merged PRs (30d)
- 358
Description
### Description
An agent host executor fails the whole workflow run when an agent's stream carries the same `CallId` or `RequestId` in more than one `AgentResponseUpdate`.
`AIAgentUnservicedRequestsCollector.ProcessAgentResponseUpdate` is called once per streamed update, and `ProcessAIContents` throws when the ID is already in its dictionary:
```csharp
if (this._userInputRequests.ContainsKey(userInputRequest.RequestId))
{
throw new InvalidOperationException($"ToolApprovalRequestContent with duplicate RequestId: {userInputRequest.RequestId}");
}
...
if (this._functionCalls.ContainsKey(functionCall.CallId))
{
throw new InvalidOperationException($"FunctionCallContent with duplicate CallId: {functionCall.CallId}");
}
```
The throw escapes `InvokeAgentAsync`, so the run ends in `ExecutorFailedEvent` + `WorkflowErrorEvent` rather than reaching the agent's request. Both `AIAgentHostExecutor` and `HandoffAgentExecutor` construct this collector, so both are affected, and the same executor is what `workflow.AsAIAgent()` runs on.
Repeating an ID across updates is a re-emission of one pending request, not two simultaneous requests. `AIContentExternalHandler.ProcessRequestContentAsync`, the layer this collector feeds, already says so and handles it without failing:
```csharp
if (!this._pendingRequests.TryAdd(id, requestContent))
{
// Request is already pending; treat as an idempotent re-emission.
// Do not repost to the sink because request IDs must remain unique while pending.
return default;
}
```
So one layer treats the repeat as idempotent while the layer above it kills the run.
### Expected behavior
A repeated `CallId` or `RequestId` inside a single agent run is coalesced into the one pending request it belongs to, and the run continues to raise that request.
### Code Sample
A custom `AIAgent` is a supported extension point, and this is the smallest thing that reproduces it. Streaming that repeats a call while argument chunks accumulate has the same shape.
```csharp
internal sealed class RepeatingRequestAgent : AIAgent
{
// Session/serialization members omitted.
protected override async IAsyncEnumerable RunCoreStreamingAsync(
IEnumerable messages,
AgentSession? session = null,
AgentRunOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
yield return new AgentResponseUpdate(ChatRole.Assistant,
[new FunctionCallContent("call-1", "doThing", new Dictionary { ["a"] = 1 })]);
yield return new AgentResponseUpdate(ChatRole.Assistant,
[new FunctionCallContent("call-1", "doThing", new Dictionary { ["a"] = 1, ["b"] = 2 })]);
await Task.CompletedTask.ConfigureAwait(false);
}
}
ExecutorBinding binding = new RepeatingRequestAgent().BindAsExecutor(
new AIAgentHostOptions { InterceptUnterminatedFunctionCalls = false, EmitAgentUpdateEvents = true });
Workflow workflow = new WorkflowBuilder(binding).Build();
List updates = await workflow.AsAIAgent("WorkflowAgent", includeExceptionDetails: true)
.RunStreamingAsync(new ChatMessage(ChatRole.User, "Hi"))
.ToListAsync();
```
Observed update stream:
```text
WorkflowStartedEvent | SuperStepStartedEvent | ExecutorInvokedEvent | ExecutorCompletedEvent
| ExecutorInvokedEvent | [FunctionCallContent:call-1] | [FunctionCallContent:call-1]
| ExecutorFailedEvent [ErrorContent: FunctionCallContent with duplicate CallId: call-1]
| WorkflowErrorEvent [ErrorContent: FunctionCallContent with duplicate CallId: call-1]
```
Two updates carrying `new ToolApprovalRequestContent("req-1", mcpCall)` with `InterceptUserInputRequests = false` fail the same way:
```text
ExecutorFailedEvent [ErrorContent: ToolApprovalRequestContent with duplicate RequestId: req-1]
| WorkflowErrorEvent [ErrorContent: ToolApprovalRequestContent with duplicate RequestId: req-1]
```
### Error Messages / Stack Traces
```shell
System.InvalidOperationException: FunctionCallContent with duplicate CallId: call-1
at Microsoft.Agents.AI.Workflows.Specialized.AIAgentUnservicedRequestsCollector.ProcessAIContents(...)
at Microsoft.Agents.AI.Workflows.Specialized.AIAgentHostExecutor.InvokeAgentAsync(...)
System.InvalidOperationException: ToolApprovalRequestContent with duplicate RequestId: req-1
at Microsoft.Agents.AI.Workflows.Specialized.AIAgentUnservicedRequestsCollector.ProcessAIContents(...)
```
### Package Versions
`Microsoft.Agents.AI.Workflows` from `main` at `edfe115e`
### .NET Version
net10.0
### Additional Context
I would like to take this one. The fix I have in mind keeps the collector's dictionaries as the single record of a pending request and treats a repeat as an update of that record rather than an error, matching what `AIContentExternalHandler` already does, with regression tests on both the function-call and approval paths for the plain agent binding and for handoff.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.