diff --git a/dotnet/samples/03-workflows/Agents/WorkflowAsAnAgent/Program.cs b/dotnet/samples/03-workflows/Agents/WorkflowAsAnAgent/Program.cs index 07ba96989a..bc9faff3b0 100644 --- a/dotnet/samples/03-workflows/Agents/WorkflowAsAnAgent/Program.cs +++ b/dotnet/samples/03-workflows/Agents/WorkflowAsAnAgent/Program.cs @@ -9,7 +9,7 @@ namespace WorkflowAsAnAgentSample; /// -/// This sample introduces the concepts workflows as agents, where a workflow can be +/// This sample introduces the concept of workflows as agents, where a workflow can be /// treated as an . This allows you to interact with a workflow /// as if it were a single agent. /// @@ -18,6 +18,14 @@ namespace WorkflowAsAnAgentSample; /// /// You will interact with the workflow in an interactive loop, sending messages and receiving /// streaming responses from the workflow as if it were an agent who responds in both languages. +/// +/// This sample also demonstrates , which is required +/// for stateful executors that are shared across multiple workflow runs. Each iteration +/// of the interactive loop triggers a new workflow run against the same workflow instance. +/// Between runs, the framework automatically calls +/// on shared executors so that accumulated state (e.g., collected messages) is cleared +/// before the next run begins. See WorkflowFactory.ConcurrentAggregationExecutor +/// for the implementation. /// /// /// Pre-requisites: @@ -39,7 +47,10 @@ private static async Task Main() var agent = workflow.AsAIAgent("workflow-agent", "Workflow Agent"); var session = await agent.CreateSessionAsync(); - // Start an interactive loop to interact with the workflow as if it were an agent + // Start an interactive loop to interact with the workflow as if it were an agent. + // Each iteration runs the workflow again on the same workflow instance. Between runs, + // the framework calls IResettableExecutor.ResetAsync() on shared stateful executors + // (like ConcurrentAggregationExecutor) to clear accumulated state from the previous run. while (true) { Console.WriteLine(); diff --git a/dotnet/samples/03-workflows/Agents/WorkflowAsAnAgent/WorkflowFactory.cs b/dotnet/samples/03-workflows/Agents/WorkflowAsAnAgent/WorkflowFactory.cs index 2fdfe703bf..bcac8894ab 100644 --- a/dotnet/samples/03-workflows/Agents/WorkflowAsAnAgent/WorkflowFactory.cs +++ b/dotnet/samples/03-workflows/Agents/WorkflowAsAnAgent/WorkflowFactory.cs @@ -10,6 +10,14 @@ internal static class WorkflowFactory { /// /// Creates a workflow that uses two language agents to process input concurrently. + /// + /// In this workflow, the Start and the + /// are provided as shared instances, meaning + /// the same executor objects are reused across multiple workflow runs. The language agents + /// (French and English) are created via a factory and instantiated per workflow run. + /// Stateful shared executors must implement so the + /// framework can clear their state between runs. Framework-provided executors like + /// already implement this interface. /// /// The chat client to use for the agents /// A workflow that processes input using two language agents @@ -40,6 +48,16 @@ private static ChatClientAgent GetLanguageAgent(string targetLanguage, IChatClie /// /// Executor that aggregates the results from the concurrent agents. + /// + /// This executor is stateful — it accumulates messages in + /// as they arrive from each agent. Because it is provided as a shared instance + /// (not via a factory), the same object is reused across workflow runs. Implementing + /// allows the framework to call + /// between runs, clearing accumulated state so each run starts fresh. + /// + /// Without , attempting to reuse a workflow containing + /// shared executor instances that do not implement this interface would throw an + /// . /// [YieldsOutput(typeof(string))] private sealed class ConcurrentAggregationExecutor() : @@ -65,7 +83,11 @@ public override async ValueTask HandleAsync(List message, IWorkflow } } - /// + /// + /// Resets the executor state between workflow runs by clearing accumulated messages. + /// The framework calls this automatically when a workflow run completes, before the + /// workflow can be used for another run. + /// public ValueTask ResetAsync() { this._messages.Clear();