Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
.NET: Add Redis checkpoint store implementation for workflows
Implement RedisCheckpointStore for distributed, durable checkpoint storage:
- Support connection string, ConfigurationOptions, and IConnectionMultiplexer
- TTL-based automatic checkpoint expiration
- Parent-child checkpoint relationships for workflow history tracking
- Extension methods for easy integration

Includes unit tests and sample application.
  • Loading branch information
ysya committed Dec 11, 2025
commit 3bcfedcf5265dbe3844f61b9a3fc4090e9709787
2 changes: 2 additions & 0 deletions dotnet/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="2.1.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Mcp" Version="1.0.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.7" />
<!-- Redis -->
<PackageVersion Include="StackExchange.Redis" Version="2.8.16" />
<!-- Test -->
<PackageVersion Include="FluentAssertions" Version="8.8.0" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Condition="'$(TargetFramework)' == 'net8.0'" Version="8.0.22" />
Expand Down
3 changes: 3 additions & 0 deletions dotnet/agent-framework-dotnet.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@
<Project Path="samples/GettingStarted/Workflows/Checkpoint/CheckpointAndRehydrate/CheckpointAndRehydrate.csproj" />
<Project Path="samples/GettingStarted/Workflows/Checkpoint/CheckpointAndResume/CheckpointAndResume.csproj" />
<Project Path="samples/GettingStarted/Workflows/Checkpoint/CheckpointWithHumanInTheLoop/CheckpointWithHumanInTheLoop.csproj" />
<Project Path="samples/GettingStarted/Workflows/Checkpoint/CheckpointWithRedis/CheckpointWithRedis.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/Workflows/HumanInTheLoop/">
<Project Path="samples/GettingStarted/Workflows/HumanInTheLoop/HumanInTheLoopBasic/HumanInTheLoopBasic.csproj" />
Expand Down Expand Up @@ -368,6 +369,7 @@
<Project Path="src/Microsoft.Agents.AI.Mem0/Microsoft.Agents.AI.Mem0.csproj" />
<Project Path="src/Microsoft.Agents.AI.OpenAI/Microsoft.Agents.AI.OpenAI.csproj" />
<Project Path="src/Microsoft.Agents.AI.Purview/Microsoft.Agents.AI.Purview.csproj" />
<Project Path="src/Microsoft.Agents.AI.Redis/Microsoft.Agents.AI.Redis.csproj" />
<Project Path="src/Microsoft.Agents.AI.Workflows.Declarative.AzureAI/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.csproj" />
<Project Path="src/Microsoft.Agents.AI.Workflows.Declarative/Microsoft.Agents.AI.Workflows.Declarative.csproj" />
<Project Path="src/Microsoft.Agents.AI.Workflows/Microsoft.Agents.AI.Workflows.csproj" />
Expand Down Expand Up @@ -408,6 +410,7 @@
<Project Path="tests/Microsoft.Agents.AI.Mem0.UnitTests/Microsoft.Agents.AI.Mem0.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.OpenAI.UnitTests/Microsoft.Agents.AI.OpenAI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Purview.UnitTests/Microsoft.Agents.AI.Purview.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Redis.UnitTests/Microsoft.Agents.AI.Redis.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.UnitTests/Microsoft.Agents.AI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Workflows.UnitTests/Microsoft.Agents.AI.Workflows.UnitTests.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
Comment thread
ysya marked this conversation as resolved.
Resolved
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);MEAI001</NoWarn>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Redis\Microsoft.Agents.AI.Redis.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Text.Json;
using Microsoft.Agents.AI.Workflows;

namespace CheckpointWithRedis;

