// Copyright (c) Microsoft. All rights reserved.
// ConfigureAwait Usage in Orchestration Code:
// This file uses ConfigureAwait(true) because it runs within orchestration context.
// Durable Task orchestrations require deterministic replay - the same code must execute
// identically across replays. ConfigureAwait(true) ensures continuations run on the
// orchestration's synchronization context, which is essential for replay correctness.
// Using ConfigureAwait(false) here could cause non-deterministic behavior during replay.
// Superstep execution walkthrough for a workflow like below:
//
// [A] ──► [B] ──► [C] ──► [E] (B→D has condition: x => x.NeedsReview)
// │ ▲
// └──► [D] ──────┘
//
// Superstep 1 — A runs
// Queues before: A:[input] Results: {}
// Dispatch: A executes, returns resultA
// Route: EdgeMap routes A's output → B's queue
// Queues after: B:[resultA] Results: {A: resultA}
//
// Superstep 2 — B runs
// Queues before: B:[resultA] Results: {A: resultA}
// Dispatch: B executes, returns resultB (type: Order)
// Route: FanOutRouter sends resultB to:
// C's queue (unconditional)
// D's queue (only if resultB.NeedsReview == true)
// Queues after: C:[resultB], D:[resultB] Results: {A: .., B: resultB}
// (D may be empty if condition was false)
//
// Superstep 3 — C and D run in parallel
// Queues before: C:[resultB], D:[resultB]
// Dispatch: C and D execute concurrently via Task.WhenAll
// Route: Both route output → E's queue
// Queues after: E:[resultC, resultD] Results: {.., C: resultC, D: resultD}
//
// Superstep 4 — E runs (fan-in)
// Queues before: E:[resultC, resultD] ◄── IsFanInExecutor("E") = true
// Collect: AggregateQueueMessages merges into JSON array ["resultC","resultD"]
// Dispatch: E executes with aggregated input
// Route: E has no successors → nothing enqueued
// Queues after: (all empty) Results: {.., E: resultE}
//
// Superstep 5 — loop exits (no pending messages)
// GetFinalResult returns resultE
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Microsoft.Agents.AI.DurableTask.Workflows.EdgeRouters;
using Microsoft.Agents.AI.Workflows;
using Microsoft.DurableTask;
using Microsoft.Extensions.Logging;
namespace Microsoft.Agents.AI.DurableTask.Workflows;
// Superstep loop:
//
// ┌───────────────┐ ┌───────────────┐ ┌───────────────────┐
// │ Collect │───►│ Dispatch │───►│ Process Results │
// │ Executor │ │ Executors │ │ & Route Messages │
// │ Inputs │ │ in Parallel │ │ │
// └───────────────┘ └───────────────┘ └───────────────────┘
// ▲ │
// └───────────────────────────────────────────┘
// (repeat until no pending messages)
///
/// Runs workflow orchestrations using message-driven superstep execution with Durable Task.
///
internal sealed class DurableWorkflowRunner
{
private const int MaxSupersteps = 100;
///
/// Initializes a new instance of the class.
///
/// The durable options containing workflow configurations.
public DurableWorkflowRunner(DurableOptions durableOptions)
{
ArgumentNullException.ThrowIfNull(durableOptions);
this.Options = durableOptions.Workflows;
}
///
/// Gets the workflow options.
///
private DurableWorkflowOptions Options { get; }
///
/// Runs a workflow orchestration.
///
/// The task orchestration context.
/// The workflow input envelope containing workflow input and metadata.
/// The replay-safe logger for orchestration logging.
/// The result of the workflow execution.
/// Thrown when the specified workflow is not found.
internal async Task RunWorkflowOrchestrationAsync(
TaskOrchestrationContext context,
DurableWorkflowInput