From 1e242c2f9fd9292f8797d31921a9bfff01f5d89f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 23:26:02 +0000 Subject: [PATCH] Add Consecutive_Stalls_Trigger_Reset E2E test for multi-stall threshold 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 | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs index c3f38d6a89..1e722d80c1 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs @@ -1007,6 +1007,78 @@ public class MagenticOrchestrationTests runResult.Result![0].Text.Should().Contain("Completed without reset!"); } + [Fact] + public async Task Consecutive_Stalls_Trigger_Reset() + { + // Arrange: MaxStallCount=2 — two consecutive stalls trigger reset. + // Round 1: isInLoop=true (stall count → 1), delegates to Worker + // Round 2: isProgressBeingMade=false (stall count → 2 → IsStalled) → reset & replan + // After reset: new plan → ledger(satisfied) → final answer + List facts1 = CreatePlanResponse("Initial facts"); + List plan1 = CreatePlanResponse("Initial plan"); + List ledger1 = CreateProgressLedgerResponse( + isRequestSatisfied: false, + isInLoop: true, // stall #1 → StallCount=1 + isProgressBeingMade: true, + nextSpeaker: "Worker", + instructionOrQuestion: "Keep trying"); + + // After Worker responds → re-enter TakeTurnAsync → replan + List facts2 = CreatePlanResponse("Updated facts round 2"); + List plan2 = CreatePlanResponse("Updated plan round 2"); + List ledger2 = CreateProgressLedgerResponse( + isRequestSatisfied: false, + isInLoop: false, + isProgressBeingMade: false, // stall #2 → StallCount=2 → IsStalled → reset + nextSpeaker: "Worker", + instructionOrQuestion: "No progress"); + + // Reset & replan: new facts + plan, then coordination round + List resetFacts = CreatePlanResponse("Fresh facts after stall reset"); + List resetPlan = CreatePlanResponse("Fresh plan after stall reset"); + List postResetLedger = CreateProgressLedgerResponse( + isRequestSatisfied: true, + isInLoop: false, + isProgressBeingMade: true, + nextSpeaker: "Worker", + instructionOrQuestion: "Done"); + List finalAnswerResponse = CreateFinalAnswerResponse("Recovered after consecutive stalls!"); + + TestReplayAgent manager = new( + [facts1, plan1, ledger1, + facts2, plan2, ledger2, + resetFacts, resetPlan, postResetLedger, finalAnswerResponse], + name: "Manager"); + TestEchoAgent worker = new(name: "Worker"); + + List collectedEvents = []; + + Workflow workflow = new MagenticWorkflowBuilder(manager) + .AddParticipants(worker) + .RequirePlanSignoff(false) + .WithMaxStalls(2) // requires 2 consecutive stalls + .Build(); + + // Act + WorkflowRunResult runResult = await RunMagenticWorkflowAsync( + workflow, + [new ChatMessage(ChatRole.User, "Test consecutive stalls")], + eventCollector: collectedEvents); + + // Assert: Two initial coordination rounds + one post-reset round = 3 ledger events + collectedEvents.OfType().Should().HaveCount(3, + "two pre-reset rounds and one post-reset round"); + + // Replan from stall reset should appear as an extra MagenticReplannedEvent + // Normal flow: 1 PlanCreated + 1 replan (re-entry after round 1). Stall adds 1 more replan = 2 total replans. + collectedEvents.OfType().Should().ContainSingle(); + collectedEvents.OfType().Should().HaveCount(2, + "one normal re-entry replan + one stall-triggered reset replan"); + + runResult.Result.Should().NotBeNull(); + runResult.Result![0].Text.Should().Contain("Recovered after consecutive stalls!"); + } + [Fact] public async Task PlanReview_Multiple_Revisions() {