diff --git a/dotnet/src/Microsoft.Agents.Workflows/CheckpointInfo.cs b/dotnet/src/Microsoft.Agents.Workflows/CheckpointInfo.cs
new file mode 100644
index 0000000000..5a6f31b2ad
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.Workflows/CheckpointInfo.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+
+namespace Microsoft.Agents.Workflows;
+
+///
+/// Represents a checkpoint with a unique identifier and a timestamp indicating when it was created.
+///
+public class CheckpointInfo : IEquatable
+{
+ ///
+ /// The unique identifier for the checkpoint.
+ ///
+ public string CheckpointId { get; } = Guid.NewGuid().ToString("N");
+
+ ///
+ /// The date and time when the object was created, in Coordinated Universal Time (UTC).
+ ///
+ public DateTimeOffset CreatedAt { get; } = DateTimeOffset.UtcNow;
+
+ ///
+ public bool Equals(CheckpointInfo? other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ return this.CheckpointId == other.CheckpointId &&
+ this.CreatedAt == other.CreatedAt;
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return this.Equals(obj as CheckpointInfo);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(this.CheckpointId, this.CreatedAt);
+ }
+
+ ///
+ public override string ToString() => $"CheckpointId: {this.CheckpointId}, CreatedAt: {this.CreatedAt:O}";
+}
diff --git a/dotnet/src/Microsoft.Agents.Workflows/CheckpointManager.cs b/dotnet/src/Microsoft.Agents.Workflows/CheckpointManager.cs
new file mode 100644
index 0000000000..c1caeab66a
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.Workflows/CheckpointManager.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.Agents.Workflows.Checkpointing;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.Workflows;
+
+///
+/// An in-memory implementation of that stores checkpoints in a dictionary.
+///
+public sealed class CheckpointManager : ICheckpointManager
+{
+ private readonly Dictionary _checkpoints = new();
+
+ ValueTask ICheckpointManager.CommitCheckpointAsync(Checkpoint checkpoint)
+ {
+ Throw.IfNull(checkpoint);
+
+ this._checkpoints[checkpoint] = checkpoint;
+ return new(checkpoint);
+ }
+
+ ValueTask ICheckpointManager.LookupCheckpointAsync(CheckpointInfo checkpointInfo)
+ {
+ Throw.IfNull(checkpointInfo);
+
+ if (!this._checkpoints.TryGetValue(checkpointInfo, out Checkpoint? checkpoint))
+ {
+ throw new KeyNotFoundException($"Checkpoint not found: {checkpointInfo}");
+ }
+
+ return new ValueTask(checkpoint);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.Workflows/Checkpointed.cs b/dotnet/src/Microsoft.Agents.Workflows/Checkpointed.cs
new file mode 100644
index 0000000000..ec9499b56c
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.Workflows/Checkpointed.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Agents.Workflows.Checkpointing;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.Workflows;
+
+///
+/// Represents a workflow run that supports checkpointing.
+///
+/// The type of the underlying workflow run handle
+///
+///
+///
+///
+public class Checkpointed
+{
+ internal Checkpointed(TRun run, ICheckpointingRunner runner)
+ {
+ this.Run = Throw.IfNull(run);
+ this._runner = Throw.IfNull(runner);
+ }
+
+ private readonly ICheckpointingRunner _runner;
+
+ ///
+ /// Gets the workflow run associated with this instance.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public TRun Run { get; }
+
+ ///
+ public IReadOnlyList Checkpoints => this._runner.Checkpoints;
+
+ ///
+ /// Gets the most recent checkpoint information.
+ ///
+ public CheckpointInfo? LastCheckpoint => this.Checkpoints.Count > 0 ? this.Checkpoints[this.Checkpoints.Count - 1] : null;
+
+ ///
+ public ValueTask RestoreCheckpointAsync(CheckpointInfo checkpointInfo, CancellationToken cancellation = default)
+ => this._runner.RestoreCheckpointAsync(checkpointInfo, cancellation);
+}
diff --git a/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/Checkpoint.cs b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/Checkpoint.cs
new file mode 100644
index 0000000000..7390c38255
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/Checkpoint.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using Microsoft.Agents.Workflows.Execution;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.Workflows.Checkpointing;
+
+internal class Checkpoint : CheckpointInfo
+{
+ internal Checkpoint(
+ int stepNumber,
+ WorkflowInfo workflow,
+ RunnerStateData runnerData,
+ Dictionary stateData,
+ Dictionary edgeStateData)
+ {
+ this.StepNumber = Throw.IfLessThan(stepNumber, -1); // -1 is a special flag indicating the initial checkpoint.
+ this.Workflow = Throw.IfNull(workflow);
+ this.RunnerData = Throw.IfNull(runnerData);
+ this.State = Throw.IfNull(stateData);
+ this.EdgeState = Throw.IfNull(edgeStateData);
+ }
+
+ public bool IsInitial => this.StepNumber == -1;
+
+ public int StepNumber { get; }
+ public WorkflowInfo Workflow { get; }
+ public RunnerStateData RunnerData { get; }
+
+ public readonly Dictionary State = new();
+ public readonly Dictionary EdgeState = new();
+}
diff --git a/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/DirectEdgeInfo.cs b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/DirectEdgeInfo.cs
new file mode 100644
index 0000000000..1bcd6c3e7f
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/DirectEdgeInfo.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace Microsoft.Agents.Workflows.Checkpointing;
+
+internal class DirectEdgeInfo(DirectEdgeData data) : EdgeInfo(Edge.Type.Direct, data.Connection)
+{
+ public bool HasCondition => data.Condition != null;
+
+ protected override bool IsMatchInternal(EdgeData edgeData)
+ {
+ return edgeData is DirectEdgeData directEdge
+ && this.HasCondition == (directEdge.Condition != null);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/EdgeInfo.cs b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/EdgeInfo.cs
new file mode 100644
index 0000000000..c9aa4dd50a
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/EdgeInfo.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Agents.Workflows.Execution;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.Workflows.Checkpointing;
+
+internal abstract class EdgeInfo(Edge.Type edgeType, EdgeConnection connection)
+{
+ public Edge.Type EdgeType => edgeType;
+ public EdgeConnection Connection { get; } = Throw.IfNull(connection);
+
+ public bool IsMatch(Edge edge)
+ {
+ return this.EdgeType == edge.EdgeType
+ && this.Connection.Equals(edge.Data.Connection)
+ && this.IsMatchInternal(edge.Data);
+ }
+
+ protected virtual bool IsMatchInternal(EdgeData edgeData) => true;
+}
diff --git a/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/ExecutorInfo.cs b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/ExecutorInfo.cs
new file mode 100644
index 0000000000..546cb824af
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/ExecutorInfo.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace Microsoft.Agents.Workflows.Checkpointing;
+
+internal record class ExecutorInfo(TypeId ExecutorType, string ExecutorId)
+{
+ public bool IsMatch() where T : Executor
+ {
+ return this.ExecutorType.IsMatch()
+ && this.ExecutorId == typeof(T).Name;
+ }
+
+ public bool IsMatch(Executor executor)
+ {
+ return this.ExecutorType.IsMatch(executor.GetType())
+ && this.ExecutorId == executor.Id;
+ }
+
+ public bool IsMatch(ExecutorRegistration registration)
+ {
+ return this.ExecutorType.IsMatch(registration.ExecutorType)
+ && this.ExecutorId == registration.Id;
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/ExportedState.cs b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/ExportedState.cs
new file mode 100644
index 0000000000..fcdbf8bdc2
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/ExportedState.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.Workflows.Checkpointing;
+
+internal class ExportedState(object state)
+{
+ public Type RuntimeType => Throw.IfNull(state).GetType();
+ public object Value => Throw.IfNull(state);
+}
diff --git a/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/FanInEdgeInfo.cs b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/FanInEdgeInfo.cs
new file mode 100644
index 0000000000..8ea914bb3b
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/FanInEdgeInfo.cs
@@ -0,0 +1,5 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace Microsoft.Agents.Workflows.Checkpointing;
+
+internal class FanInEdgeInfo(FanInEdgeData data) : EdgeInfo(Edge.Type.FanIn, data.Connection);
diff --git a/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/FanOutEdgeInfo.cs b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/FanOutEdgeInfo.cs
new file mode 100644
index 0000000000..7e4aa65502
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/FanOutEdgeInfo.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace Microsoft.Agents.Workflows.Checkpointing;
+
+internal class FanOutEdgeInfo(FanOutEdgeData data) : EdgeInfo(Edge.Type.FanOut, data.Connection)
+{
+ public bool HasAssigner => data.EdgeAssigner != null;
+
+ protected override bool IsMatchInternal(EdgeData edgeData)
+ {
+ return edgeData is FanOutEdgeData fanOutEdge
+ && this.HasAssigner == (fanOutEdge.EdgeAssigner != null);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/ICheckpointManager.cs b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/ICheckpointManager.cs
new file mode 100644
index 0000000000..be93c391ea
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/ICheckpointManager.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Microsoft.Agents.Workflows.Checkpointing;
+
+///
+/// A manager for storing and retrieving workflow execution checkpoints.
+///
+internal interface ICheckpointManager
+{
+ ///
+ /// Commits the specified checkpoint and returns information that can be used to retrieve it later.
+ ///
+ /// The to be committed.
+ /// A representing the incoming checkpoint.
+ ValueTask CommitCheckpointAsync(Checkpoint checkpoint);
+
+ ///
+ /// Retrieves the checkpoint associated with the specified checkpoint information.
+ ///
+ /// The information used to identify the checkpoint.
+ /// A representing the asynchronous operation. The result contains the associated with the specified .
+ /// Thrown if the checkpoint is not found.
+ ValueTask LookupCheckpointAsync(CheckpointInfo checkpointInfo);
+}
diff --git a/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/ICheckpointingRunner.cs b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/ICheckpointingRunner.cs
new file mode 100644
index 0000000000..fe29b9507f
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/ICheckpointingRunner.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Agents.Workflows.Checkpointing;
+
+internal interface ICheckpointingRunner
+{
+ // TODO: Convert this to a multi-timeline (e.g.: Live timeline + forks for orphaned checkpoints due to timetravel)
+ IReadOnlyList Checkpoints { get; }
+
+ ValueTask RestoreCheckpointAsync(CheckpointInfo checkpointInfo, CancellationToken cancellation = default);
+}
diff --git a/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/InputPortInfo.cs b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/InputPortInfo.cs
new file mode 100644
index 0000000000..abe2dddc91
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/InputPortInfo.cs
@@ -0,0 +1,5 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace Microsoft.Agents.Workflows.Checkpointing;
+
+internal record class InputPortInfo(TypeId InputType, TypeId OutputType, string PortId);
diff --git a/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/RepresentationExtensions.cs b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/RepresentationExtensions.cs
new file mode 100644
index 0000000000..96e3822d30
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/RepresentationExtensions.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.Workflows.Checkpointing;
+
+internal static class RepresentationExtensions
+{
+ public static ExecutorInfo ToExecutorInfo(this ExecutorRegistration registration)
+ {
+ Throw.IfNull(registration);
+ return new ExecutorInfo(new TypeId(registration.ExecutorType), registration.Id);
+ }
+
+ public static EdgeInfo ToEdgeInfo(this Edge edge)
+ {
+ Throw.IfNull(edge);
+ return edge.EdgeType switch
+ {
+ Edge.Type.Direct => new DirectEdgeInfo(edge.DirectEdgeData!),
+ Edge.Type.FanOut => new FanOutEdgeInfo(edge.FanOutEdgeData!),
+ Edge.Type.FanIn => new FanInEdgeInfo(edge.FanInEdgeData!),
+ _ => throw new NotSupportedException($"Unsupported edge type: {edge.EdgeType}")
+ };
+ }
+
+ public static InputPortInfo ToPortInfo(this InputPort port)
+ {
+ Throw.IfNull(port);
+ return new(new TypeId(port.Request), new TypeId(port.Response), port.Id);
+ }
+
+ private static WorkflowInfo ToWorkflowInfo(this Workflow workflow, TypeId? outputType, string? outputExecutorId)
+ {
+ Throw.IfNull(workflow);
+
+ Dictionary executors =
+ workflow.Registrations.Values.ToDictionary(
+ keySelector: registration => registration.Id,
+ elementSelector: ToExecutorInfo);
+
+ Dictionary> edges = workflow.Edges.Keys.ToDictionary(
+ keySelector: sourceId => sourceId,
+ elementSelector: sourceId => workflow.Edges[sourceId].Select(ToEdgeInfo).ToList());
+
+ HashSet inputPorts = new(workflow.Ports.Values.Select(ToPortInfo));
+
+ return new WorkflowInfo(executors, edges, inputPorts, new TypeId(workflow.InputType), workflow.StartExecutorId, outputType, outputExecutorId);
+ }
+
+ public static WorkflowInfo ToWorkflowInfo(this Workflow workflow)
+ => workflow.ToWorkflowInfo(outputType: null, outputExecutorId: null);
+
+ public static WorkflowInfo GetInfo(this Workflow workflow)
+ => workflow.ToWorkflowInfo(outputType: new TypeId(typeof(TResult)), outputExecutorId: workflow.OutputCollectorId);
+}
diff --git a/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/TypeId.cs b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/TypeId.cs
new file mode 100644
index 0000000000..077484442f
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/TypeId.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.Workflows.Checkpointing;
+
+internal class TypeId(Type type)
+{
+ public string AssemblyName => Throw.IfNull(type.Assembly.FullName);
+ public string TypeName => Throw.IfNull(type.FullName);
+
+ public bool IsMatch(Type type)
+ {
+ return this.AssemblyName == type.Assembly.FullName
+ && this.TypeName == type.FullName;
+ }
+
+ public bool IsMatch() => this.IsMatch(typeof(T));
+}
diff --git a/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/WorkflowInfo.cs b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/WorkflowInfo.cs
new file mode 100644
index 0000000000..e879b3f71a
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.Workflows/Checkpointing/WorkflowInfo.cs
@@ -0,0 +1,111 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.Workflows.Checkpointing;
+
+internal class WorkflowInfo
+{
+ internal WorkflowInfo(
+ Dictionary executors,
+ Dictionary> edges,
+ HashSet inputPorts,
+ TypeId inputType,
+ string startExecutorId,
+ TypeId? outputType = null,
+ string? outputCollectorId = null)
+ {
+ this.Executors = Throw.IfNull(executors);
+ this.Edges = Throw.IfNull(edges);
+ this.InputPorts = Throw.IfNull(inputPorts);
+
+ this.InputType = Throw.IfNull(inputType);
+ this.StartExecutorId = Throw.IfNullOrEmpty(startExecutorId);
+
+ if (outputType != null && outputCollectorId != null)
+ {
+ this.OutputType = outputType;
+ this.OutputCollectorId = outputCollectorId;
+ }
+ else if (outputCollectorId != null)
+ {
+ throw new InvalidOperationException(
+ $"Either both or none of OutputType and OutputCollectorId must be set. ({nameof(outputType)}: {outputType} vs. {nameof(outputCollectorId)}: {outputCollectorId})"
+ );
+ }
+ }
+
+ public Dictionary Executors { get; }
+ public Dictionary> Edges { get; }
+ public HashSet InputPorts { get; }
+
+ public TypeId InputType { get; }
+ public string StartExecutorId { get; }
+
+ public TypeId? OutputType { get; }
+ public string? OutputCollectorId { get; }
+
+ private bool IsMatch(Workflow workflow)
+ {
+ if (workflow is null)
+ {
+ return false;
+ }
+
+ if (!this.InputType.IsMatch(workflow.InputType))
+ {
+ return false;
+ }
+
+ if (this.StartExecutorId != workflow.StartExecutorId)
+ {
+ return false;
+ }
+
+ // Validate the executors
+ if (workflow.Registrations.Count != this.Executors.Count ||
+ this.Executors.Keys.Any(
+ executorId => workflow.Registrations.TryGetValue(executorId, out ExecutorRegistration? registration)
+ && !this.Executors[executorId].IsMatch(registration)))
+ {
+ return false;
+ }
+
+ // Validate the edges
+ if (workflow.Edges.Count != this.Edges.Count ||
+ this.Edges.Keys.Any(
+ sourceId =>
+ // If the sourceId is not present in the workflow edges, or
+ !workflow.Edges.TryGetValue(sourceId, out var edgeList) ||
+ // If the edge list count does not match, or
+ edgeList.Count != this.Edges[sourceId].Count ||
+ // If any edge in the workflow edge list does not match the corresponding edge in this.Edges[sourceId]
+ !edgeList.All(edge => this.Edges[sourceId].Any(e => e.IsMatch(edge)))
+ ))
+ {
+ return false;
+ }
+
+ // Validate the input ports
+ if (workflow.Ports.Count != this.InputPorts.Count ||
+ this.InputPorts.Any(portInfo =>
+ !workflow.Ports.TryGetValue(portInfo.PortId, out InputPort? port) ||
+ !portInfo.InputType.IsMatch(port.Request) ||
+ !portInfo.OutputType.IsMatch(port.Response)))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public bool IsMatch(Workflow workflow) => this.IsMatch(workflow as Workflow);
+
+ public bool IsMatch(Workflow workflow)
+ => this.IsMatch(workflow as Workflow)
+ && this.OutputType != null && this.OutputType.IsMatch(typeof(TResult))
+ && this.OutputCollectorId != null && this.OutputCollectorId == workflow.OutputCollectorId;
+}
diff --git a/dotnet/src/Microsoft.Agents.Workflows/DirectEdgeData.cs b/dotnet/src/Microsoft.Agents.Workflows/DirectEdgeData.cs
index 19cdd296a0..165a43390e 100644
--- a/dotnet/src/Microsoft.Agents.Workflows/DirectEdgeData.cs
+++ b/dotnet/src/Microsoft.Agents.Workflows/DirectEdgeData.cs
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
+using Microsoft.Agents.Workflows.Execution;
using PredicateT = System.Func