Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion agent-framework/workflows/human-in-the-loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ zone_pivot_groups: programming-languages
author: TaoChenOSU
ms.topic: tutorial
ms.author: taochen
ms.date: 05/27/2026
ms.date: 07/16/2026
ms.service: agent-framework
---

Expand Down Expand Up @@ -304,6 +304,8 @@ The `RequestPort` pattern described above works with custom executors and `Workf

Agents can use tools that require human approval before execution. When the agent attempts to call an approval-required tool, the workflow pauses and emits a `RequestInfoEvent` just like the `RequestPort` pattern, but the event payload contains a `ToolApprovalRequestContent` (C# and Go) or a `Content` with `type == "function_approval_request"` (Python) instead of a custom request type.

For interactive scenarios where an agent needs to gather more information from the user and iterate before proceeding; rather than only approving or rejecting a tool call; use the **[handoff orchestration](./orchestrations/handoff.md)**. Handoff is interactive by default: when an agent responds without handing off to another agent, control returns to the user for the next input, which enables multi-turn back-and-forth within the orchestration. Sequential, concurrent, and group chat orchestrations do not pause for free-form user input on their own; pair them with a `RequestPort` in a custom `WorkflowBuilder` workflow when you need that control between steps.

> [!TIP]
> For complete examples with code, see:
> - [Sequential orchestration with HITL](./orchestrations/sequential.md#sequential-orchestration-with-human-in-the-loop)
Expand Down
127 changes: 124 additions & 3 deletions agent-framework/workflows/orchestrations/handoff.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: In-depth look at Handoff Orchestrations in Microsoft Agent Framewor
author: TaoChenOSU
ms.topic: tutorial
ms.author: taochen
ms.date: 05/27/2026
ms.date: 07/16/2026
ms.service: agent-framework
zone_pivot_groups: programming-languages
---
Expand All @@ -18,8 +18,8 @@ zone_pivot_groups: programming-languages
| Define Specialized Agents | ✅ | ✅ | ❌ | |
| Configure Handoff Rules | ✅ | ✅ | ❌ | |
| Run Interactive Workflow | ✅ | ✅ | ❌ | Different patterns per language |
| Autonomous Mode | | ✅ | ❌ | Python-specific |
| Tool Approval (HITL) | | ✅ | ❌ | |
| Autonomous Mode | | ✅ | ❌ | |
| Tool Approval (HITL) | | ✅ | ❌ | |
| Checkpointing | ❌ | ✅ | ❌ | |
| Sample Interaction | ✅ | ✅ | ❌ | |
| Context Synchronization | ✅ | ✅ | ❌ | Shared section |
Expand Down Expand Up @@ -180,6 +180,125 @@ triage_agent: This is another math question. I'll route this to the math tutor.
math_tutor: I'd be happy to help with calculus integration! Integration is the reverse of differentiation. The basic power rule for integration is: ∫x^n dx = x^(n+1)/(n+1) + C, where C is the constant of integration.
```

## Autonomous Mode

By default, handoff orchestration is interactive: when an agent responds without handing off, the workflow returns control to you for the next user input. Enable **autonomous mode** to let an agent keep working without waiting for user input. When an agent does not hand off, the workflow feeds it a continuation prompt and invokes it again, until the agent hands off, a termination condition is met, or the per-agent turn limit is reached.

Enable it by calling `WithAutonomousMode()` on the handoff builder:

```csharp
var workflow = AgentWorkflowBuilder.CreateHandoffBuilderWith(triageAgent)
.WithHandoffs(triageAgent, [mathTutor, historyTutor])
.WithHandoffs([mathTutor, historyTutor], triageAgent)
.WithAutonomousMode()
.Build();
```

By default, each agent runs up to 50 autonomous turns, and each continuation uses the prompt `"User did not respond. Continue assisting autonomously."`. Override the turn limit and prompt as needed:

```csharp
var workflow = AgentWorkflowBuilder.CreateHandoffBuilderWith(triageAgent)
.WithHandoffs(triageAgent, [mathTutor, historyTutor])
.WithAutonomousMode(turnLimit: 10, continuationPrompt: "Continue assisting the user.")
.Build();
```

