From e8cbd1cfec29cabfa3a8beb2bf8f4738532302c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 23:23:29 +0000 Subject: [PATCH] Add Progress_Made_Decrements_StallCount E2E test verifying stall count decrement avoids reset Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/9b34e409-61b8-4650-ae55-34efad034ed0 Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com> --- .../MagenticOrchestrationTests.cs | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs index 763d23d20f..c3f38d6a89 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs @@ -933,6 +933,80 @@ public class MagenticOrchestrationTests runResult.Result![0].Text.Should().Contain("Delegated correctly!"); } + [Fact] + public async Task Progress_Made_Decrements_StallCount() + { + // Arrange: MaxStallCount=3, so a single stall won't trigger reset. + // Round 1: isInLoop=true (stall count → 1), delegates to Worker + // Round 2: progress being made (stall count → 0), delegates to Worker + // Round 3: satisfied → final answer + // No reset should occur because the stall count was decremented before reaching threshold. + List facts1 = CreatePlanResponse("Initial facts"); + List plan1 = CreatePlanResponse("Initial plan"); + List ledger1 = CreateProgressLedgerResponse( + isRequestSatisfied: false, + isInLoop: true, // triggers stall increment → StallCount=1 + isProgressBeingMade: true, + nextSpeaker: "Worker", + instructionOrQuestion: "Keep trying"); + + // After Worker responds → re-enter TakeTurnAsync + List facts2 = CreatePlanResponse("Updated facts round 2"); + List plan2 = CreatePlanResponse("Updated plan round 2"); + List ledger2 = CreateProgressLedgerResponse( + isRequestSatisfied: false, + isInLoop: false, + isProgressBeingMade: true, // progress → stall count decrements → StallCount=0 + nextSpeaker: "Worker", + instructionOrQuestion: "Good progress"); + + // After Worker responds → re-enter TakeTurnAsync + List facts3 = CreatePlanResponse("Updated facts round 3"); + List plan3 = CreatePlanResponse("Updated plan round 3"); + List ledger3 = CreateProgressLedgerResponse( + isRequestSatisfied: true, + isInLoop: false, + isProgressBeingMade: true, + nextSpeaker: "Worker", + instructionOrQuestion: "All done"); + List finalAnswerResponse = CreateFinalAnswerResponse("Completed without reset!"); + + TestReplayAgent manager = new( + [facts1, plan1, ledger1, + facts2, plan2, ledger2, + facts3, plan3, ledger3, finalAnswerResponse], + name: "Manager"); + TestEchoAgent worker = new(name: "Worker"); + + List collectedEvents = []; + + Workflow workflow = new MagenticWorkflowBuilder(manager) + .AddParticipants(worker) + .RequirePlanSignoff(false) + .WithMaxStalls(3) // high threshold so single stall doesn't trigger reset + .Build(); + + // Act + WorkflowRunResult runResult = await RunMagenticWorkflowAsync( + workflow, + [new ChatMessage(ChatRole.User, "Test stall decrement")], + eventCollector: collectedEvents); + + // Assert: Three progress ledger updates, no stall-triggered reset + collectedEvents.OfType().Should().HaveCount(3, + "three coordination rounds should produce three progress ledger events"); + + // One initial plan + two replans from re-entering after rounds 1 and 2 + // If stall reset had occurred, there would be an additional replan beyond normal re-entry + collectedEvents.OfType().Should().ContainSingle( + "only one initial plan should be created"); + collectedEvents.OfType().Should().HaveCount(2, + "two normal re-entry replans after rounds 1 and 2; no extra replan from stall reset"); + + runResult.Result.Should().NotBeNull(); + runResult.Result![0].Text.Should().Contain("Completed without reset!"); + } + [Fact] public async Task PlanReview_Multiple_Revisions() {