/// <summary>
/// This sample demonstrates how to use Redis-backed checkpoint storage for workflows.
/// Key concepts:
/// - RedisCheckpointStore: A distributed, durable checkpoint store using Redis
/// - TTL (Time-To-Live): Automatic expiration of checkpoints
/// - Parent-child relationships: Linking checkpoints to track workflow history
/// </summary>
/// <remarks>
/// Pre-requisites:
/// - Redis must be running. Start it with: docker run --name redis -p 6379:6379 -d redis:7-alpine
/// - Or set REDIS_CONNECTION_STRING environment variable to your Redis instance.
/// </remarks>
public static class Program
{
public static async Task<int> Main()
{
// Configuration
var redisConnectionString = Environment.GetEnvironmentVariable("REDIS_CONNECTION_STRING") ?? "localhost:6379";
var ttl = TimeSpan.FromHours(24);
Comment thread
ysya marked this conversation as resolved.
Resolved

Console.WriteLine("=== Redis Checkpoint Storage Demo ===\n");
Console.WriteLine($"Connecting to Redis: {redisConnectionString}");

try
{
// Create checkpoint store with TTL
using var checkpointStore = RedisWorkflowExtensions.CreateRedisCheckpointStoreWithTtl(
redisConnectionString,
ttl,
keyPrefix: "workflow_checkpoints");

Console.WriteLine($"Key prefix: {checkpointStore.KeyPrefix}");
Console.WriteLine($"TTL: {checkpointStore.TimeToLive}\n");

// Sample workflow data
var runId = $"run_{Guid.NewGuid():N}";
var workflowState = new WorkflowState
{
CurrentStep = "initialize",
Variables = new Dictionary<string, object>
{
["user_input"] = "Hello, Agent!",
["timestamp"] = DateTimeOffset.UtcNow.ToString("o")
}
};
Comment thread
ysya marked this conversation as resolved.
Resolved

// Create initial checkpoint
Console.WriteLine("--- Creating Initial Checkpoint ---");
var initialCheckpoint = await checkpointStore.CreateCheckpointAsync(
runId,
JsonSerializer.SerializeToElement(workflowState));
Comment thread
ysya marked this conversation as resolved.
Resolved

Console.WriteLine($"Run ID: {runId}");
Console.WriteLine($"Checkpoint ID: {initialCheckpoint.CheckpointId}");
Console.WriteLine($"State: {workflowState.CurrentStep}\n");

// Simulate workflow progress
workflowState.CurrentStep = "processing";
workflowState.Variables["processed"] = true;
workflowState.Variables["processing_time"] = DateTimeOffset.UtcNow.ToString("o");

// Create child checkpoint (linked to parent)
Console.WriteLine("--- Creating Child Checkpoint ---");
var processingCheckpoint = await checkpointStore.CreateCheckpointAsync(
runId,
JsonSerializer.SerializeToElement(workflowState),
parent: initialCheckpoint);
Comment thread
ysya marked this conversation as resolved.
Resolved

Console.WriteLine($"Checkpoint ID: {processingCheckpoint.CheckpointId}");
Console.WriteLine($"Parent ID: {initialCheckpoint.CheckpointId}");
Console.WriteLine($"State: {workflowState.CurrentStep}\n");

// Simulate more progress
workflowState.CurrentStep = "completed";
workflowState.Variables["result"] = "Success!";
workflowState.Variables["completion_time"] = DateTimeOffset.UtcNow.ToString("o");

// Create final checkpoint
Console.WriteLine("--- Creating Final Checkpoint ---");
var finalCheckpoint = await checkpointStore.CreateCheckpointAsync(
runId,
JsonSerializer.SerializeToElement(workflowState),
parent: processingCheckpoint);
Comment thread
ysya marked this conversation as resolved.
Resolved

Console.WriteLine($"Checkpoint ID: {finalCheckpoint.CheckpointId}");
Console.WriteLine($"State: {workflowState.CurrentStep}\n");

// List all checkpoints for the run
Console.WriteLine("--- All Checkpoints for Run ---");
var allCheckpoints = await checkpointStore.RetrieveIndexAsync(runId);
Comment thread
ysya marked this conversation as resolved.
Resolved
var checkpointList = allCheckpoints.ToList();
Comment thread
ysya marked this conversation as resolved.
Resolved
Console.WriteLine($"Total checkpoints: {checkpointList.Count}");
foreach (var cp in checkpointList)
{
Console.WriteLine($" - {cp.CheckpointId}");
}

Console.WriteLine();

// List children of the initial checkpoint
Console.WriteLine("--- Children of Initial Checkpoint ---");
var children = await checkpointStore.RetrieveIndexAsync(runId, initialCheckpoint);
Comment thread
ysya marked this conversation as resolved.
Resolved
var childList = children.ToList();
Comment thread
ysya marked this conversation as resolved.
Resolved
Console.WriteLine($"Child checkpoints: {childList.Count}");
foreach (var child in childList)
{
Console.WriteLine($" - {child.CheckpointId}");
}

Console.WriteLine();

// Retrieve and display checkpoint data
Console.WriteLine("--- Retrieving Final Checkpoint Data ---");
var retrievedData = await checkpointStore.RetrieveCheckpointAsync(runId, finalCheckpoint);
Comment thread
ysya marked this conversation as resolved.
Resolved
Console.WriteLine($"Current step: {retrievedData.GetProperty("CurrentStep").GetString()}");

var variables = retrievedData.GetProperty("Variables");
Comment thread
ysya marked this conversation as resolved.
Resolved
Console.WriteLine($"User input: {variables.GetProperty("user_input").GetString()}");
Console.WriteLine($"Result: {variables.GetProperty("result").GetString()}");
Console.WriteLine($"Processed: {variables.GetProperty("processed").GetBoolean()}");

Console.WriteLine("\n=== Demo Complete ===");
}
catch (Exception ex)
{
Console.WriteLine($"\nError: {ex.Message}");
Console.WriteLine("\nMake sure Redis is running. You can start it with:");
Console.WriteLine(" docker run --name redis -p 6379:6379 -d redis:7-alpine");
return 1;
}

return 0;
}
}

/// <summary>
/// Sample workflow state class.
/// </summary>
public class WorkflowState
{
public string CurrentStep { get; set; } = string.Empty;
public Dictionary<string, object> Variables { get; set; } = new();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(TargetFrameworksCore)</TargetFrameworks>
<RootNamespace>Microsoft.Agents.AI</RootNamespace>
<NoWarn>$(NoWarn);MEAI001</NoWarn>
<VersionSuffix>preview</VersionSuffix>
</PropertyGroup>

<PropertyGroup>
<InjectSharedThrow>true</InjectSharedThrow>
<InjectDiagnosticClassesOnLegacy>true</InjectDiagnosticClassesOnLegacy>
<InjectTrimAttributesOnLegacy>true</InjectTrimAttributesOnLegacy>
<InjectIsExternalInitOnLegacy>true</InjectIsExternalInitOnLegacy>
<InjectRequiredMemberOnLegacy>true</InjectRequiredMemberOnLegacy>
<InjectCompilerFeatureRequiredOnLegacy>true</InjectCompilerFeatureRequiredOnLegacy>
</PropertyGroup>

<Import Project="$(RepoRoot)/dotnet/nuget/nuget-package.props" />

<PropertyGroup>
<!-- NuGet Package Settings -->
<Title>Microsoft Agent Framework Redis Integration</Title>
<Description>Provides Redis implementations for Microsoft Agent Framework storage abstractions including CheckpointStore.</Description>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
<ProjectReference Include="..\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="StackExchange.Redis" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="Microsoft.Agents.AI.Redis.UnitTests" />
</ItemGroup>

</Project>
Loading
Loading