Pass a list of agents to the `agents` parameter to enable autonomous mode for only a subset of participants. Agents not in the list always return control after a single response:

```csharp
var workflow = AgentWorkflowBuilder.CreateHandoffBuilderWith(triageAgent)
.WithHandoffs(triageAgent, [mathTutor, historyTutor])
.WithAutonomousMode(agents: [triageAgent]) // Only triageAgent runs autonomously
.Build();
```

Combine autonomous mode with a termination condition to stop the loop when the conversation reaches a certain state:

```csharp
var workflow = AgentWorkflowBuilder.CreateHandoffBuilderWith(triageAgent)
.WithHandoffs(triageAgent, [mathTutor, historyTutor])
.WithAutonomousMode(turnLimit: 10)
.WithTerminationCondition(conversation => conversation.Any(m => m.Text?.Contains("RESOLVED") == true))
.Build();
```

## Advanced: Tool Approval in Handoff Workflows

Agents in a handoff workflow can use tools that require human approval before they run; useful for sensitive operations such as processing refunds, making purchases, or executing irreversible actions. Wrap the sensitive function with `ApprovalRequiredAIFunction`. When the agent tries to call it, the workflow pauses and emits a `RequestInfoEvent` containing a `ToolApprovalRequestContent`.

### Define Agents with Approval-Required Tools

```csharp
ChatClientAgent triageAgent = new(client,
"You are frontline support. Route the customer to the right specialist.",
"triage_agent",
"Routes customers to specialists");

ChatClientAgent refundAgent = new(client,
"You process refund requests.",
"refund_agent",
"Handles refund requests",
[new ApprovalRequiredAIFunction(AIFunctionFactory.Create(ProcessRefund))]);
```

### Handle User Input and Tool Approval Requests

Two things can pause a handoff workflow: an agent finishing its turn and waiting for the next user message, and an approval-required tool call. Handle both in the same event loop; respond to a `RequestInfoEvent` approval with `SendResponseAsync`, and supply the next user message when the workflow returns control:

```csharp
var workflow = AgentWorkflowBuilder.CreateHandoffBuilderWith(triageAgent)
.WithHandoffs(triageAgent, [refundAgent])
.WithHandoffs([refundAgent], triageAgent)
.Build();

List<ChatMessage> messages = [];

while (true)
{
Console.Write("You: ");
string userInput = Console.ReadLine()!;
if (userInput.Equals("exit", StringComparison.OrdinalIgnoreCase))
{
break;
}

messages.Add(new(ChatRole.User, userInput));

await using StreamingRun run = await InProcessExecution.RunStreamingAsync(workflow, messages);
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));

List<ChatMessage> newMessages = [];
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
// An approval-required tool call pauses the workflow and emits a RequestInfoEvent.
if (evt is RequestInfoEvent requestEvt &&
requestEvt.Request.TryGetDataAs(out ToolApprovalRequestContent? approval))
{
var toolCall = (FunctionCallContent)approval.ToolCall;
Console.Write($"Approve {toolCall.Name}? (y/n): ");
bool approved = (Console.ReadLine() ?? "n").Trim().Equals("y", StringComparison.OrdinalIgnoreCase);
await run.SendResponseAsync(requestEvt.Request.CreateResponse(approval.CreateResponse(approved)));
}
else if (evt is AgentResponseUpdateEvent update)
{
Console.Write(update.Update.Text);
}
else if (evt is WorkflowOutputEvent outputEvt)
{
newMessages = outputEvt.As<List<ChatMessage>>()!;
break;
}
}

// Control returns here after the agent responds without handing off. Merge the new
// messages into the conversation and loop to collect the next user input.
messages.AddRange(newMessages.Skip(messages.Count));
}
```

