Skip to content

Commit 596df49

Browse files
committed
Merge remote-tracking branch 'upstream/main' into fix/foundry-hosting-handoff-arguments
2 parents 03b0bfe + 2ef20cd commit 596df49

4 files changed

Lines changed: 1442 additions & 11 deletions

File tree

dotnet/src/Microsoft.Agents.AI.Workflows/MagenticWorkflowBuilder.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright (c) Microsoft. All rights reserved.
22

3+
using System;
34
using System.Collections.Generic;
45
using System.Diagnostics.CodeAnalysis;
56
using System.Threading.Tasks;
@@ -140,7 +141,15 @@ private WorkflowBuilder ReduceToWorkflowBuilder()
140141
}
141142

142143
/// <inheritdoc cref="WorkflowBuilder.Build"/>
143-
public Workflow Build() => this.ReduceToWorkflowBuilder().Build();
144+
public Workflow Build()
145+
{
146+
if (this._team.Count == 0)
147+
{
148+
throw new InvalidOperationException("At least one participant must be added via AddParticipants() before building the workflow.");
149+
}
150+
151+
return this.ReduceToWorkflowBuilder().Build();
152+
}
144153

145154
private TaskLimits Limits => new(
146155
MaxRoundCount: this._maxRounds,

dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/Magentic/MagenticOrchestrator.cs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ protected override ProtocolBuilder ConfigureProtocol(ProtocolBuilder protocolBui
101101
return base.ConfigureProtocol(protocolBuilder)
102102
.SendsMessage<ChatMessage>()
103103
.SendsMessage<ResetChatSignal>()
104+
.YieldsOutput<List<ChatMessage>>()
104105
.ConfigureRoutes(ConfigureRoutes);
105106

106107
void ConfigureRoutes(RouteBuilder routeBuilder) => routeBuilder.AddPortHandler<MagenticPlanReviewRequest, MagenticPlanReviewResponse>(
@@ -109,15 +110,15 @@ void ConfigureRoutes(RouteBuilder routeBuilder) => routeBuilder.AddPortHandler<M
109110
out this._planReviewPort);
110111
}
111112

112-
private ValueTask SubmitPlanReviewRequestAsync(MagenticTaskContext taskContext, IWorkflowContext workflowContext)
113+
private ValueTask SubmitPlanReviewRequestAsync(MagenticTaskContext taskContext, IWorkflowContext workflowContext, bool replanAfterStall = false)
113114
{
114115
MagenticProgressLedger? progressLedger = taskContext.ProgressLedger;
115116
if (progressLedger?.IsStarted is not true)
116117
{
117118
progressLedger = null;
118119
}
119120

120-
MagenticPlanReviewRequest request = new(taskContext.TaskLedger!.CurrentPlan, progressLedger, taskContext.IsStalled);
121+
MagenticPlanReviewRequest request = new(taskContext.TaskLedger!.CurrentPlan, progressLedger, replanAfterStall);
121122

122123
return this._planReviewPort!.PostRequestAsync(request);
123124
}
@@ -146,7 +147,7 @@ to the conversation and enters the inner loop.
146147

147148
if (this._taskContext.IsTerminated)
148149
{
149-
throw new InvalidOperationException("Magentic Orchestration has already been terminated and cannot process new messages. Please start a new session.");
150+
throw new InvalidOperationException("This Magentic orchestration has already terminated. To process new messages, create a new workflow instance.");
150151
}
151152

152153
if (response.IsApproved)
@@ -161,7 +162,7 @@ to the conversation and enters the inner loop.
161162
}
162163
}
163164

164-
private async ValueTask UpdatePlanAndDelegateAsync(MagenticTaskContext taskContext, IWorkflowContext context, CancellationToken cancellationToken)
165+
private async ValueTask UpdatePlanAndDelegateAsync(MagenticTaskContext taskContext, IWorkflowContext context, CancellationToken cancellationToken, bool replanAfterStall = false)
165166
{
166167
bool isReplan = taskContext.TaskLedger != null;
167168

@@ -177,7 +178,7 @@ await context.AddEventAsync(isReplan
177178

178179
if (requirePlanSignoff)
179180
{
180-
await this.SubmitPlanReviewRequestAsync(taskContext, context).ConfigureAwait(false);
181+
await this.SubmitPlanReviewRequestAsync(taskContext, context, replanAfterStall).ConfigureAwait(false);
181182
}
182183
else
183184
{
@@ -187,9 +188,22 @@ await context.AddEventAsync(isReplan
187188

188189
protected override async ValueTask TakeTurnAsync(List<ChatMessage> messages, IWorkflowContext context, bool? emitEvents, CancellationToken cancellationToken = default)
189190
{
190-
// First Turn: Initialize the task context and send the initial messages to the planner agent
191-
this._taskContext ??= new(messages, team, limits, emitEvents, []);
192-
await this.UpdatePlanAndDelegateAsync(this._taskContext, context, cancellationToken).ConfigureAwait(false);
191+
if (this._taskContext?.IsTerminated == true)
192+
{
193+
throw new InvalidOperationException("This Magentic orchestration has already terminated. To process new messages, create a new workflow instance.");
194+
}
195+
196+
if (this._taskContext == null)
197+
{
198+
// First Turn: Initialize the task context and create the initial plan
199+
this._taskContext = new(messages, team, limits, emitEvents, []);
200+
await this.UpdatePlanAndDelegateAsync(this._taskContext, context, cancellationToken).ConfigureAwait(false);
201+
}
202+
else
203+
{
204+
// Subsequent turns: agent returned control, go directly to coordination (progress ledger only, no replan)
205+
await this.RunCoordinationRoundAsync(this._taskContext, context, cancellationToken).ConfigureAwait(false);
206+
}
193207
}
194208

195209
private ChatMessage? _fullTaskLedgerMessage;
@@ -288,10 +302,11 @@ await context.AddEventAsync(new WorkflowWarningEvent($"Invalid next speaker: {ne
288302

289303
private async ValueTask ResetAndReplanAsync(MagenticTaskContext taskContext, IWorkflowContext context, CancellationToken cancellationToken)
290304
{
305+
bool wasStalled = taskContext.IsStalled;
291306
taskContext.Reset();
292307
await context.SendMessageAsync(new ResetChatSignal(), cancellationToken: cancellationToken).ConfigureAwait(false);
293308

294-
await this.UpdatePlanAndDelegateAsync(taskContext, context, cancellationToken).ConfigureAwait(false);
309+
await this.UpdatePlanAndDelegateAsync(taskContext, context, cancellationToken, replanAfterStall: wasStalled).ConfigureAwait(false);
295310
}
296311

297312
private async ValueTask PrepareFinalAnswerAsync(MagenticTaskContext taskContext, IWorkflowContext context, CancellationToken cancellationToken)

dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/Magentic/MagenticTaskContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ internal MagenticTaskContext(MagenticTaskState state, List<AIAgent> team, TaskLi
5858

5959
public bool IsTerminated { get; internal set; }
6060

61-
public bool IsStalled => this.TaskCounters.StallCount >= this.TaskLimits.MaxStallCount;
61+
public bool IsStalled => this.TaskCounters.StallCount > this.TaskLimits.MaxStallCount;
6262

6363
public (bool HitRoundLimit, bool HitResetLimit) CheckLimits()
6464
{

0 commit comments

Comments
 (0)