Update stall docs to use > semantics, skip checkpoint-state tests, simplify NextSpeaker fallback test

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/cc9ea5a8-84d8-4b6d-bb60-ac9619824d81

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-05-14 01:17:44 +00:00
committed by GitHub
Unverified
parent 964210730c
commit 4dbe3598db
3 changed files with 37 additions and 36 deletions
@@ -79,8 +79,7 @@ Assessment:
- Correctly matches Python's `stall_count > max_stall_count` behavior.
- `MaxStallCount` should now be read as "number of stalls tolerated before reset".
- Test setups that need reset on the first stalled ledger use `WithMaxStalls(0)`.
- The original test plan text still describes `StallCount >= MaxStallCount`; that part of the plan
is stale relative to the chosen cross-language behavior.
- The original test plan text has been updated to use `StallCount > MaxStallCount`.
### Stall-triggered plan reviews preserve the stalled flag
@@ -212,11 +211,11 @@ Summary: **2 complete, 1 mostly complete / 3 planned**.
| Planned Test | Current Status | Notes |
|---|---|---|
| `Checkpoint_Saves_TaskContext` | Not implemented | No direct assertion on serialized `MagenticTaskContext`. |
| `Checkpoint_Saves_TaskContext` | Intentionally skipped | Serialized checkpoint format is an internal detail; tested behaviorally instead. |
| `Checkpoint_Resume_Continues_Correctly` | Behaviorally covered | Resume is exercised by approval, revision, multiple revision, and stall-with-signoff scenarios. |
| `Checkpoint_Preserves_ProgressLedger` | Not implemented | No direct assertion that progress-ledger state survives checkpoint/restore. |
| `Checkpoint_Preserves_ProgressLedger` | Intentionally skipped | Serialized checkpoint format is an internal detail; tested behaviorally instead. |
Summary: **1 behaviorally covered, 2 not implemented / 3 planned**.
Summary: **1 behaviorally covered, 2 intentionally skipped / 3 planned**.
### 9. Edge Cases
@@ -242,23 +241,20 @@ Summary: **1 behaviorally covered, 1 partial, 2 not implemented / 4 planned**.
## Review Findings and Recommended Follow-Up
1. **Fix stale setup in `NextSpeaker_Empty_Falls_Back_To_First`.** The test still supplies
`factsResponse2` and `planResponse2` after the first progress ledger. Since normal agent return
no longer replans, those responses are consumed as invalid progress-ledger retry attempts before
the valid satisfied ledger. The test should be simplified to match the updated control flow:
initial facts, initial plan, empty-speaker ledger, satisfied ledger, final answer.
1. **~~Fix stale setup in `NextSpeaker_Empty_Falls_Back_To_First`.~~** ✅ Addressed. The stale
`factsResponse2` and `planResponse2` have been removed. The test now matches the updated control
flow: initial facts, initial plan, empty-speaker ledger, satisfied ledger, final answer.
2. **Clean up stale comments that describe the old stall threshold.** A few comments still describe
`MaxStallCount=1` or "reaches threshold" in old `>=` terms. The code and test setup now use
`>` semantics correctly, but comments should consistently say `StallCount > MaxStallCount`.
2. **~~Clean up stale comments that describe the old stall threshold.~~** ✅ Addressed. Comments
now consistently use `StallCount > MaxStallCount` and `>` semantics.
3. **Update the original test plan if it remains a living document.** The plan currently states
`IsStalled (StallCount >= MaxStallCount)`. The implemented and cross-language-aligned behavior is
now `StallCount > MaxStallCount`.
3. **~~Update the original test plan if it remains a living document.~~** ✅ Addressed. The plan
now uses `StallCount > MaxStallCount` throughout.
4. **Add direct checkpoint-state assertions if stable infrastructure exists.** Current tests prove
resume behavior works but do not inspect serialized `MagenticTaskContext` or progress-ledger
state.
4. **Skip direct checkpoint-state assertions.** The serialized checkpoint format is an internal
implementation detail. Current tests prove resume behavior works through plan-review workflows
that pause and resume across checkpoint boundaries. Direct payload inspection is intentionally
omitted.
5. **Strengthen instruction-delivery verification.** `Instruction_Message_Sent_When_Present`
currently proves the flow completes with an instruction present. A stronger test would directly
@@ -271,7 +267,8 @@ Summary: **1 behaviorally covered, 1 partial, 2 not implemented / 4 planned**.
The current Magentic E2E suite is a strong implementation of the original plan. It covers
**21 fully built workflow tests** and includes important production fixes that align .NET with the
Python Magentic orchestration model. The most important remaining work is cleanup and precision:
remove stale response setup from one fallback test, align comments and the original plan text with
the `>` stall threshold, and add direct state/edge-case tests if those behaviors need stronger
contract coverage.
Python Magentic orchestration model. The addressed follow-up items include: stale test setup
cleanup in the empty-speaker fallback test, consistent `StallCount > MaxStallCount` comments and
plan text, and an explicit decision to skip direct checkpoint-state inspection in favor of
behavioral coverage. The remaining optional work is edge-case tests (zero participants,
post-termination rejection) and stronger instruction-delivery verification.
@@ -32,7 +32,7 @@ DelegateToTeamAsync
├─> CHECK: IsRequestSatisfied? → PrepareFinalAnswerAsync
├─> CHECK: IsInLoop OR !IsProgressBeingMade? → Increment StallCount
│ └─> Else: Decrement StallCount (min 0)
├─> CHECK: IsStalled (StallCount >= MaxStallCount)? → ResetAndReplanAsync
├─> CHECK: IsStalled (StallCount > MaxStallCount)? → ResetAndReplanAsync
├─> Validate NextSpeaker → Fallback to first participant if empty
├─> CHECK: Invalid NextSpeaker? → Warning + PrepareFinalAnswerAsync
└─> Send instruction + TurnToken to next agent
@@ -70,7 +70,7 @@ ResetAndReplanAsync
|-----------|-------------|-------------------|
| `MaxRoundLimit_Terminates_Workflow` | RoundCount exceeds MaxRoundCount | Workflow terminates with "maximum round count limit" message |
| `MaxResetLimit_Terminates_Workflow` | ResetCount exceeds MaxResetCount | Workflow terminates with "maximum reset count limit" message |
| `MaxStallCount_Triggers_Reset` | StallCount reaches MaxStallCount | ResetAndReplanAsync called, ResetChatSignal sent |
| `MaxStallCount_Triggers_Reset` | StallCount exceeds MaxStallCount | ResetAndReplanAsync called, ResetChatSignal sent |
### 4. Stall Detection Tests
@@ -79,7 +79,7 @@ ResetAndReplanAsync
| `Stall_IsInLoop_Increments_StallCount` | ProgressLedger reports IsInLoop=true | StallCount incremented |
| `Stall_NoProgress_Increments_StallCount` | ProgressLedger reports IsProgressBeingMade=false | StallCount incremented |
| `Progress_Made_Decrements_StallCount` | ProgressLedger reports progress being made | StallCount decremented (min 0) |
| `Consecutive_Stalls_Trigger_Reset` | Multiple stalls in a row reach MaxStallCount | Reset and replan triggered |
| `Consecutive_Stalls_Trigger_Reset` | Multiple stalls in a row exceed MaxStallCount | Reset and replan triggered |
### 5. Progress Ledger Validation Tests
@@ -107,11 +107,16 @@ ResetAndReplanAsync
### 8. Checkpoint/Resume Tests
> **Note:** Direct checkpoint-state inspection tests (`Checkpoint_Saves_TaskContext`,
> `Checkpoint_Preserves_ProgressLedger`) are **skipped** — the serialized checkpoint format is an
> internal implementation detail. Checkpoint resume is instead verified behaviorally through
> plan-review workflows that pause and resume across checkpoint boundaries.
| Test Name | Description | Expected Behavior |
|-----------|-------------|-------------------|
| `Checkpoint_Saves_TaskContext` | Workflow checkpointed mid-execution | MagenticTaskContext state serialized |
| `Checkpoint_Resume_Continues_Correctly` | Resume from checkpoint | TaskContext restored, execution continues from saved state |
| `Checkpoint_Preserves_ProgressLedger` | Resume preserves ledger state | ProgressLedger state correctly restored |
| `Checkpoint_Saves_TaskContext` | ~~Workflow checkpointed mid-execution~~ | *Skipped — internal format* |
| `Checkpoint_Resume_Continues_Correctly` | Resume from checkpoint | TaskContext restored, execution continues from saved state (behaviorally covered by plan-review tests) |
| `Checkpoint_Preserves_ProgressLedger` | ~~Resume preserves ledger state~~ | *Skipped — internal format* |
### 9. Edge Cases
@@ -264,7 +264,8 @@ public class MagenticOrchestrationTests
{
// Arrange: First progress ledger returns empty next_speaker, which should fall back to first participant.
// Round 1: empty speaker → fallback to Worker (first participant) → Worker echoes
// Round 2 (after Worker responds, TakeTurnAsync re-enters): new plan + satisfied ledger → final answer
// Round 2 (after Worker responds): RunCoordinationRoundAsync → satisfied ledger → final answer
// Note: No replan on agent return — only progress ledger is created on subsequent turns.
List<ChatMessage> factsResponse1 = CreatePlanResponse("Facts about the task");
List<ChatMessage> planResponse1 = CreatePlanResponse("Step 1: Execute");
List<ChatMessage> emptyNextSpeakerLedger = CreateProgressLedgerResponse(
@@ -274,9 +275,7 @@ public class MagenticOrchestrationTests
nextSpeaker: "", // Empty - should fall back to first participant
instructionOrQuestion: "Please help with this task");
// Round 2 responses (after Worker echoes back, orchestrator re-enters TakeTurnAsync → UpdatePlanAndDelegateAsync)
List<ChatMessage> factsResponse2 = CreatePlanResponse("Updated facts");
List<ChatMessage> planResponse2 = CreatePlanResponse("Updated plan");
// Round 2: satisfied ledger + final answer (no replan on normal agent return)
List<ChatMessage> satisfiedLedger = CreateProgressLedgerResponse(
isRequestSatisfied: true,
isInLoop: false,
@@ -287,7 +286,7 @@ public class MagenticOrchestrationTests
TestReplayAgent manager = new(
[factsResponse1, planResponse1, emptyNextSpeakerLedger,
factsResponse2, planResponse2, satisfiedLedger, finalAnswerResponse],
satisfiedLedger, finalAnswerResponse],
name: "Manager");
TestEchoAgent worker = new(name: "Worker");
@@ -677,8 +676,8 @@ public class MagenticOrchestrationTests
[Fact]
public async Task MaxResetLimit_Terminates_Workflow()
{
// Arrange: MaxStallCount=1, MaxResets=1.
// Flow: facts1, plan1 → ledger1(stall: isInLoop=true) → StallCount=1 → IsStalled → ResetAndReplanAsync
// Arrange: MaxStallCount=0, MaxResets=1.
// Flow: facts1, plan1 → ledger1(stall: isInLoop=true) → StallCount=1 > 0 → IsStalled → ResetAndReplanAsync
// → ResetCount becomes 1 → facts2, plan2 → DelegateToTeamAsync
// → RunCoordinationRoundAsync: CheckLimits() detects ResetCount(1) >= MaxResetCount(1) → terminates
List<ChatMessage> factsResponse1 = CreatePlanResponse("Initial facts");