Introduce some interfaces,

This commit is contained in:
Shyju Krishnankutty
2026-01-27 13:35:21 -08:00
Unverified
parent 6b55de8438
commit 8add9748ef
13 changed files with 286 additions and 41 deletions
@@ -2,7 +2,7 @@
// This sample demonstrates how to run a workflow as a durable orchestration from a console application.
// The workflow consists of three executors: OrderLookup -> OrderCancel -> SendEmail.
// It uses the DurableExecution API similar to InProcessExecution for in-process workflows.
// It uses the DurableExecutionEnvironment which is injected via DI.
//
// DURABILITY DEMONSTRATION:
// - Each activity has artificial delays to simulate real-world operations
@@ -13,7 +13,6 @@
using Microsoft.Agents.AI.DurableTask;
using Microsoft.Agents.AI.Workflows;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Client.AzureManaged;
using Microsoft.DurableTask.Worker.AzureManaged;
using Microsoft.Extensions.DependencyInjection;
@@ -51,7 +50,8 @@ IHost host = Host.CreateDefaultBuilder(args)
await host.StartAsync();
DurableTaskClient durableClient = host.Services.GetRequiredService<DurableTaskClient>();
// Get the DurableExecutionEnvironment from DI - no need to manually resolve DurableTaskClient
DurableExecutionEnvironment durableExecution = host.Services.GetRequiredService<DurableExecutionEnvironment>();
Console.WriteLine("Durable Workflow Sample");
Console.WriteLine("Workflow: OrderLookup (2s) -> OrderCancel (5s) -> SendEmail (1s)");
@@ -75,7 +75,7 @@ while (true)
try
{
await StartNewWorkflowAsync(input, cancelOrder, durableClient);
await StartNewWorkflowAsync(input, cancelOrder, durableExecution);
}
catch (Exception ex)
{
@@ -87,12 +87,13 @@ while (true)
await host.StopAsync();
// Start a new workflow
async Task StartNewWorkflowAsync(string orderId, Workflow workflow, DurableTaskClient client)
// Start a new workflow using DurableExecutionEnvironment (no DurableTaskClient needed)
async Task StartNewWorkflowAsync(string orderId, Workflow workflow, DurableExecutionEnvironment execution)
{
Console.WriteLine($"Starting workflow for order '{orderId}'...");
await using DurableRun run = await DurableWorkflow.RunAsync(workflow, orderId, client);
// RunAsync returns IRun, cast to DurableRun for durable-specific features like WaitForCompletionAsync
await using DurableRun run = (DurableRun)await execution.RunAsync(workflow, orderId);
Console.WriteLine($"Instance ID: {run.InstanceId}");
try
@@ -27,7 +27,6 @@ using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.DurableTask;
using Microsoft.Agents.AI.Workflows;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Client.AzureManaged;
using Microsoft.DurableTask.Worker.AzureManaged;
using Microsoft.Extensions.DependencyInjection;
@@ -77,7 +76,9 @@ IHost host = Host.CreateDefaultBuilder(args)
.Build();
await host.StartAsync();
DurableTaskClient durableClient = host.Services.GetRequiredService<DurableTaskClient>();
// Get the DurableExecutionEnvironment from DI - no need to manually resolve DurableTaskClient
DurableExecutionEnvironment durableExecution = host.Services.GetRequiredService<DurableExecutionEnvironment>();
// Console UI
Console.ForegroundColor = ConsoleColor.Cyan;
@@ -109,7 +110,8 @@ while (true)
try
{
await using DurableRun run = await DurableWorkflow.RunAsync(workflow, input, durableClient);
// Cast to DurableRun for durable-specific features like InstanceId and WaitForCompletionAsync
await using DurableRun run = (DurableRun)await durableExecution.RunAsync(workflow, input);
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine($"Instance: {run.InstanceId}");
Console.ResetColor();
@@ -7,7 +7,6 @@
using Microsoft.Agents.AI.DurableTask;
using Microsoft.Agents.AI.Workflows;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Client.AzureManaged;
using Microsoft.DurableTask.Worker.AzureManaged;
using Microsoft.Extensions.DependencyInjection;
@@ -44,15 +43,16 @@ IHost host = Host.CreateDefaultBuilder(args)
await host.StartAsync();
// Get services
DurableTaskClient durableClient = host.Services.GetRequiredService<DurableTaskClient>();
// Get the DurableExecutionEnvironment from DI - no need to manually resolve DurableTaskClient
DurableExecutionEnvironment durableExecution = host.Services.GetRequiredService<DurableExecutionEnvironment>();
// Start the workflow with an expense ID as input
string expenseId = "EXP-2025-001";
Console.WriteLine($"Starting expense reimbursement workflow for expense: {expenseId}");
// Start the workflow and get a streaming handle
await using DurableStreamingRun run = await DurableWorkflow.StreamAsync(expenseApproval, expenseId, durableClient);
// Cast to DurableStreamingRun for durable-specific features like InstanceId and SendResponseAsync
await using DurableStreamingRun run = (DurableStreamingRun)await durableExecution.StreamAsync(expenseApproval, expenseId);
Console.WriteLine($"Workflow started with instance ID: {run.InstanceId}");
Console.WriteLine("Watching for workflow events...\n");
@@ -9,7 +9,7 @@
// 1. AddEventAsync - Emit custom events that callers can observe in real-time
// 2. YieldOutputAsync - Stream intermediate outputs during long-running operations
//
// The sample uses DurableWorkflow.StreamAsync to observe events as they occur,
// The sample uses DurableExecutionEnvironment.StreamAsync to observe events as they occur,
// showing how callers can receive real-time updates from the workflow.
//
// Workflow: OrderLookup -> OrderCancel -> SendEmail
@@ -17,7 +17,6 @@
using Microsoft.Agents.AI.DurableTask;
using Microsoft.Agents.AI.Workflows;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Client.AzureManaged;
using Microsoft.DurableTask.Worker.AzureManaged;
using Microsoft.Extensions.DependencyInjection;
@@ -55,7 +54,8 @@ IHost host = Host.CreateDefaultBuilder(args)
await host.StartAsync();
DurableTaskClient durableClient = host.Services.GetRequiredService<DurableTaskClient>();
// Get the DurableExecutionEnvironment from DI - no need to manually resolve DurableTaskClient
DurableExecutionEnvironment durableExecution = host.Services.GetRequiredService<DurableExecutionEnvironment>();
Console.WriteLine("Workflow Events Demo - Enter order ID (or 'exit'):");
@@ -70,7 +70,7 @@ while (true)
try
{
await RunWorkflowWithStreamingAsync(input, cancelOrder, durableClient);
await RunWorkflowWithStreamingAsync(input, cancelOrder, durableExecution);
}
catch (Exception ex)
{
@@ -83,10 +83,11 @@ while (true)
await host.StopAsync();
// Runs a workflow and streams events as they occur
async Task RunWorkflowWithStreamingAsync(string orderId, Workflow workflow, DurableTaskClient client)
async Task RunWorkflowWithStreamingAsync(string orderId, Workflow workflow, DurableExecutionEnvironment execution)
{
// StreamAsync starts the workflow and returns a handle for observing events
await using DurableStreamingRun run = await DurableWorkflow.StreamAsync(workflow, orderId, client);
// Cast to DurableStreamingRun for durable-specific features like InstanceId
await using DurableStreamingRun run = (DurableStreamingRun)await execution.StreamAsync(workflow, orderId);
Console.WriteLine($"Started: {run.InstanceId}");
// WatchStreamAsync yields events as they're emitted by executors
@@ -19,7 +19,6 @@
using Azure.Identity;
using Microsoft.Agents.AI.DurableTask;
using Microsoft.Agents.AI.Workflows;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Client.AzureManaged;
using Microsoft.DurableTask.Worker.AzureManaged;
using Microsoft.Extensions.AI;
@@ -62,7 +61,8 @@ IHost host = Host.CreateDefaultBuilder(args)
await host.StartAsync();
DurableTaskClient durableClient = host.Services.GetRequiredService<DurableTaskClient>();
// Get the DurableExecutionEnvironment from DI - no need to manually resolve DurableTaskClient
DurableExecutionEnvironment durableExecution = host.Services.GetRequiredService<DurableExecutionEnvironment>();
Console.WriteLine("Workflow Events Demo - Enter input for slogan generation (or 'exit'):");
@@ -77,7 +77,7 @@ while (true)
try
{
await RunWorkflowWithStreamingAsync(input, workflow, durableClient);
await RunWorkflowWithStreamingAsync(input, workflow, durableExecution);
}
catch (Exception ex)
{
@@ -90,10 +90,11 @@ while (true)
await host.StopAsync();
// Runs a workflow and streams events as they occur
async Task RunWorkflowWithStreamingAsync(string orderId, Workflow workflow, DurableTaskClient client)
async Task RunWorkflowWithStreamingAsync(string orderId, Workflow workflow, DurableExecutionEnvironment execution)
{
// StreamAsync starts the workflow and returns a handle for observing events
await using DurableStreamingRun run = await DurableWorkflow.StreamAsync(workflow, orderId, client);
// Cast to DurableStreamingRun for durable-specific features like InstanceId
await using DurableStreamingRun run = (DurableStreamingRun)await execution.StreamAsync(workflow, orderId);
Console.WriteLine($"Started: {run.InstanceId}");
// WatchStreamAsync yields events as they're emitted by executors
+4
View File
@@ -0,0 +1,4 @@
/* Visual Studio Settings File */
{
"environment.visualExperience.colorTheme": "dark-plus"
}
@@ -0,0 +1,114 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
using Microsoft.DurableTask.Client;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Provides a DI-friendly execution environment for running workflows as durable orchestrations.
/// </summary>
/// <remarks>
/// This class wraps the <see cref="DurableTaskClient"/> and provides methods to run workflows
/// without requiring the client to be passed explicitly. Register this class in DI using
/// <see cref="DurableWorkflowServiceCollectionExtensions.ConfigureDurableWorkflows"/>.
/// </remarks>
public sealed class DurableExecutionEnvironment
{
private readonly DurableTaskClient _client;
/// <summary>
/// Initializes a new instance of the <see cref="DurableExecutionEnvironment"/> class.
/// </summary>
/// <param name="client">The durable task client for orchestration operations.</param>
public DurableExecutionEnvironment(DurableTaskClient client)
{
this._client = client ?? throw new ArgumentNullException(nameof(client));
}
/// <summary>
/// Runs a workflow as a durable orchestration and returns a handle to monitor its execution.
/// </summary>
/// <typeparam name="TInput">The type of the input to the workflow.</typeparam>
/// <param name="workflow">The workflow to execute.</param>
/// <param name="input">The input to pass to the workflow's starting executor.</param>
/// <param name="instanceId">Optional instance ID for the orchestration. If not provided, a new ID will be generated.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>An <see cref="IRun"/> that can be used to monitor the workflow execution.</returns>
/// <exception cref="ArgumentNullException">Thrown when workflow is null.</exception>
/// <exception cref="ArgumentException">Thrown when the workflow does not have a valid name.</exception>
public ValueTask<IRun> RunAsync<TInput>(
Workflow workflow,
TInput input,
string? instanceId = null,
CancellationToken cancellationToken = default)
where TInput : notnull
=> DurableWorkflow.RunAsync(workflow, input, this._client, instanceId, cancellationToken);
/// <summary>
/// Runs a workflow as a durable orchestration with string input.
/// </summary>
/// <param name="workflow">The workflow to execute.</param>
/// <param name="input">The string input to pass to the workflow.</param>
/// <param name="instanceId">Optional instance ID for the orchestration.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>An <see cref="IRun"/> that can be used to monitor the workflow execution.</returns>
public ValueTask<IRun> RunAsync(
Workflow workflow,
string input,
string? instanceId = null,
CancellationToken cancellationToken = default)
=> DurableWorkflow.RunAsync(workflow, input, this._client, instanceId, cancellationToken);
/// <summary>
/// Starts a workflow as a durable orchestration and returns a streaming handle to watch events.
/// </summary>
/// <typeparam name="TInput">The type of the input to the workflow.</typeparam>
/// <param name="workflow">The workflow to execute.</param>
/// <param name="input">The input to pass to the workflow's starting executor.</param>
/// <param name="instanceId">Optional instance ID for the orchestration. If not provided, a new ID will be generated.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>An <see cref="IStreamingRun"/> that can be used to stream workflow events.</returns>
/// <exception cref="ArgumentNullException">Thrown when workflow is null.</exception>
/// <exception cref="ArgumentException">Thrown when the workflow does not have a valid name.</exception>
public ValueTask<IStreamingRun> StreamAsync<TInput>(
Workflow workflow,
TInput input,
string? instanceId = null,
CancellationToken cancellationToken = default)
where TInput : notnull
=> DurableWorkflow.StreamAsync(workflow, input, this._client, instanceId, cancellationToken);
/// <summary>
/// Starts a workflow as a durable orchestration with string input and returns a streaming handle.
/// </summary>
/// <param name="workflow">The workflow to execute.</param>
/// <param name="input">The string input to pass to the workflow.</param>
/// <param name="instanceId">Optional instance ID for the orchestration.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>An <see cref="IStreamingRun"/> that can be used to stream workflow events.</returns>
public ValueTask<IStreamingRun> StreamAsync(
Workflow workflow,
string input,
string? instanceId = null,
CancellationToken cancellationToken = default)
=> DurableWorkflow.StreamAsync(workflow, input, this._client, instanceId, cancellationToken);
/// <summary>
/// Attaches to an existing workflow orchestration instance.
/// </summary>
/// <param name="instanceId">The instance ID of the orchestration to attach to.</param>
/// <param name="workflowName">The name of the workflow being executed.</param>
/// <returns>An <see cref="IRun"/> that can be used to monitor the workflow execution.</returns>
public IRun Attach(string instanceId, string workflowName)
=> DurableWorkflow.Attach(instanceId, workflowName, this._client);
/// <summary>
/// Attaches to an existing workflow orchestration instance for streaming.
/// </summary>
/// <param name="instanceId">The instance ID of the orchestration to attach to.</param>
/// <param name="workflow">The workflow being executed.</param>
/// <returns>An <see cref="IStreamingRun"/> that can be used to stream workflow events.</returns>
public IStreamingRun AttachStream(string instanceId, Workflow workflow)
=> DurableWorkflow.AttachStream(instanceId, workflow, this._client);
}
@@ -14,7 +14,7 @@ namespace Microsoft.Agents.AI.DurableTask;
/// This class provides a similar API to <see cref="Run"/> but for workflows executed as durable orchestrations.
/// Events are received by raising external events to the orchestration and can be streamed to the caller.
/// </remarks>
public sealed class DurableRun : IAsyncDisposable
public sealed class DurableRun : IRun
{
private readonly DurableTaskClient _client;
private readonly List<WorkflowEvent> _eventSink = [];
@@ -32,6 +32,9 @@ public sealed class DurableRun : IAsyncDisposable
/// </summary>
public string InstanceId { get; }
/// <inheritdoc/>
public string RunId => this.InstanceId;
/// <summary>
/// Gets the name of the workflow being executed.
/// </summary>
@@ -16,7 +16,7 @@ namespace Microsoft.Agents.AI.DurableTask;
/// Events are detected by monitoring the orchestration status for <see cref="RequestPort"/> executors that are waiting
/// for external input (human-in-the-loop scenarios).
/// </remarks>
public sealed class DurableStreamingRun : IAsyncDisposable
public sealed class DurableStreamingRun : IStreamingRun
{
private readonly DurableTaskClient _client;
private readonly Workflow _workflow;
@@ -37,6 +37,9 @@ public sealed class DurableStreamingRun : IAsyncDisposable
/// </summary>
public string InstanceId { get; }
/// <inheritdoc/>
public string RunId => this.InstanceId;
/// <summary>
/// Gets the name of the workflow being executed.
/// </summary>
@@ -76,6 +79,10 @@ public sealed class DurableStreamingRun : IAsyncDisposable
};
}
/// <inheritdoc/>
public IAsyncEnumerable<WorkflowEvent> WatchStreamAsync(CancellationToken cancellationToken = default)
=> this.WatchStreamAsync(pollingInterval: null, cancellationToken);
/// <summary>
/// Asynchronously streams workflow events as they occur during workflow execution.
/// </summary>
@@ -94,8 +101,8 @@ public sealed class DurableStreamingRun : IAsyncDisposable
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>An asynchronous stream of <see cref="WorkflowEvent"/> objects.</returns>
public async IAsyncEnumerable<WorkflowEvent> WatchStreamAsync(
TimeSpan? pollingInterval = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
TimeSpan? pollingInterval,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
TimeSpan interval = pollingInterval ?? TimeSpan.FromMilliseconds(500);
@@ -350,6 +357,10 @@ public sealed class DurableStreamingRun : IAsyncDisposable
cancellation: cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc/>
public ValueTask SendResponseAsync(ExternalResponse response, CancellationToken cancellationToken = default)
=> this.SendExternalEventAsync("ExternalResponse", response, cancellationToken);
/// <summary>
/// Sends a response to a pending request in the workflow.
/// </summary>
@@ -19,10 +19,10 @@ public static class DurableWorkflow
/// <param name="client">The durable task client for orchestration operations.</param>
/// <param name="instanceId">Optional instance ID for the orchestration. If not provided, a new ID will be generated.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>A <see cref="DurableRun"/> that can be used to monitor the workflow execution.</returns>
/// <returns>An <see cref="IRun"/> that can be used to monitor the workflow execution.</returns>
/// <exception cref="ArgumentNullException">Thrown when workflow or client is null.</exception>
/// <exception cref="ArgumentException">Thrown when the workflow does not have a valid name.</exception>
public static async ValueTask<DurableRun> RunAsync<TInput>(
public static async ValueTask<IRun> RunAsync<TInput>(
Workflow workflow,
TInput input,
DurableTaskClient client,
@@ -55,8 +55,8 @@ public static class DurableWorkflow
/// <param name="client">The durable task client for orchestration operations.</param>
/// <param name="instanceId">Optional instance ID for the orchestration.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>A <see cref="DurableRun"/> that can be used to monitor the workflow execution.</returns>
public static ValueTask<DurableRun> RunAsync(
/// <returns>An <see cref="IRun"/> that can be used to monitor the workflow execution.</returns>
public static ValueTask<IRun> RunAsync(
Workflow workflow,
string input,
DurableTaskClient client,
@@ -73,10 +73,10 @@ public static class DurableWorkflow
/// <param name="client">The durable task client for orchestration operations.</param>
/// <param name="instanceId">Optional instance ID for the orchestration. If not provided, a new ID will be generated.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>A <see cref="DurableStreamingRun"/> that can be used to stream workflow events.</returns>
/// <returns>An <see cref="IStreamingRun"/> that can be used to stream workflow events.</returns>
/// <exception cref="ArgumentNullException">Thrown when workflow or client is null.</exception>
/// <exception cref="ArgumentException">Thrown when the workflow does not have a valid name.</exception>
public static async ValueTask<DurableStreamingRun> StreamAsync<TInput>(
public static async ValueTask<IStreamingRun> StreamAsync<TInput>(
Workflow workflow,
TInput input,
DurableTaskClient client,
@@ -109,8 +109,8 @@ public static class DurableWorkflow
/// <param name="client">The durable task client for orchestration operations.</param>
/// <param name="instanceId">Optional instance ID for the orchestration.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>A <see cref="DurableStreamingRun"/> that can be used to stream workflow events.</returns>
public static ValueTask<DurableStreamingRun> StreamAsync(
/// <returns>An <see cref="IStreamingRun"/> that can be used to stream workflow events.</returns>
public static ValueTask<IStreamingRun> StreamAsync(
Workflow workflow,
string input,
DurableTaskClient client,
@@ -124,8 +124,8 @@ public static class DurableWorkflow
/// <param name="instanceId">The instance ID of the orchestration to attach to.</param>
/// <param name="workflowName">The name of the workflow being executed.</param>
/// <param name="client">The durable task client for orchestration operations.</param>
/// <returns>A <see cref="DurableRun"/> that can be used to monitor the workflow execution.</returns>
public static DurableRun Attach(
/// <returns>An <see cref="IRun"/> that can be used to monitor the workflow execution.</returns>
public static IRun Attach(
string instanceId,
string workflowName,
DurableTaskClient client)
@@ -143,8 +143,8 @@ public static class DurableWorkflow
/// <param name="instanceId">The instance ID of the orchestration to attach to.</param>
/// <param name="workflow">The workflow being executed.</param>
/// <param name="client">The durable task client for orchestration operations.</param>
/// <returns>A <see cref="DurableStreamingRun"/> that can be used to stream workflow events.</returns>
public static DurableStreamingRun AttachStream(
/// <returns>An <see cref="IStreamingRun"/> that can be used to stream workflow events.</returns>
public static IStreamingRun AttachStream(
string instanceId,
Workflow workflow,
DurableTaskClient client)
@@ -113,6 +113,9 @@ public static class DurableWorkflowServiceCollectionExtensions
services.AddDurableTaskClient(clientBuilder);
}
// Register the DurableExecutionEnvironment for DI-friendly workflow execution
services.TryAddSingleton<DurableExecutionEnvironment>();
return services;
}
@@ -0,0 +1,53 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Represents a workflow run that tracks execution status and emitted workflow events,
/// supporting resumption with responses to external requests.
/// </summary>
/// <remarks>
/// This interface defines the common contract for workflow runs across different execution
/// environments (in-process, durable, etc.). Implementations provide the mechanism to
/// interact with running workflows, send responses, and access emitted events.
/// </remarks>
public interface IRun : IAsyncDisposable
{
/// <summary>
/// Gets the unique identifier for the run.
/// </summary>
/// <remarks>
/// This identifier can be provided at the start of the run, or auto-generated.
/// For durable runs, this corresponds to the orchestration instance ID.
/// </remarks>
string RunId { get; }
/// <summary>
/// Gets all events that have been emitted by the workflow.
/// </summary>
IEnumerable<WorkflowEvent> OutgoingEvents { get; }
/// <summary>
/// Gets the number of events emitted since the last access to <see cref="NewEvents"/>.
/// </summary>
int NewEventCount { get; }
/// <summary>
/// Gets all events emitted by the workflow since the last access to this property.
/// </summary>
/// <remarks>
/// Each access to this property advances the bookmark, so subsequent accesses
/// will only return events emitted after the previous access.
/// </remarks>
IEnumerable<WorkflowEvent> NewEvents { get; }
/// <summary>
/// Sends an external response to the workflow.
/// </summary>
/// <param name="response">The external response to send.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
ValueTask SendResponseAsync(ExternalResponse response, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,52 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Represents a workflow run that supports streaming workflow events as they occur,
/// providing a mechanism to send responses back to the workflow.
/// </summary>
/// <remarks>
/// This interface defines the common contract for streaming workflow runs across different
/// execution environments (in-process, durable, etc.). Implementations provide real-time
/// access to workflow events and the ability to respond to external requests.
/// </remarks>
public interface IStreamingRun : IAsyncDisposable
{
/// <summary>
/// Gets the unique identifier for the run.
/// </summary>
/// <remarks>
/// This identifier can be provided at the start of the run, or auto-generated.
/// For durable runs, this corresponds to the orchestration instance ID.
/// </remarks>
string RunId { get; }
/// <summary>
/// Asynchronously streams workflow events as they occur during workflow execution.
/// </summary>
/// <remarks>
/// This method yields <see cref="WorkflowEvent"/> instances in real time as the workflow
/// progresses. The stream completes when the workflow completes, fails, or is terminated.
/// Events are delivered in the order they are raised.
/// </remarks>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken"/> that can be used to cancel the streaming operation.
/// If cancellation is requested, the stream will end and no further events will be yielded.
/// </param>
/// <returns>
/// An asynchronous stream of <see cref="WorkflowEvent"/> objects representing significant
/// workflow state changes.
/// </returns>
IAsyncEnumerable<WorkflowEvent> WatchStreamAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Sends an external response to the workflow.
/// </summary>
/// <param name="response">The external response to send.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
ValueTask SendResponseAsync(ExternalResponse response, CancellationToken cancellationToken = default);
}