From 0b7af758f9beae3b0e2f84ff2efa87e849ce7f78 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 13:25:42 +0000
Subject: [PATCH 1/9] Initial plan
From e81c62dfba51a488039da926b50a3bf5ba9ec0d2 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 13:39:28 +0000
Subject: [PATCH 2/9] Add C# GroupChat tool approval sample implementation
Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
---
.../DeploymentGroupChatManager.cs | 47 ++++
.../GroupChatToolApproval.csproj | 22 ++
.../Agents/GroupChatToolApproval/Program.cs | 209 ++++++++++++++++++
.../Agents/GroupChatToolApproval/README.md | 70 ++++++
.../GettingStarted/Workflows/README.md | 1 +
5 files changed, 349 insertions(+)
create mode 100644 dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/DeploymentGroupChatManager.cs
create mode 100644 dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/GroupChatToolApproval.csproj
create mode 100644 dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs
create mode 100644 dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/README.md
diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/DeploymentGroupChatManager.cs b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/DeploymentGroupChatManager.cs
new file mode 100644
index 0000000000..19938ae64e
--- /dev/null
+++ b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/DeploymentGroupChatManager.cs
@@ -0,0 +1,47 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.Workflows;
+using Microsoft.Extensions.AI;
+
+namespace WorkflowGroupChatToolApprovalSample;
+
+///
+/// Custom GroupChatManager that selects the next speaker based on the conversation flow.
+///
+///
+/// This simple selector follows a predefined flow:
+/// 1. QA Engineer runs tests
+/// 2. DevOps Engineer checks staging and creates rollback plan
+/// 3. DevOps Engineer deploys to production (triggers approval)
+///
+internal sealed class DeploymentGroupChatManager : GroupChatManager
+{
+ private readonly IReadOnlyList _agents;
+
+ public DeploymentGroupChatManager(IReadOnlyList agents)
+ {
+ this._agents = agents;
+ }
+
+ protected override ValueTask SelectNextAgentAsync(
+ IReadOnlyList history,
+ CancellationToken cancellationToken = default)
+ {
+ if (history.Count == 0)
+ {
+ throw new InvalidOperationException("Conversation is empty; cannot select next speaker.");
+ }
+
+ // First speaker after initial user message
+ if (this.IterationCount == 0)
+ {
+ AIAgent qaAgent = this._agents.First(a => a.Id == "QAEngineer");
+ return new ValueTask(qaAgent);
+ }
+
+ // Subsequent speakers are DevOps Engineer
+ AIAgent devopsAgent = this._agents.First(a => a.Id == "DevOpsEngineer");
+ return new ValueTask(devopsAgent);
+ }
+}
diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/GroupChatToolApproval.csproj b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/GroupChatToolApproval.csproj
new file mode 100644
index 0000000000..d0c0656ade
--- /dev/null
+++ b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/GroupChatToolApproval.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net10.0
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs
new file mode 100644
index 0000000000..479a2ec308
--- /dev/null
+++ b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs
@@ -0,0 +1,209 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample demonstrates how to use GroupChatBuilder with tools that require human
+// approval before execution. A group of specialized agents collaborate on a task, and
+// sensitive tool calls trigger human-in-the-loop approval.
+//
+// This sample works as follows:
+// 1. A GroupChatBuilder workflow is created with multiple specialized agents.
+// 2. A custom manager determines which agent speaks next based on conversation state.
+// 3. Agents collaborate on a software deployment task.
+// 4. When the deployment agent tries to deploy to production, it triggers an approval request.
+// 5. The sample simulates human approval and the workflow completes.
+//
+// Purpose:
+// Show how tool call approvals integrate with multi-agent group chat workflows where
+// different agents have different levels of tool access.
+//
+// Demonstrate:
+// - Using custom GroupChatManager with agents that have approval-required tools.
+// - Handling FunctionApprovalRequestContent in group chat scenarios.
+// - Multi-round group chat with tool approval interruption and resumption.
+
+using System.ComponentModel;
+using System.Text.Json;
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.Workflows;
+using Microsoft.Extensions.AI;
+
+namespace WorkflowGroupChatToolApprovalSample;
+
+///
+/// This sample demonstrates how to use GroupChatBuilder with tools that require human
+/// approval before execution.
+///
+///
+/// Pre-requisites:
+/// - An Azure OpenAI chat completion deployment must be configured.
+///
+public static class Program
+{
+ private static async Task Main()
+ {
+ var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
+ var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+
+ // 2. Create specialized agents
+ IChatClient client = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
+ .GetChatClient(deploymentName)
+ .AsIChatClient();
+
+ ChatClientAgent qaEngineer = new(
+ client,
+ "You are a QA engineer responsible for running tests before deployment. Run the appropriate test suites and report results clearly.",
+ "QAEngineer",
+ "QA engineer who runs tests",
+ [AIFunctionFactory.Create(RunTests)]);
+
+ ChatClientAgent devopsEngineer = new(
+ client,
+ "You are a DevOps engineer responsible for deployments. First check staging status and create a rollback plan, then proceed with production deployment. Always ensure safety measures are in place before deploying.",
+ "DevOpsEngineer",
+ "DevOps engineer who handles deployments",
+ [
+ AIFunctionFactory.Create(CheckStagingStatus),
+ AIFunctionFactory.Create(CreateRollbackPlan),
+ new ApprovalRequiredAIFunction(AIFunctionFactory.Create(DeployToProduction))
+ ]);
+
+ // 3. Create custom GroupChatManager with speaker selection logic
+ DeploymentGroupChatManager manager = new([qaEngineer, devopsEngineer])
+ {
+ MaximumIterationCount = 4 // Limit to 4 rounds
+ };
+
+ // 4. Build a group chat workflow with the custom manager
+ Workflow workflow = AgentWorkflowBuilder
+ .CreateGroupChatBuilderWith(_ => manager)
+ .AddParticipants(qaEngineer, devopsEngineer)
+ .Build();
+
+ // 5. Start the workflow
+ Console.WriteLine("Starting group chat workflow for software deployment...");
+ Console.WriteLine($"Agents: [{qaEngineer.Id}, {devopsEngineer.Id}]");
+ Console.WriteLine(new string('-', 60));
+
+ List messages = [new(ChatRole.User, "We need to deploy version 2.4.0 to production. Please coordinate the deployment.")];
+
+ // Phase 1: Run workflow and collect all events (stream ends at IDLE or IDLE_WITH_PENDING_REQUESTS)
+ List approvalRequests = [];
+ await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, messages);
+ await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
+
+ string? lastExecutorId = null;
+ await foreach (WorkflowEvent evt in run.WatchStreamAsync())
+ {
+ switch (evt)
+ {
+ case AgentResponseUpdateEvent e:
+ if (e.ExecutorId != lastExecutorId)
+ {
+ if (lastExecutorId is not null)
+ {
+ Console.WriteLine();
+ }
+ Console.Write($"- {e.ExecutorId}: ");
+ lastExecutorId = e.ExecutorId;
+ }
+
+ Console.Write(e.Update.Text);
+
+ // Check for user input requests (approval requests)
+ FunctionApprovalRequestContent? approvalRequest = e.Update.Contents
+ .OfType()
+ .FirstOrDefault();
+
+ if (approvalRequest is not null)
+ {
+ approvalRequests.Add(approvalRequest);
+ Console.WriteLine();
+ Console.WriteLine($"[APPROVAL REQUIRED] From agent: {e.ExecutorId}");
+ Console.WriteLine($" Tool: {approvalRequest.FunctionCall.Name}");
+ Console.WriteLine($" Arguments: {JsonSerializer.Serialize(approvalRequest.FunctionCall.Arguments)}");
+ }
+
+ break;
+
+ case WorkflowOutputEvent:
+ // Workflow has completed, check if we have pending approval requests
+ if (approvalRequests.Count == 0)
+ {
+ Console.WriteLine();
+ Console.WriteLine("Workflow completed without requiring production deployment approval.");
+ }
+ else
+ {
+ // We have approval requests to handle
+ Console.WriteLine();
+ Console.WriteLine(new string('=', 60));
+ Console.WriteLine("Human review required for production deployment!");
+ Console.WriteLine("In a real scenario, you would review the deployment details here.");
+ Console.WriteLine("Simulating approval for demo purposes...");
+ Console.WriteLine(new string('=', 60));
+
+ // Phase 2: Send approval and continue workflow
+ List approvalMessages = [];
+ foreach (FunctionApprovalRequestContent req in approvalRequests)
+ {
+ approvalMessages.Add(new ChatMessage(ChatRole.User, [req.CreateResponse(approved: true)]));
+ }
+
+ messages.AddRange(approvalMessages);
+
+ // Resume workflow with approval responses
+ await using StreamingRun resumeRun = await InProcessExecution.StreamAsync(workflow, messages);
+ await resumeRun.TrySendMessageAsync(new TurnToken(emitEvents: true));
+
+ lastExecutorId = null;
+ await foreach (WorkflowEvent resumeEvt in resumeRun.WatchStreamAsync())
+ {
+ if (resumeEvt is AgentResponseUpdateEvent e2)
+ {
+ if (e2.ExecutorId != lastExecutorId)
+ {
+ if (lastExecutorId is not null)
+ {
+ Console.WriteLine();
+ }
+ Console.Write($"- {e2.ExecutorId}: ");
+ lastExecutorId = e2.ExecutorId;
+ }
+
+ Console.Write(e2.Update.Text);
+ }
+ else if (resumeEvt is WorkflowOutputEvent)
+ {
+ Console.WriteLine();
+ Console.WriteLine(new string('-', 60));
+ Console.WriteLine("Deployment workflow completed successfully!");
+ Console.WriteLine("All agents have finished their tasks.");
+ break;
+ }
+ }
+ }
+ return;
+ }
+ }
+ }
+
+ // 1. Define tools for different agents
+ [Description("Run automated tests for the application.")]
+ private static string RunTests([Description("Name of the test suite to run")] string testSuite)
+ => $"Test suite '{testSuite}' completed: 47 passed, 0 failed, 0 skipped";
+
+ [Description("Check the current status of the staging environment.")]
+ private static string CheckStagingStatus()
+ => "Staging environment: Healthy, Version 2.3.0 deployed, All services running";
+
+ [Description("Deploy specified components to production. Requires human approval.")]
+ private static string DeployToProduction(
+ [Description("The version to deploy")] string version,
+ [Description("Comma-separated list of components to deploy")] string components)
+ => $"Production deployment complete: Version {version}, Components: {components}";
+
+ [Description("Create a rollback plan for the deployment.")]
+ private static string CreateRollbackPlan([Description("The version being deployed")] string version)
+ => $"Rollback plan created for version {version}: Automated rollback to v2.2.0 if health checks fail within 5 minutes";
+}
diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/README.md b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/README.md
new file mode 100644
index 0000000000..9c3d401b90
--- /dev/null
+++ b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/README.md
@@ -0,0 +1,70 @@
+# Group Chat with Tool Approval Sample
+
+This sample demonstrates how to use `GroupChatBuilder` with tools that require human approval before execution. A group of specialized agents collaborate on a task, and sensitive tool calls trigger human-in-the-loop approval.
+
+## What This Sample Demonstrates
+
+- Using a custom `GroupChatManager` with agents that have approval-required tools
+- Handling `FunctionApprovalRequestContent` in group chat scenarios
+- Multi-round group chat with tool approval interruption and resumption
+- Integrating tool call approvals with multi-agent workflows where different agents have different levels of tool access
+
+## How It Works
+
+1. A `GroupChatBuilder` workflow is created with multiple specialized agents
+2. A custom `DeploymentGroupChatManager` determines which agent speaks next based on conversation state
+3. Agents collaborate on a software deployment task:
+ - **QA Engineer**: Runs automated tests
+ - **DevOps Engineer**: Checks staging status, creates rollback plan, and deploys to production
+4. When the deployment agent tries to deploy to production, it triggers an approval request
+5. The sample simulates human approval and the workflow completes
+
+## Key Components
+
+### Approval-Required Tools
+
+The `DeployToProduction` function is wrapped with `ApprovalRequiredAIFunction` to require human approval:
+
+```csharp
+new ApprovalRequiredAIFunction(AIFunctionFactory.Create(DeployToProduction))
+```
+
+### Custom Group Chat Manager
+
+The `DeploymentGroupChatManager` implements custom speaker selection logic:
+- First iteration: QA Engineer runs tests
+- Subsequent iterations: DevOps Engineer handles deployment tasks
+
+### Approval Handling
+
+The sample demonstrates two-phase execution:
+1. **Phase 1**: Run workflow until approval is needed
+2. **Phase 2**: Handle approval requests and resume workflow
+
+## Prerequisites
+
+- Azure OpenAI or OpenAI configured with the required environment variables
+- `AZURE_OPENAI_ENDPOINT` environment variable set
+- `AZURE_OPENAI_DEPLOYMENT_NAME` environment variable (defaults to "gpt-4o-mini")
+
+## Running the Sample
+
+```bash
+dotnet run
+```
+
+## Expected Output
+
+The sample will show:
+1. QA Engineer running tests
+2. DevOps Engineer checking staging and creating rollback plan
+3. An approval request for production deployment
+4. Simulated approval response
+5. DevOps Engineer completing the deployment
+6. Workflow completion message
+
+## Related Samples
+
+- [Agent Function Tools with Approvals](../../../Agents/Agent_Step04_UsingFunctionToolsWithApprovals) - Basic function approval pattern
+- [Agent Workflow Patterns](../../_Foundational/04_AgentWorkflowPatterns) - Group chat without approvals
+- [Human-in-the-Loop Basic](../../HumanInTheLoop/HumanInTheLoopBasic) - Workflow-level human interaction
diff --git a/dotnet/samples/GettingStarted/Workflows/README.md b/dotnet/samples/GettingStarted/Workflows/README.md
index 072acfa560..f7704d0e42 100644
--- a/dotnet/samples/GettingStarted/Workflows/README.md
+++ b/dotnet/samples/GettingStarted/Workflows/README.md
@@ -32,6 +32,7 @@ Once completed, please proceed to other samples listed below.
| [Foundry Agents in Workflows](./Agents/FoundryAgent) | Demonstrates using Azure Foundry Agents within a workflow |
| [Custom Agent Executors](./Agents/CustomAgentExecutors) | Shows how to create a custom agent executor for more complex scenarios |
| [Workflow as an Agent](./Agents/WorkflowAsAnAgent) | Illustrates how to encapsulate a workflow as an agent |
+| [Group Chat with Tool Approval](./Agents/GroupChatToolApproval) | Shows multi-agent group chat with tool approval requests and human-in-the-loop interaction |
### Concurrent Execution
From a9c0d5ddb03502f16b82eff3fc174cae2f9ecb93 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 13:44:19 +0000
Subject: [PATCH 3/9] Fix comment numbering for better code clarity
Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
---
.../Workflows/Agents/GroupChatToolApproval/Program.cs | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs
index 479a2ec308..b6ecc16137 100644
--- a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs
+++ b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs
@@ -45,11 +45,12 @@ private static async Task Main()
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
- // 2. Create specialized agents
+ // 1. Create AI client
IChatClient client = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetChatClient(deploymentName)
.AsIChatClient();
+ // 2. Create specialized agents with their tools
ChatClientAgent qaEngineer = new(
client,
"You are a QA engineer responsible for running tests before deployment. Run the appropriate test suites and report results clearly.",
@@ -188,7 +189,7 @@ private static async Task Main()
}
}
- // 1. Define tools for different agents
+ // Tool definitions - These are called by the agents during workflow execution
[Description("Run automated tests for the application.")]
private static string RunTests([Description("Name of the test suite to run")] string testSuite)
=> $"Test suite '{testSuite}' completed: 47 passed, 0 failed, 0 skipped";
From 237e5a658b81d71a9b15d043070d47a167296e45 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 17:05:31 +0000
Subject: [PATCH 4/9] Add GroupChatToolApproval project to solution
Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
---
dotnet/agent-framework-dotnet.slnx | 1 +
1 file changed, 1 insertion(+)
diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index 4bc586befc..be1816dcdd 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -229,6 +229,7 @@
+
From 2535dde08b0e38ff84dbdb99a29d59cf6f964957 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 18:24:03 +0000
Subject: [PATCH 5/9] Use Name property instead of Id for agent selection
Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
---
.../GroupChatToolApproval/DeploymentGroupChatManager.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/DeploymentGroupChatManager.cs b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/DeploymentGroupChatManager.cs
index 19938ae64e..92c151a03a 100644
--- a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/DeploymentGroupChatManager.cs
+++ b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/DeploymentGroupChatManager.cs
@@ -36,12 +36,12 @@ protected override ValueTask SelectNextAgentAsync(
// First speaker after initial user message
if (this.IterationCount == 0)
{
- AIAgent qaAgent = this._agents.First(a => a.Id == "QAEngineer");
+ AIAgent qaAgent = this._agents.First(a => a.Name == "QAEngineer");
return new ValueTask(qaAgent);
}
// Subsequent speakers are DevOps Engineer
- AIAgent devopsAgent = this._agents.First(a => a.Id == "DevOpsEngineer");
+ AIAgent devopsAgent = this._agents.First(a => a.Name == "DevOpsEngineer");
return new ValueTask(devopsAgent);
}
}
From 7610a6de165b14aa418f3b195379810a86d09ac7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 18:44:42 +0000
Subject: [PATCH 6/9] Fix file encoding - add UTF-8 BOM to C# files
Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
---
.../Agents/GroupChatToolApproval/DeploymentGroupChatManager.cs | 2 +-
.../Workflows/Agents/GroupChatToolApproval/Program.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/DeploymentGroupChatManager.cs b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/DeploymentGroupChatManager.cs
index 92c151a03a..db50333697 100644
--- a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/DeploymentGroupChatManager.cs
+++ b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/DeploymentGroupChatManager.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft. All rights reserved.
+// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs
index b6ecc16137..041e5cc2a3 100644
--- a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs
+++ b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft. All rights reserved.
+// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates how to use GroupChatBuilder with tools that require human
// approval before execution. A group of specialized agents collaborate on a task, and
From 6a12f2861b6a8e4ab9dabf3a83d82777bd52d43a Mon Sep 17 00:00:00 2001
From: SergeyMenshykh
Date: Tue, 27 Jan 2026 11:57:20 +0000
Subject: [PATCH 7/9] fix the sample
---
.../Agents/GroupChatToolApproval/Program.cs | 89 +++++--------------
1 file changed, 24 insertions(+), 65 deletions(-)
diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs
index 041e5cc2a3..6511aa089d 100644
--- a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs
+++ b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs
@@ -83,14 +83,12 @@ private static async Task Main()
// 5. Start the workflow
Console.WriteLine("Starting group chat workflow for software deployment...");
- Console.WriteLine($"Agents: [{qaEngineer.Id}, {devopsEngineer.Id}]");
+ Console.WriteLine($"Agents: [{qaEngineer.Name}, {devopsEngineer.Name}]");
Console.WriteLine(new string('-', 60));
List messages = [new(ChatRole.User, "We need to deploy version 2.4.0 to production. Please coordinate the deployment.")];
- // Phase 1: Run workflow and collect all events (stream ends at IDLE or IDLE_WITH_PENDING_REQUESTS)
- List approvalRequests = [];
- await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, messages);
+ await using StreamingRun run = await InProcessExecution.Lockstep.StreamAsync(workflow, messages);
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
string? lastExecutorId = null;
@@ -98,14 +96,28 @@ private static async Task Main()
{
switch (evt)
{
+ case RequestInfoEvent e:
+ {
+ if (e.Request.DataAs() is { } approvalRequestContent)
+ {
+ // Approve the tool call request
+ Console.WriteLine($"Tool: {approvalRequestContent.FunctionCall.Name} approved");
+ await run.SendResponseAsync(e.Request.CreateResponse(approvalRequestContent.CreateResponse(approved: true)));
+ }
+
+ break;
+ }
+
case AgentResponseUpdateEvent e:
+ {
if (e.ExecutorId != lastExecutorId)
{
if (lastExecutorId is not null)
{
Console.WriteLine();
}
- Console.Write($"- {e.ExecutorId}: ");
+
+ Console.WriteLine($"- {e.ExecutorId}: ");
lastExecutorId = e.ExecutorId;
}
@@ -118,75 +130,22 @@ private static async Task Main()
if (approvalRequest is not null)
{
- approvalRequests.Add(approvalRequest);
Console.WriteLine();
Console.WriteLine($"[APPROVAL REQUIRED] From agent: {e.ExecutorId}");
Console.WriteLine($" Tool: {approvalRequest.FunctionCall.Name}");
Console.WriteLine($" Arguments: {JsonSerializer.Serialize(approvalRequest.FunctionCall.Arguments)}");
- }
-
- break;
-
- case WorkflowOutputEvent:
- // Workflow has completed, check if we have pending approval requests
- if (approvalRequests.Count == 0)
- {
Console.WriteLine();
- Console.WriteLine("Workflow completed without requiring production deployment approval.");
}
- else
- {
- // We have approval requests to handle
- Console.WriteLine();
- Console.WriteLine(new string('=', 60));
- Console.WriteLine("Human review required for production deployment!");
- Console.WriteLine("In a real scenario, you would review the deployment details here.");
- Console.WriteLine("Simulating approval for demo purposes...");
- Console.WriteLine(new string('=', 60));
-
- // Phase 2: Send approval and continue workflow
- List approvalMessages = [];
- foreach (FunctionApprovalRequestContent req in approvalRequests)
- {
- approvalMessages.Add(new ChatMessage(ChatRole.User, [req.CreateResponse(approved: true)]));
- }
- messages.AddRange(approvalMessages);
-
- // Resume workflow with approval responses
- await using StreamingRun resumeRun = await InProcessExecution.StreamAsync(workflow, messages);
- await resumeRun.TrySendMessageAsync(new TurnToken(emitEvents: true));
-
- lastExecutorId = null;
- await foreach (WorkflowEvent resumeEvt in resumeRun.WatchStreamAsync())
- {
- if (resumeEvt is AgentResponseUpdateEvent e2)
- {
- if (e2.ExecutorId != lastExecutorId)
- {
- if (lastExecutorId is not null)
- {
- Console.WriteLine();
- }
- Console.Write($"- {e2.ExecutorId}: ");
- lastExecutorId = e2.ExecutorId;
- }
-
- Console.Write(e2.Update.Text);
- }
- else if (resumeEvt is WorkflowOutputEvent)
- {
- Console.WriteLine();
- Console.WriteLine(new string('-', 60));
- Console.WriteLine("Deployment workflow completed successfully!");
- Console.WriteLine("All agents have finished their tasks.");
- break;
- }
- }
- }
- return;
+ break;
+ }
}
}
+
+ Console.WriteLine();
+ Console.WriteLine(new string('-', 60));
+ Console.WriteLine("Deployment workflow completed successfully!");
+ Console.WriteLine("All agents have finished their tasks.");
}
// Tool definitions - These are called by the agents during workflow execution
From 897769a7da57a376305911b7815bf7aa39e3867a Mon Sep 17 00:00:00 2001
From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Date: Tue, 27 Jan 2026 13:05:52 +0000
Subject: [PATCH 8/9] Update
dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/README.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.../Workflows/Agents/GroupChatToolApproval/README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/README.md b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/README.md
index 9c3d401b90..84c6baa83c 100644
--- a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/README.md
+++ b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/README.md
@@ -37,9 +37,9 @@ The `DeploymentGroupChatManager` implements custom speaker selection logic:
### Approval Handling
-The sample demonstrates two-phase execution:
-1. **Phase 1**: Run workflow until approval is needed
-2. **Phase 2**: Handle approval requests and resume workflow
+The sample demonstrates continuous event-driven execution with inline approval handling:
+- The workflow runs in a single event loop.
+- When an approval-required tool is invoked, the loop surfaces an approval request, processes the (simulated) human response, and then continues execution without starting a separate phase.
## Prerequisites
From 62b3891b6d56e21ed6955afa7815035a9e6ca49b Mon Sep 17 00:00:00 2001
From: SergeyMenshykh
Date: Tue, 27 Jan 2026 18:54:27 +0000
Subject: [PATCH 9/9] address PR review feedback
---
.../Agents/GroupChatToolApproval/Program.cs | 22 ++++++-------------
1 file changed, 7 insertions(+), 15 deletions(-)
diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs
index 6511aa089d..267c6d7ce5 100644
--- a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs
+++ b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs
@@ -98,8 +98,14 @@ private static async Task Main()
{
case RequestInfoEvent e:
{
- if (e.Request.DataAs() is { } approvalRequestContent)
+ if (e.Request.DataIs(out FunctionApprovalRequestContent? approvalRequestContent))
{
+ Console.WriteLine();
+ Console.WriteLine($"[APPROVAL REQUIRED] From agent: {e.Request.PortInfo.PortId}");
+ Console.WriteLine($" Tool: {approvalRequestContent.FunctionCall.Name}");
+ Console.WriteLine($" Arguments: {JsonSerializer.Serialize(approvalRequestContent.FunctionCall.Arguments)}");
+ Console.WriteLine();
+
// Approve the tool call request
Console.WriteLine($"Tool: {approvalRequestContent.FunctionCall.Name} approved");
await run.SendResponseAsync(e.Request.CreateResponse(approvalRequestContent.CreateResponse(approved: true)));
@@ -123,20 +129,6 @@ private static async Task Main()
Console.Write(e.Update.Text);
- // Check for user input requests (approval requests)
- FunctionApprovalRequestContent? approvalRequest = e.Update.Contents
- .OfType()
- .FirstOrDefault();
-
- if (approvalRequest is not null)
- {
- Console.WriteLine();
- Console.WriteLine($"[APPROVAL REQUIRED] From agent: {e.ExecutorId}");
- Console.WriteLine($" Tool: {approvalRequest.FunctionCall.Name}");
- Console.WriteLine($" Arguments: {JsonSerializer.Serialize(approvalRequest.FunctionCall.Arguments)}");
- Console.WriteLine();
- }
-
break;
}
}