From 2673054af214bba2b3e18426c3a7df18ef69ed3a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 22:26:31 +0000 Subject: [PATCH] Add PlanReview_On_Stall_Replan E2E test Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/aba19507-7c7e-40dd-850d-d1fabb5dfa65 Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com> --- .../MagenticOrchestrationTests.cs | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs index 2cafc54f6a..17f9cb1c8c 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs @@ -532,6 +532,97 @@ public class MagenticOrchestrationTests runResult.Result![0].Text.Should().Contain("Recovered after stall reset"); } + [Fact] + public async Task PlanReview_On_Stall_Replan() + { + // Arrange: Plan signoff enabled, stall triggers reset, replan requires new plan review. + // Flow: facts1, plan1 → PlanCreatedEvent → plan review (pending) + // resume with approval → ledger1(stall: isInLoop=true) → StallCount=1 → IsStalled → Reset + // → facts2, plan2 → MagenticReplannedEvent → plan review again (pending) + // resume with approval → ledger2(satisfied) → finalAnswer + List factsResponse1 = CreatePlanResponse("Initial facts"); + List planResponse1 = CreatePlanResponse("Initial plan"); + List stalledLedger = CreateProgressLedgerResponse( + isRequestSatisfied: false, + isInLoop: true, // This triggers stall + isProgressBeingMade: true, + nextSpeaker: "Worker", + instructionOrQuestion: "Keep trying"); + + // After reset: new plan + List factsResponse2 = CreatePlanResponse("Fresh facts after stall reset"); + List planResponse2 = CreatePlanResponse("Fresh plan after stall reset"); + // After second approval: satisfied ledger + final answer + List satisfiedLedger = CreateProgressLedgerResponse( + isRequestSatisfied: true, + isInLoop: false, + isProgressBeingMade: true, + nextSpeaker: "Worker", + instructionOrQuestion: "Done"); + List finalAnswerResponse = CreateFinalAnswerResponse("Recovered after stall with plan review"); + + TestReplayAgent manager = new( + [factsResponse1, planResponse1, stalledLedger, + factsResponse2, planResponse2, satisfiedLedger, finalAnswerResponse], + name: "Manager"); + TestEchoAgent worker = new(name: "Worker"); + + Workflow workflow = new MagenticWorkflowBuilder(manager) + .AddParticipants(worker) + .RequirePlanSignoff(true) + .WithMaxStalls(1) + .Build(); + + CheckpointManager checkpointManager = CheckpointManager.CreateInMemory(); + List allEvents = []; + + // Act 1: First run - should pause for initial plan review + WorkflowRunResult firstResult = await RunMagenticWorkflowAsync( + workflow, + [new ChatMessage(ChatRole.User, "Do task")], + checkpointManager: checkpointManager, + eventCollector: allEvents); + + firstResult.PendingRequests.Should().ContainSingle(); + ExternalRequest request1 = firstResult.PendingRequests[0].Request; + MagenticPlanReviewRequest? reviewRequest1 = request1.Data.As(); + reviewRequest1.Should().NotBeNull(); + reviewRequest1!.Plan.Text.Should().Contain("Initial plan"); + + // Act 2: Approve initial plan → stall occurs → reset → replan → new plan review + MagenticPlanReviewResponse approval1 = reviewRequest1.Approve(); + ExternalResponse approvalResponse1 = request1.CreateResponse(approval1); + WorkflowRunResult secondResult = await ResumeMagenticWorkflowAsync( + workflow, + approvalResponse1, + checkpointManager, + firstResult.LastCheckpoint, + eventCollector: allEvents); + + // Should pause for review of the replanned plan + secondResult.PendingRequests.Should().NotBeEmpty(); + ExternalRequest request2 = secondResult.PendingRequests[^1].Request; + MagenticPlanReviewRequest? reviewRequest2 = request2.Data.As(); + reviewRequest2.Should().NotBeNull(); + reviewRequest2!.Plan.Text.Should().Contain("Fresh plan after stall reset"); + + // Act 3: Approve the revised plan → satisfied → final answer + MagenticPlanReviewResponse approval2 = reviewRequest2.Approve(); + ExternalResponse approvalResponse2 = request2.CreateResponse(approval2); + WorkflowRunResult thirdResult = await ResumeMagenticWorkflowAsync( + workflow, + approvalResponse2, + checkpointManager, + secondResult.LastCheckpoint, + eventCollector: allEvents); + + // Assert + allEvents.OfType().Should().NotBeEmpty("initial plan emits PlanCreatedEvent"); + allEvents.OfType().Should().NotBeEmpty("stall reset triggers ReplannedEvent"); + thirdResult.Result.Should().NotBeNull(); + thirdResult.Result![0].Text.Should().Contain("Recovered after stall with plan review"); + } + [Fact] public async Task MaxResetLimit_Terminates_Workflow() {