diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/HandoffAgentExecutor.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/HandoffAgentExecutor.cs index 3ba6603b73..80799bb242 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/HandoffAgentExecutor.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/HandoffAgentExecutor.cs @@ -316,8 +316,6 @@ internal sealed class HandoffAgentExecutor : // turn by injecting a synthetic user message instead of returning control to the user. if (this._options.AutonomousMode && !result.IsHandoffRequested && this._autonomousModeTurnCount < this._options.AutonomousModeTurnLimit) { - this._autonomousModeTurnCount++; - ChatMessage autonomousMessage = new(ChatRole.User, this._options.AutonomousModePrompt) { CreatedAt = DateTimeOffset.UtcNow, @@ -334,6 +332,10 @@ internal sealed class HandoffAgentExecutor : context, cancellationToken).ConfigureAwait(false); + // Increment only after successfully adding the autonomous message to shared state. + // This ensures the counter remains accurate if the state write throws an exception. + this._autonomousModeTurnCount++; + return await this.ContinueTurnAsync( state with { ConversationBookmark = autonomousBookmark }, [autonomousMessage], diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffAgentExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffAgentExecutorTests.cs index 9ce9918d16..cf98e01434 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffAgentExecutorTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffAgentExecutorTests.cs @@ -239,13 +239,12 @@ public class HandoffAgentExecutorTests : AIAgentHostingExecutorTestsBase [InlineData(1)] [InlineData(2)] [InlineData(3)] - public async Task Test_HandoffAgentExecutor_AutonomousMode_InvokesAgentUpToTurnLimitPlusOne(int turnLimit) + public async Task Test_HandoffAgentExecutor_AutonomousMode_InvokesAgentExactlyOnePlusTurnLimitTimes(int turnLimit) { // Arrange: agent with many prepared turns; no handoff ever requested; autonomous mode ON - // We prepare (turnLimit + 2) turns so that if the implementation over-invokes by one, - // TestReplayAgent.Turn will be (turnLimit + 2) rather than the expected (turnLimit + 1). - // TestReplayAgent does NOT increment Turn when it runs out of prepared messages, so preparing - // exactly (turnLimit + 1) would make an extra invocation silently undetectable. + // We prepare (turnLimit + 2) turns to detect off-by-one errors. TestReplayAgent stops + // incrementing Turn when prepared messages are exhausted, so preparing exactly (turnLimit + 1) + // turns would fail to detect if the implementation invokes the agent one extra time. int totalTurns = turnLimit + 2; TestReplayAgent agent = new( Enumerable.Range(0, totalTurns)