> [!NOTE]
> Tool approval works with `CreateHandoffBuilderWith()` out of the box; no extra builder configuration is needed. When an agent calls a tool wrapped with `ApprovalRequiredAIFunction`, the workflow automatically pauses and emits a `RequestInfoEvent`. The same `RequestInfoEvent` handling pattern is used across orchestrations; see the [`GroupChatToolApproval` sample](https://github.com/microsoft/agent-framework/tree/main/dotnet/samples/03-workflows/Agents/GroupChatToolApproval) for a complete runnable project.

::: zone-end

::: zone pivot="programming-language-python"
Expand Down Expand Up @@ -635,6 +754,8 @@ After broadcasting the response, the participant then checks whether it needs to
- **Context Preservation**: Full conversation history is maintained across all handoffs
- **Multi-turn Support**: Supports ongoing conversations with seamless agent switching
- **Specialized Expertise**: Each agent focuses on their domain while collaborating through handoffs
- **WithAutonomousMode()**: Lets agents continue without waiting for user input, up to a per-agent turn limit or until a termination condition is met
- **Tool Approval (HITL)**: Wrap sensitive tools with `ApprovalRequiredAIFunction`; the workflow pauses and emits a `RequestInfoEvent` with `ToolApprovalRequestContent`, which you answer via `SendResponseAsync`

::: zone-end

Expand Down
12 changes: 10 additions & 2 deletions agent-framework/workflows/orchestrations/sequential.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ zone_pivot_groups: programming-languages
author: TaoChenOSU
ms.topic: tutorial
ms.author: taochen
ms.date: 07/01/2026
ms.date: 07/16/2026
ms.service: agent-framework
---

Expand All @@ -19,7 +19,7 @@ ms.service: agent-framework
| Set Up the Sequential Orchestration | ✅ | ✅ | ✅ | |
| Run the Sequential Workflow | ✅ | ✅ | ✅ | |
| Sample Output | ✅ | ✅ | ✅ | |
| Sequential with Human-in-the-Loop | ✅ | ✅ | ✅ | |
| Sequential with Human-in-the-Loop | ✅ | ✅ | ✅ | Python adds a request-info feedback step; C# covers tool approval and links to Handoff for interactive HITL |
| Advanced: Mixing Agents with Custom Executors | ❌ | ✅ | ✅ | Python/Go-specific |
| Controlling Context Between Agents | ❌ | ✅ | ✅ | Python/Go-specific |
| Intermediate Outputs | ❌ | ✅ | ✅ | Python/Go-specific |
Expand Down Expand Up @@ -207,6 +207,13 @@ await foreach (WorkflowEvent evt in run.WatchStreamAsync())
> [!TIP]
> For a complete runnable example of this approval flow, see the [`GroupChatToolApproval` sample](https://github.com/microsoft/agent-framework/tree/main/dotnet/samples/03-workflows/Agents/GroupChatToolApproval). The same `RequestInfoEvent` handling pattern applies to other orchestrations.

### Beyond Tool Approval: Interactive Feedback

Tool approval lets a human accept or reject a specific tool call, but a sequential orchestration does not include a built-in step to pause for free-form user feedback between agents, and it cannot return control to a previous agent. When an agent needs to interactively ask the user for more information and iterate before continuing; for example, collecting booking details before it calls a reservation tool; use one of the following approaches instead:

- **[Handoff orchestration](./handoff.md)** is interactive by default: when an agent responds without handing off, control returns to the user for the next input, enabling multi-turn back-and-forth within the orchestration. Restrict each agent to a single handoff target to approximate a sequential flow that still pauses for user input.
- A **custom workflow** built with `WorkflowBuilder` and a [`RequestPort`](../human-in-the-loop.md) lets you send a typed request to the user at any point and route the response back to an executor, which you can place before or after your agents in the pipeline.

## Key Concepts

- **Sequential Processing**: Each agent processes the output of the previous agent in order
Expand All @@ -216,6 +223,7 @@ await foreach (WorkflowEvent evt in run.WatchStreamAsync())
- **Event Handling**: Monitor agent progress through `AgentResponseUpdateEvent` and completion through `WorkflowOutputEvent`
- **Tool Approval**: Wrap sensitive tools with `ApprovalRequiredAIFunction` to require human approval before execution
- **RequestInfoEvent**: Emitted when a tool requires approval; contains `ToolApprovalRequestContent` with the tool call details
- **Interactive HITL**: Sequential orchestration covers tool approval; for interactive back-and-forth where an agent gathers more information from the user, use [handoff orchestration](./handoff.md) or a custom `RequestPort` workflow

::: zone-end

Expand Down