Commit cd84898
committed
[feat] Implement checkpointing mechanism for task execution
- Introduced CheckpointSaver interface and its implementations (InMemoryCheckpointSaver, TabularCheckpointSaver) for persisting execution state.
- Enhanced task runners (GraphAsTaskRunner, IteratorTaskRunner, TaskRunner, WhileTaskRunner) to support checkpointing, allowing for state recovery and iteration-level checkpoints.
- Updated TaskGraph to manage checkpointing configuration and resume execution from checkpoints.
- Added tests to validate checkpoint saving and retrieval functionality, ensuring robust handling of task execution states.
Plan:
---
name: Checkpointing & Retry
overview: Add execution checkpointing to @workglow/task-graph that saves graph state (task statuses, outputs, dataflow data) after each task completion, with configurable granularity. Build InMemory and Tabular checkpoint savers, add thread_id isolation, enable resume-from-checkpoint. On the builder side, wire checkpoint data into activities and build iteration time-travel UI (builder not a part)
todos:
- id: checkpoint-types
content: Create checkpoint data model types (CheckpointData, TaskCheckpointState, etc.) in packages/task-graph/src/checkpoint/
status: pending
- id: checkpoint-saver-interface
content: Create abstract CheckpointSaver class with save/get/getLatest/getHistory/delete methods
status: pending
- id: inmemory-saver
content: Implement InMemoryCheckpointSaver using Map with threadId index
status: pending
- id: tabular-saver
content: Implement TabularCheckpointSaver using existing ITabularStorage interface
status: pending
- id: runner-checkpoint-hooks
content: Add captureCheckpoint() to TaskGraphRunner, call after each task completion with configurable granularity
status: pending
- id: runner-restore
content: Add restoreFromCheckpoint() to TaskGraphRunner for resume-on-failure
status: pending
- id: config-extensions
content: Extend TaskGraphRunConfig, IRunConfig, and IExecuteContext with checkpointSaver/threadId/granularity
status: pending
- id: iteration-checkpoints
content: Add iteration checkpointing to WhileTask and IteratorTaskRunner after each subgraph run
status: pending
- id: checkpoint-events
content: Add 'checkpoint' event to TaskGraphEvents and emit from runner
status: pending
- id: exports
content: Export all checkpoint types/classes from common.ts and add checkpoint/index.ts
status: pending
- id: tests
content: Write tests for checkpoint save/restore, resume-from-failure, and iteration checkpoints
status: pending
isProject: false
---
# Checkpointing & Retry
## Architecture Overview
```mermaid
flowchart TD
subgraph taskGraph ["@workglow/task-graph"]
CheckpointSaver["CheckpointSaver (abstract)"]
InMemory["InMemoryCheckpointSaver"]
Tabular["TabularCheckpointSaver"]
CheckpointData["CheckpointData"]
TaskGraphRunner_CP["TaskGraphRunner (checkpoint hooks)"]
WhileTask_CP["WhileTask / IteratorTask (iteration checkpoints)"]
end
subgraph builder ["Builder (frontend)"]
ActivityRepo["ActivityRepository + checkpoint_id refs"]
ActivityViewer["ActivityViewer (per-task drill-in)"]
TimeTravelUI["Iteration Time Travel UI"]
end
TaskGraphRunner_CP -->|"after each task"| CheckpointSaver
WhileTask_CP -->|"after each iteration"| CheckpointSaver
CheckpointSaver --> InMemory
CheckpointSaver --> Tabular
ActivityRepo -->|"reads"| CheckpointSaver
ActivityViewer -->|"reads"| ActivityRepo
TimeTravelUI -->|"navigates"| ActivityViewer
```
## Part 1: Checkpoint Data Model & Saver Interface
**New directory:** `packages/task-graph/src/checkpoint/`
### 1a. Checkpoint Types (`CheckpointTypes.ts`)
Define the core data structures:
```typescript
export type CheckpointId = string;
export type ThreadId = string;
export interface TaskCheckpointState {
taskId: unknown;
taskType: string;
status: TaskStatus;
inputData: TaskInput;
outputData: TaskOutput;
progress: number;
error?: string;
startedAt?: string;
completedAt?: string;
}
export interface DataflowCheckpointState {
id: string;
sourceTaskId: unknown;
targetTaskId: unknown;
status: TaskStatus;
portData?: TaskOutput;
}
export interface CheckpointData {
checkpointId: CheckpointId;
threadId: ThreadId;
parentCheckpointId?: CheckpointId;
graphJson: TaskGraphJson; // structural definition
taskStates: TaskCheckpointState[]; // runtime state per task
dataflowStates: DataflowCheckpointState[];
metadata: {
createdAt: string;
triggerTaskId?: unknown; // task that just completed
iterationIndex?: number; // for while/map loops
iterationParentTaskId?: unknown; // which iterator task owns this
};
}
```
### 1b. CheckpointSaver Interface (`CheckpointSaver.ts`)
```typescript
export abstract class CheckpointSaver {
abstract saveCheckpoint(data: CheckpointData): Promise<void>;
abstract getCheckpoint(checkpointId: CheckpointId): Promise<CheckpointData | undefined>;
abstract getLatestCheckpoint(threadId: ThreadId): Promise<CheckpointData | undefined>;
abstract getCheckpointHistory(threadId: ThreadId): Promise<CheckpointData[]>;
abstract getCheckpointsForIteration(
threadId: ThreadId,
iterationParentTaskId: unknown
): Promise<CheckpointData[]>;
abstract deleteCheckpoints(threadId: ThreadId): Promise<void>;
}
```
Modeled after the existing `TaskOutputRepository` pattern with `EventEmitter` support and a service token (`CHECKPOINT_SAVER`).
### 1c. InMemoryCheckpointSaver (`InMemoryCheckpointSaver.ts`)
Simple `Map<CheckpointId, CheckpointData>` with a secondary index on `threadId`. Follows the same pattern as existing in-memory storage implementations.
### 1d. TabularCheckpointSaver (`TabularCheckpointSaver.ts`)
Uses the existing `ITabularStorage` interface (same as `TaskOutputTabularRepository`). Schema:
- Primary key: `checkpoint_id`
- Columns: `thread_id`, `parent_checkpoint_id`, `graph_json` (compressed JSON), `task_states` (compressed JSON), `dataflow_states` (compressed JSON), `metadata` (JSON), `created_at`
- Searchable by: `thread_id`
This automatically gives us SQLite, Postgres, IndexedDB, Supabase, and File-backed checkpoint storage via the existing tabular storage backends.
### 1e. Exports
Add all checkpoint exports to `[packages/task-graph/src/common.ts](packages/task-graph/src/common.ts)`:
```typescript
export * from "./checkpoint/CheckpointTypes";
export * from "./checkpoint/CheckpointSaver";
export * from "./checkpoint/InMemoryCheckpointSaver";
export * from "./checkpoint/TabularCheckpointSaver";
```
## Part 2: Integrate Checkpointing into Execution
### 2a. Add `CheckpointSaver` to `TaskGraphRunConfig`
In `[packages/task-graph/src/task-graph/TaskGraph.ts](packages/task-graph/src/task-graph/TaskGraph.ts)`, extend `TaskGraphRunConfig`:
```typescript
export interface TaskGraphRunConfig {
outputCache?: TaskOutputRepository | boolean;
parentSignal?: AbortSignal;
registry?: ServiceRegistry;
checkpointSaver?: CheckpointSaver; // NEW
threadId?: string; // NEW
resumeFromCheckpoint?: CheckpointId; // NEW
checkpointGranularity?: "every-task" | "top-level-only" | "none"; // NEW, default 'every-task'
}
```
### 2b. Checkpoint Hook in `TaskGraphRunner`
In `[packages/task-graph/src/task-graph/TaskGraphRunner.ts](packages/task-graph/src/task-graph/TaskGraphRunner.ts)`:
1. Store `checkpointSaver`, `threadId`, and `checkpointGranularity` as instance properties (set in `handleStart`).
2. Add a `captureCheckpoint(triggerTaskId)` method that snapshots the full graph state (iterating `graph.getTasks()` and `graph.getDataflows()` to build `TaskCheckpointState[]` and `DataflowCheckpointState[]`).
3. Call `captureCheckpoint` in `runGraph()` after each task completes (inside the `runAsync` function, after `pushOutputFromNodeToEdges` and `pushStatusFromNodeToEdges`), respecting `checkpointGranularity`.
4. Emit a new `checkpoint` event on the graph: `this.graph.emit("checkpoint", checkpointData)`.
### 2c. Resume from Checkpoint
Add a `restoreFromCheckpoint(checkpointData: CheckpointData)` method to `TaskGraphRunner` that:
1. For each task in `checkpointData.taskStates` with status `COMPLETED` or `DISABLED`, restore the task's `status`, `runOutputData`, `progress`, `error`.
2. For each dataflow, restore `portData` and `status`.
3. Configure the `DependencyBasedScheduler` to skip already-completed tasks by calling `onTaskCompleted` for each.
4. The subsequent `runGraph` call then only processes `PENDING` tasks.
In `TaskGraph.run()`, if `config.resumeFromCheckpoint` is provided, call `restoreFromCheckpoint` instead of `resetGraph` in `handleStart`.
### 2d. Iteration Checkpoints in `WhileTask` and `IteratorTaskRunner`
In `[packages/task-graph/src/task/WhileTask.ts](packages/task-graph/src/task/WhileTask.ts)` (line ~380, inside the while loop):
- After each iteration's `subGraph.run()` completes, if the execution context has a checkpoint saver, capture a checkpoint with `iterationIndex` and `iterationParentTaskId` metadata.
In `[packages/task-graph/src/task/IteratorTaskRunner.ts](packages/task-graph/src/task/IteratorTaskRunner.ts)` (inside `executeSubgraphIteration`):
- Same pattern: after each subgraph run, capture an iteration checkpoint.
This requires threading the `checkpointSaver` and `threadId` through the execution context (`IExecuteContext` or `IRunConfig`). The cleanest approach is to add optional `checkpointSaver` and `threadId` to the `IRunConfig` interface in `[packages/task-graph/src/task/ITask.ts](packages/task-graph/src/task/ITask.ts)`.
### 2e. Thread ID Concept
The `threadId` serves as the isolation key for checkpoint namespacing. When running a graph:
- If no `threadId` is provided, generate one via `uuid4()`.
- The `threadId` is stored on the runner and propagated to all child graph runs.
- Maps directly to `activity_id` in the builder.
## Part 3: Builder - Checkpoint Data in Activities
### 3a. Wire CheckpointSaver into `runWorkflow`
In `[builder/src/lib/run-workflow.ts](builder/src/lib/run-workflow.ts)`:
1. Create/get a `CheckpointSaver` (TabularCheckpointSaver backed by the same storage infrastructure used by `ActivityRepository`).
2. Pass it to `taskGraph.run()` via the config: `{ checkpointSaver, threadId: actId }`.
3. On failure, the checkpoint is already saved. The existing `activity_id` serves as the `threadId`.
4. Add a `resumeFromCheckpoint` option to `RunWorkflowOptions` that, when set, passes `resumeFromCheckpoint` to the graph config to skip completed tasks.
### 3b. Checkpoint Repository for the Builder
Create `builder/src/components/activities/CheckpointRepository.ts`:
- Wraps a `TabularCheckpointSaver` (or an `InMemoryCheckpointSaver` for browser-only mode).
- Provides queries: `getCheckpointsForActivity(activityId)`, `getIterationCheckpoints(activityId, taskId)`.
- Registered alongside `ActivityRepository` in the builder's storage setup.
### 3c. Activity Detail: Per-Task Run Data
Enhance `[builder/src/components/activities/ActivityViewer.tsx](builder/src/components/activities/ActivityViewer.tsx)`:
- Fetch checkpoints for the current activity using `CheckpointRepository`.
- Display a timeline of task completions derived from checkpoint `metadata.triggerTaskId` and `metadata.createdAt`.
- For each task in the graph, show its state (status, inputs, outputs, timing) by reading from the relevant checkpoint's `taskStates`.
- For iterative tasks (WhileTask, MapTask), show an expandable list of iteration checkpoints.
## File Summary
| Area | Files | Action |
| ---- | --------------------------------------------------------------- | ------------------------------------- |
| libs | `packages/task-graph/src/checkpoint/CheckpointTypes.ts` | New |
| libs | `packages/task-graph/src/checkpoint/CheckpointSaver.ts` | New |
| libs | `packages/task-graph/src/checkpoint/InMemoryCheckpointSaver.ts` | New |
| libs | `packages/task-graph/src/checkpoint/TabularCheckpointSaver.ts` | New |
| libs | `packages/task-graph/src/checkpoint/index.ts` | New |
| libs | `packages/task-graph/src/common.ts` | Modify (add checkpoint exports) |
| libs | `packages/task-graph/src/task-graph/TaskGraph.ts` | Modify (extend config) |
| libs | `packages/task-graph/src/task-graph/TaskGraphRunner.ts` | Modify (checkpoint hooks, restore) |
| libs | `packages/task-graph/src/task-graph/TaskGraphEvents.ts` | Modify (add checkpoint event) |
| libs | `packages/task-graph/src/task/ITask.ts` | Modify (add checkpoint to IRunConfig) |
| libs | `packages/task-graph/src/task/TaskRunner.ts` | Modify (propagate checkpoint config) |
| libs | `packages/task-graph/src/task/WhileTask.ts` | Modify (iteration checkpoints) |
| libs | `packages/task-graph/src/task/IteratorTaskRunner.ts` | Modify (iteration checkpoints) |
## Testing
Tests should be added in `packages/test/src/test/task/`:
- `Checkpoint.test.ts` - Test checkpoint save/restore cycle for a simple graph
- `CheckpointResume.test.ts` - Test resume from checkpoint after simulated failure
- `CheckpointIteration.test.ts` - Test iteration checkpoints for WhileTask and MapTask1 parent 5c0247f commit cd84898
16 files changed
Lines changed: 1020 additions & 4 deletions
File tree
- packages
- task-graph/src
- checkpoint
- task-graph
- task
- test/src/test/task
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
Lines changed: 61 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
Lines changed: 165 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
| 25 | + | |
| 26 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
| 9 | + | |
8 | 10 | | |
9 | 11 | | |
10 | 12 | | |
| |||
39 | 41 | | |
40 | 42 | | |
41 | 43 | | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
42 | 52 | | |
43 | 53 | | |
44 | 54 | | |
| |||
103 | 113 | | |
104 | 114 | | |
105 | 115 | | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
106 | 120 | | |
107 | 121 | | |
108 | 122 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| |||
19 | 20 | | |
20 | 21 | | |
21 | 22 | | |
| 23 | + | |
22 | 24 | | |
23 | 25 | | |
24 | 26 | | |
| |||
0 commit comments