microsoft / microsoft/agent-framework

.NET: [Bug]: AgentWorkflowBuilder Handoff orchestration breaks when handoffs bounce back, especially with reasoning

Open
#7,384 2 comments 0 reactions 2 assignees View on GitHub

@peibekwe is already working on this.

Since Aug 25, 2026.

.NET agents harness requested-info
Dominant language
Python
Stars
13.6k
Forks
2.3k
Avg merge
2d 45m
Merged PRs (30d)
358

Description

### Description

`HarnessAgent.BuildInnerAgent` unconditionally builds its inner `ChatClientAgent`
with `ChatHistoryProvider` and `RequirePerServiceCallChatHistoryPersistence = true`
— no `HarnessAgentOptions` flag disables either.

This conflicts with Handoff orchestration
(`Microsoft.Agents.AI.Workflows.Specialized.HandoffAgentExecutor`), which owns
the full multi-party conversation itself and re-invokes each participant as a
**stateless** function, resending the relevant message slice every turn. The
official Handoff sample (`dotnet/samples/03-workflows/Orchestration/Handoff`)
builds participants with `chatClient.AsAIAgent(...)` — stateless, not
`HarnessAgent`.

Using a `HarnessAgent` as a Handoff participant instead breaks the handoff
tool call — a declaration-only `AIFunction`
(`AIFunctionFactory.CreateDeclaration`) intentionally left un-invoked so
`HandoffAgentExecutor` can intercept it. Observed symptoms in our app:

- A turn producing no response at all (silent hang).
- The handoff tool's arguments emitted as literal assistant **text** instead
of a `FunctionCallContent`, e.g. `{"reasonForHandoff":"..."}` printed as
chat output rather than executed as a tool call.

Likely cause: the executor reuses one `AgentSession` per participant across
turns, resending the full conversation slice itself each time; `HarnessAgent`'s
forced `ChatHistoryProvider` tracks its *own* copy of the conversation against
that same session. The two diverge, and the model ends up with a
confused/duplicated context instead of emitting a clean tool call.

**Caveat:** this is backed by the observed app-level symptoms plus a source
read of `HarnessAgent.cs`/`HandoffAgentExecutor.cs`, not an isolated minimal
console repro. No official sample combines `HarnessAgent` with
`AgentWorkflowBuilder`, and we found no existing issue covering this
combination.

### Code Sample

```markdown
// Works today — the official Handoff sample's pattern.
AIAgent workingParticipant = chatClient.AsAIAgent(instructions: "...", name: "triage");

// Breaks — HarnessAgent with everything non-essential disabled.
AIAgent brokenParticipant = chatClient.AsHarnessAgent(new HarnessAgentOptions
{
Name = "triage",
HarnessInstructions = string.Empty,
ChatOptions = new ChatOptions { Instructions = "..." },
DisableToolAutoApproval = true,
DisableWebSearch = true,
DisableFileMemory = true,
DisableTodoProvider = true,
DisableAgentModeProvider = true,
DisableAgentSkillsProvider = true,
DisableOpenTelemetry = true,
// No flag disables ChatHistoryProvider / RequirePerServiceCallChatHistoryPersistence.
});

#pragma warning disable MAAIW001
Workflow workflow = AgentWorkflowBuilder
.CreateHandoffBuilderWith(brokenParticipant) // swap in workingParticipant to compare
.WithHandoff(brokenParticipant, someExpertAgent, "reason for handoff")
.Build();
#pragma warning restore MAAIW001
```

### Error Messages / Stack Traces

```markdown
No exception — the failure is behavioral:

Turn 1 (expected a silent handoff): no response streamed at all.

Turn 2 (expected a silent handoff to a different expert):
Assistant: {"reasonForHandoff":"order status question, belongs to the Orders domain"}

The second line is the handoff tool's JSON arguments as plain text, not a
`FunctionCallContent` named `handoff_N`.
```

### Package Versions

Microsoft.Agents.AI: 1.15.0, Microsoft.Agents.AI.Harness: 1.15.0, Microsoft.Agents.AI.Workflows: 1.15.0, Microsoft.Agents.AI.Hosting.AGUI.AspNetCore: 1.15.0-preview.260722.1

### .NET Version

.NET 10.0

### Additional Context

- `dotnet/samples/02-agents/Harness/*` (`Harness_Step01_Research` …
`Harness_Step05_Loop`, `BuildYourOwnClaw`) are all single-agent console apps
— none combine `HarnessAgent` with `AgentWorkflowBuilder`.
- `HarnessAgent.BuildInnerAgent` (`dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgent.cs`):
```csharp
return chatClientBuilder.BuildAIAgent(new ChatClientAgentOptions
{
ChatHistoryProvider = chatHistoryProvider, // InMemoryChatHistoryProvider by default
RequirePerServiceCallChatHistoryPersistence = true, // hardcoded
...
});
```
- `HandoffAgentExecutor.InvokeAgentAsync` (`dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/HandoffAgentExecutor.cs`):
```csharp
this._session ??= await this._agent.CreateSessionAsync(cancellationToken).ConfigureAwait(false);
this._agent.RunStreamingAsync(messages, this._session, this._agentOptions, cancellationToken);
```
- Possibly related (same class of problem — persisted `ChatHistoryProvider`
state fighting an external conversation owner), none an exact match:
#5621, #6967, PR #5805.

### Suggested Fix

- Add a `HarnessAgentOptions` flag to opt out of `ChatHistoryProvider`/
`RequirePerServiceCallChatHistoryPersistence`, so `HarnessAgent` can be used
as a stateless Workflow participant, **or**
- document that `HarnessAgent` isn't meant to be used as a Workflow/Handoff
participant.

Happy to help narrow this down further if useful.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.