[BREAKING] .NET: Decouple Checkpointing from Run/StreamAsync APIs (#4037)

* [BREAKING] refactor: Decouple Checkpointing and Execution APIs

With this change, Checkpointing becomes an property of an IWorkflowExecutionEnvironment. This lets environments that are tightly-coupled to their CheckpointManager avoid needing to present APIs that would not work (e.g. taking in an InMemory CheckpointManager for Durable Tasks, for example)

* refactor: Normalize IsCheckpointingEnabled naming
This commit is contained in:
Jacob Alber
2026-02-19 16:41:35 +00:00
committed by GitHub
parent fd4e6e816c
commit c73bd87503
34 changed files with 299 additions and 317 deletions
@@ -6,12 +6,13 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Agents.AI.Workflows.InProc;
namespace Microsoft.Agents.AI.Workflows.Sample;
internal static class Step5EntryPoint
{
public static async ValueTask<string> RunAsync(TextWriter writer, Func<string, int> userGuessCallback, IWorkflowExecutionEnvironment environment, bool rehydrateToRestore = false, CheckpointManager? checkpointManager = null)
public static async ValueTask<string> RunAsync(TextWriter writer, Func<string, int> userGuessCallback, InProcessExecutionEnvironment environment, bool rehydrateToRestore = false, CheckpointManager? checkpointManager = null)
{
Dictionary<CheckpointInfo, (NumberSignal signal, string? prompt)> checkpointedOutputs = [];
@@ -22,14 +23,14 @@ internal static class Step5EntryPoint
Workflow workflow = Step4EntryPoint.CreateWorkflowInstance(out JudgeExecutor judge);
Checkpointed<StreamingRun> checkpointed =
await environment.StreamAsync(workflow, NumberSignal.Init, checkpointManager)
StreamingRun handle =
await environment.WithCheckpointing(checkpointManager)
.StreamAsync(workflow, NumberSignal.Init)
.ConfigureAwait(false);
List<CheckpointInfo> checkpoints = [];
CancellationTokenSource cancellationSource = new();
StreamingRun handle = checkpointed.Run;
string? result = await RunStreamToHaltOrMaxStepAsync(maxStep: 6).ConfigureAwait(false);
result.Should().BeNull();
@@ -42,13 +43,13 @@ internal static class Step5EntryPoint
{
await handle.DisposeAsync().ConfigureAwait(false);
checkpointed = await environment.ResumeStreamAsync(workflow, targetCheckpoint, checkpointManager, cancellationToken: CancellationToken.None)
.ConfigureAwait(false);
handle = checkpointed.Run;
handle = await environment.WithCheckpointing(checkpointManager)
.ResumeStreamAsync(workflow, targetCheckpoint, CancellationToken.None)
.ConfigureAwait(false);
}
else
{
await checkpointed.RestoreCheckpointAsync(checkpoints[2], CancellationToken.None).ConfigureAwait(false);
await handle.RestoreCheckpointAsync(checkpoints[2], CancellationToken.None).ConfigureAwait(false);
}
(signal, prompt) = checkpointedOutputs[targetCheckpoint];
@@ -48,10 +48,9 @@ internal static class Step13EntryPoint
return session;
}
public static async ValueTask<CheckpointInfo> RunAsync(TextWriter writer, string input, IWorkflowExecutionEnvironment environment, CheckpointManager checkpointManager, CheckpointInfo? resumeFrom)
public static async ValueTask<CheckpointInfo> RunAsync(TextWriter writer, string input, IWorkflowExecutionEnvironment environment, CheckpointInfo? resumeFrom)
{
await using Checkpointed<StreamingRun> checkpointed = await BeginAsync();
StreamingRun run = checkpointed.Run;
await using StreamingRun run = await BeginAsync();
await run.TrySendMessageAsync(new TurnToken());
@@ -80,16 +79,16 @@ internal static class Step13EntryPoint
return lastCheckpoint!;
async ValueTask<Checkpointed<StreamingRun>> BeginAsync()
async ValueTask<StreamingRun> BeginAsync()
{
if (resumeFrom == null)
{
return await environment.StreamAsync(WorkflowInstance, input, checkpointManager);
return await environment.StreamAsync(WorkflowInstance, input);
}
Checkpointed<StreamingRun> checkpointed = await environment.ResumeStreamAsync(WorkflowInstance, resumeFrom, checkpointManager);
await checkpointed.Run.TrySendMessageAsync(input);
return checkpointed;
StreamingRun run = await environment.ResumeStreamAsync(WorkflowInstance, resumeFrom);
await run.TrySendMessageAsync(input);
return run;
}
}
}