baa-conductor

git clone 

commit
28e789b
parent
56e9a4f
author
im_wower
date
2026-03-21 21:55:54 +0800 CST
feat: scaffold checkpoint manager for worker runner
7 files changed,  +693, -57
A apps/worker-runner/src/checkpoints.ts
+150, -0
  1@@ -0,0 +1,150 @@
  2+import {
  3+  createCheckpointManager,
  4+  renderCheckpointFile,
  5+  type CheckpointManager,
  6+  type CheckpointRecord
  7+} from "@baa-conductor/checkpointing";
  8+import type {
  9+  LocalRunLogSession,
 10+  StreamChunkLogEntry,
 11+  WorkerLifecycleLogEntry
 12+} from "@baa-conductor/logging";
 13+import type { StepCheckpointConfig, StepCheckpointState, StepExecutionRequest } from "./contracts";
 14+
 15+const DEFAULT_LOG_TAIL_LINES = 20;
 16+
 17+type OrderedLogEntry =
 18+  | {
 19+      seq: number;
 20+      rendered: string;
 21+    }
 22+  | {
 23+      seq: number;
 24+      rendered: string;
 25+    };
 26+
 27+function mapWorkerEntry(entry: WorkerLifecycleLogEntry): OrderedLogEntry {
 28+  return {
 29+    seq: entry.seq,
 30+    rendered: `[worker] ${entry.renderedLine}`
 31+  };
 32+}
 33+
 34+function mapStreamEntry(channel: "stdout" | "stderr", entry: StreamChunkLogEntry): OrderedLogEntry {
 35+  return {
 36+    seq: entry.seq,
 37+    rendered: `[${channel}] ${entry.text}`
 38+  };
 39+}
 40+
 41+function createCheckpointState(manager: CheckpointManager, mode: StepCheckpointConfig["mode"]): StepCheckpointState {
 42+  const { state } = manager;
 43+  const latest = state.records.at(-1);
 44+
 45+  return {
 46+    ...state,
 47+    mode,
 48+    lastCheckpointSeq: state.lastSeq,
 49+    nextCheckpointSeq: state.nextSeq,
 50+    latest
 51+  };
 52+}
 53+
 54+function updateCheckpointState(runState: StepCheckpointState, manager: CheckpointManager): StepCheckpointState {
 55+  const nextState = createCheckpointState(manager, runState.mode);
 56+
 57+  Object.assign(runState, nextState);
 58+
 59+  return runState;
 60+}
 61+
 62+export function resolveCheckpointConfig(config?: StepCheckpointConfig): StepCheckpointConfig {
 63+  return config ?? {
 64+    mode: "capture"
 65+  };
 66+}
 67+
 68+export function createRunCheckpointManager(
 69+  request: StepExecutionRequest,
 70+  checkpointsDir: string
 71+): CheckpointManager {
 72+  const checkpointConfig = resolveCheckpointConfig(request.checkpoint);
 73+
 74+  return createCheckpointManager({
 75+    taskId: request.taskId,
 76+    stepId: request.stepId,
 77+    runId: request.runId,
 78+    directory: checkpointsDir,
 79+    resumeFromSeq: checkpointConfig.resumeFromSeq,
 80+    gitDiffBaseRef: checkpointConfig.gitDiffBaseRef,
 81+    includeGitDiffStat: checkpointConfig.includeGitDiffStat,
 82+    supportedTypes: checkpointConfig.mode === "disabled" ? [] : ["summary", "git_diff", "log_tail"],
 83+    note:
 84+      checkpointConfig.mode === "disabled"
 85+        ? "Checkpointing is disabled for this step request."
 86+        : "Checkpoint manager tracks summary/log_tail payloads and reserves git diff snapshot commands."
 87+  });
 88+}
 89+
 90+export function createPreparedCheckpointState(
 91+  manager: CheckpointManager,
 92+  config: StepCheckpointConfig
 93+): StepCheckpointState {
 94+  return createCheckpointState(manager, config.mode);
 95+}
 96+
 97+export function emitPreparationCheckpoint(
 98+  manager: CheckpointManager,
 99+  checkpoint: StepCheckpointState,
100+  request: StepExecutionRequest
101+): CheckpointRecord | undefined {
102+  if (checkpoint.mode === "disabled") {
103+    return undefined;
104+  }
105+
106+  const summary = request.checkpoint?.summaryHint ?? `Prepared local run layout for ${request.stepId}.`;
107+  const record = manager.createSummaryCheckpoint({
108+    summary,
109+    detail: `Worker runner reserved ${request.workerKind} checkpoint capture for ${request.stepName}.`
110+  });
111+
112+  updateCheckpointState(checkpoint, manager);
113+
114+  return record;
115+}
116+
117+export function emitLogTailCheckpoint(
118+  manager: CheckpointManager,
119+  checkpoint: StepCheckpointState,
120+  session: LocalRunLogSession,
121+  summary: string,
122+  lineLimit: number = DEFAULT_LOG_TAIL_LINES
123+): CheckpointRecord | undefined {
124+  if (checkpoint.mode === "disabled") {
125+    return undefined;
126+  }
127+
128+  const orderedEntries = [
129+    ...session.worker.entries.map(mapWorkerEntry),
130+    ...session.stdout.entries.map((entry) => mapStreamEntry("stdout", entry)),
131+    ...session.stderr.entries.map((entry) => mapStreamEntry("stderr", entry))
132+  ].sort((left, right) => left.seq - right.seq);
133+
134+  const lines = orderedEntries.slice(-lineLimit).map((entry) => entry.rendered);
135+  const record = manager.createLogTailCheckpoint({
136+    summary,
137+    source: "combined",
138+    lines,
139+    truncated: orderedEntries.length > lines.length
140+  });
141+
142+  updateCheckpointState(checkpoint, manager);
143+
144+  return record;
145+}
146+
147+export function describeRenderedCheckpoint(record: CheckpointRecord): string {
148+  const rendered = renderCheckpointFile(record);
149+
150+  return `${record.type}:${rendered.storagePath}`;
151+}
M apps/worker-runner/src/contracts.ts
+17, -5
 1@@ -1,3 +1,10 @@
 2+import type {
 3+  CheckpointManager,
 4+  CheckpointManagerState,
 5+  CheckpointRecord,
 6+  CheckpointType,
 7+  GitDiffSnapshotPlan
 8+} from "@baa-conductor/checkpointing";
 9 import type {
10   LocalRunLogSession,
11   LocalRunLogSummary,
12@@ -10,7 +17,7 @@ import type {
13 export type WorkerKind = "codex" | "shell" | "git";
14 export type StepKind = WorkerKind | "planner" | "review" | "finalize";
15 export type StepExecutionOutcome = "prepared" | "completed" | "failed" | "blocked";
16-export type CheckpointMode = "disabled" | "reserved_for_t006";
17+export type CheckpointMode = "disabled" | "capture";
18 export type StepArtifactKind = "log" | "metadata" | "state" | "directory" | "artifact";
19 
20 export interface StepExecutionRuntime {
21@@ -24,6 +31,9 @@ export interface StepCheckpointConfig {
22   mode: CheckpointMode;
23   resumeFromSeq?: number;
24   summaryHint?: string;
25+  gitDiffBaseRef?: string;
26+  includeGitDiffStat?: boolean;
27+  logTailLines?: number;
28 }
29 
30 export interface StepExecutionRequest {
31@@ -42,13 +52,14 @@ export interface StepExecutionRequest {
32   checkpoint?: StepCheckpointConfig;
33 }
34 
35-export interface StepCheckpointState {
36+export interface StepCheckpointState extends CheckpointManagerState {
37   mode: CheckpointMode;
38   lastCheckpointSeq: number;
39   nextCheckpointSeq: number;
40-  resumeFromSeq?: number;
41-  deferredFeatures: Array<"git_diff">;
42-  note: string;
43+  latest?: CheckpointRecord;
44+  supportedTypes: CheckpointType[];
45+  records: CheckpointRecord[];
46+  gitDiffPlan?: GitDiffSnapshotPlan;
47 }
48 
49 export interface StepArtifact {
50@@ -88,6 +99,7 @@ export interface PreparedStepRun {
51   metadata: RunMetadata;
52   state: RunStateSnapshot;
53   checkpoint: StepCheckpointState;
54+  checkpointManager: CheckpointManager;
55 }
56 
57 export interface StepExecutionMetrics {
M apps/worker-runner/src/index.ts
+1, -0
1@@ -1,2 +1,3 @@
2 export * from "./contracts";
3+export * from "./checkpoints";
4 export * from "./runner";
M apps/worker-runner/src/runner.ts
+71, -40
  1@@ -8,6 +8,7 @@ import {
  2   summarizeLocalRunLogSession,
  3   updateRunState,
  4   type LogLevel,
  5+  type StructuredData,
  6   type RunStatePatch,
  7   type RunStatus,
  8   type WorkerLifecycleEventType
  9@@ -15,38 +16,19 @@ import {
 10 import type {
 11   PreparedStepRun,
 12   StepArtifact,
 13-  StepCheckpointConfig,
 14-  StepCheckpointState,
 15   StepExecutionOutcome,
 16   StepExecutionRequest,
 17   StepExecutionResult,
 18   WorkerExecutionOutcome,
 19   WorkerExecutor
 20 } from "./contracts";
 21-
 22-const DEFERRED_CHECKPOINT_FEATURES: Array<"git_diff"> = ["git_diff"];
 23-
 24-function resolveCheckpointConfig(config?: StepCheckpointConfig): StepCheckpointConfig {
 25-  return config ?? {
 26-    mode: "reserved_for_t006"
 27-  };
 28-}
 29-
 30-function createCheckpointState(config: StepCheckpointConfig): StepCheckpointState {
 31-  const lastCheckpointSeq = config.resumeFromSeq ?? 0;
 32-
 33-  return {
 34-    mode: config.mode,
 35-    lastCheckpointSeq,
 36-    nextCheckpointSeq: lastCheckpointSeq + 1,
 37-    resumeFromSeq: config.resumeFromSeq,
 38-    deferredFeatures: [...DEFERRED_CHECKPOINT_FEATURES],
 39-    note:
 40-      config.mode === "reserved_for_t006"
 41-        ? "Checkpoint paths and sequence numbers are reserved for T-006. T-005 does not emit checkpoint content."
 42-        : "Checkpointing is disabled for this step request."
 43-  };
 44-}
 45+import {
 46+  createPreparedCheckpointState,
 47+  createRunCheckpointManager,
 48+  emitLogTailCheckpoint,
 49+  emitPreparationCheckpoint,
 50+  resolveCheckpointConfig
 51+} from "./checkpoints";
 52 
 53 function synchronizeRunState(run: PreparedStepRun, patch: RunStatePatch): void {
 54   run.state = updateRunState(run.state, {
 55@@ -92,7 +74,7 @@ function createDefaultArtifacts(run: PreparedStepRun): StepArtifact[] {
 56       name: "checkpoints",
 57       kind: "directory",
 58       path: run.logPaths.checkpointsDir,
 59-      description: "Reserved checkpoint directory for future T-006 persistence."
 60+      description: "Checkpoint directory layout for summary/log tail records and future git diff snapshots."
 61     },
 62     {
 63       name: "artifacts",
 64@@ -166,13 +148,15 @@ function resolveExitCode(execution: WorkerExecutionOutcome): number {
 65 export function prepareStepRun(request: StepExecutionRequest): PreparedStepRun {
 66   const startedAt = request.createdAt ?? new Date().toISOString();
 67   const checkpointConfig = resolveCheckpointConfig(request.checkpoint);
 68-  const checkpoint = createCheckpointState(checkpointConfig);
 69   const logPaths = createLocalRunPaths({
 70     repoRoot: request.runtime.repoRoot,
 71     taskId: request.taskId,
 72     runId: request.runId,
 73     runsRootDir: request.runtime.runsRootDir
 74   });
 75+  const checkpointManager = createRunCheckpointManager(request, logPaths.checkpointsDir);
 76+  const checkpoint = createPreparedCheckpointState(checkpointManager, checkpointConfig);
 77+  const preparationCheckpoint = emitPreparationCheckpoint(checkpointManager, checkpoint, request);
 78   const logSession = createLocalRunLogSession(logPaths, {
 79     taskId: request.taskId,
 80     stepId: request.stepId,
 81@@ -200,9 +184,10 @@ export function prepareStepRun(request: StepExecutionRequest): PreparedStepRun {
 82       attempt: request.attempt,
 83       startedAt,
 84       checkpointSeq: checkpoint.lastCheckpointSeq,
 85-      summary: `Prepared local run layout for ${request.stepId}.`
 86+      summary: preparationCheckpoint?.summary ?? `Prepared local run layout for ${request.stepId}.`
 87     }),
 88-    checkpoint
 89+    checkpoint,
 90+    checkpointManager
 91   };
 92 
 93   recordLifecycleEvent(run.logSession, {
 94@@ -221,16 +206,54 @@ export function prepareStepRun(request: StepExecutionRequest): PreparedStepRun {
 95     }
 96   });
 97 
 98-  if (checkpoint.mode === "reserved_for_t006") {
 99+  if (checkpoint.mode === "capture") {
100+    const checkpointEventData: StructuredData = {
101+      checkpointsDir: run.logPaths.checkpointsDir,
102+      nextCheckpointSeq: checkpoint.nextCheckpointSeq,
103+      supportedTypes: checkpoint.supportedTypes
104+    };
105+
106+    if (preparationCheckpoint !== undefined) {
107+      checkpointEventData.latestCheckpointPath = preparationCheckpoint.file.storagePath;
108+    }
109+
110+    if (checkpoint.gitDiffPlan !== undefined) {
111+      checkpointEventData.gitDiffCommands = {
112+        checkpointType: checkpoint.gitDiffPlan.checkpointType,
113+        cadenceSec: {
114+          min: checkpoint.gitDiffPlan.cadenceSec.min,
115+          max: checkpoint.gitDiffPlan.cadenceSec.max
116+        },
117+        commands: {
118+          statusShort: {
119+            purpose: checkpoint.gitDiffPlan.commands.statusShort.purpose,
120+            args: checkpoint.gitDiffPlan.commands.statusShort.args
121+          },
122+          binaryDiff: {
123+            purpose: checkpoint.gitDiffPlan.commands.binaryDiff.purpose,
124+            args: checkpoint.gitDiffPlan.commands.binaryDiff.args
125+          },
126+          diffStat:
127+            checkpoint.gitDiffPlan.commands.diffStat === undefined
128+              ? null
129+              : {
130+                  purpose: checkpoint.gitDiffPlan.commands.diffStat.purpose,
131+                  args: checkpoint.gitDiffPlan.commands.diffStat.args
132+                }
133+        },
134+        replay: {
135+          strategy: checkpoint.gitDiffPlan.replay.strategy,
136+          args: checkpoint.gitDiffPlan.replay.args,
137+          baseRef: checkpoint.gitDiffPlan.replay.baseRef ?? null
138+        }
139+      };
140+    }
141+
142     recordLifecycleEvent(run.logSession, {
143       type: "checkpoint_slot_reserved",
144       level: "info",
145-      message: `Reserved checkpoint sequence ${checkpoint.nextCheckpointSeq} for future T-006 integration.`,
146-      data: {
147-        checkpointsDir: run.logPaths.checkpointsDir,
148-        nextCheckpointSeq: checkpoint.nextCheckpointSeq,
149-        deferredFeatures: checkpoint.deferredFeatures
150-      }
151+      message: `Initialized checkpoint manager at sequence ${checkpoint.lastCheckpointSeq}.`,
152+      data: checkpointEventData
153     });
154   }
155 
156@@ -304,9 +327,9 @@ export async function runStep(
157     recordLifecycleEvent(run.logSession, {
158       type: "worker_execution_deferred",
159       level: "info",
160-      message: `Real ${request.workerKind} execution is intentionally deferred in T-005.`,
161+      message: `Real ${request.workerKind} execution is intentionally deferred while checkpoint capture remains active.`,
162       data: {
163-        deferredFeatures: run.checkpoint.deferredFeatures,
164+        supportedTypes: run.checkpoint.supportedTypes,
165         nextCheckpointSeq: run.checkpoint.nextCheckpointSeq
166       }
167     });
168@@ -337,6 +360,14 @@ export async function runStep(
169     }
170   });
171 
172+  emitLogTailCheckpoint(
173+    run.checkpointManager,
174+    run.checkpoint,
175+    run.logSession,
176+    `Captured combined log tail after ${execution.outcome} for ${request.stepId}.`,
177+    request.checkpoint?.logTailLines
178+  );
179+
180   synchronizeRunState(run, {
181     status: mapOutcomeToRunStatus(execution.outcome),
182     updatedAt: finishedAt,
183@@ -376,7 +407,7 @@ export async function runStep(
184       lifecycleEventCount: logSummary.lifecycleEventCount,
185       stdoutChunkCount: logSummary.stdoutChunkCount,
186       stderrChunkCount: logSummary.stderrChunkCount,
187-      checkpointCount: 0
188+      checkpointCount: run.checkpoint.records.length
189     }
190   };
191 }
M apps/worker-runner/tsconfig.json
+7, -2
 1@@ -5,8 +5,13 @@
 2     "outDir": "dist",
 3     "baseUrl": "../..",
 4     "paths": {
 5-      "@baa-conductor/logging": ["packages/logging/src/index.ts"]
 6+      "@baa-conductor/logging": ["packages/logging/src/index.ts"],
 7+      "@baa-conductor/checkpointing": ["packages/checkpointing/src/index.ts"]
 8     }
 9   },
10-  "include": ["src/**/*.ts", "../../packages/logging/src/**/*.ts"]
11+  "include": [
12+    "src/**/*.ts",
13+    "../../packages/logging/src/**/*.ts",
14+    "../../packages/checkpointing/src/**/*.ts"
15+  ]
16 }
M coordination/tasks/T-006-checkpointing.md
+21, -8
 1@@ -1,10 +1,10 @@
 2 ---
 3 task_id: T-006
 4 title: Checkpoint 与 Git Diff Snapshots
 5-status: todo
 6+status: review
 7 branch: feat/T-006-checkpointing
 8 repo: /Users/george/code/baa-conductor
 9-base_ref: main@28829de
10+base_ref: main@56e9a4f
11 depends_on:
12   - T-005
13 write_scope:
14@@ -21,7 +21,7 @@ updated_at: 2026-03-21
15 
16 ## 统一开工要求
17 
18-- 必须从 `main@28829de` 切出该分支
19+- 必须从 `main@56e9a4f` 切出该分支
20 - 新 worktree 进入后先执行 `npx --yes pnpm install`
21 - 不允许从其他任务分支切分支
22 
23@@ -55,24 +55,37 @@ updated_at: 2026-03-21
24 
25 ## files_changed
26 
27-- 待填写
28+- `packages/checkpointing/src/index.ts`
29+- `apps/worker-runner/src/contracts.ts`
30+- `apps/worker-runner/src/checkpoints.ts`
31+- `apps/worker-runner/src/runner.ts`
32+- `apps/worker-runner/src/index.ts`
33+- `apps/worker-runner/tsconfig.json`
34+- `coordination/tasks/T-006-checkpointing.md`
35 
36 ## commands_run
37 
38-- 待填写
39+- `npx --yes pnpm install`
40+- `npx --yes pnpm --filter @baa-conductor/checkpointing typecheck`
41+- `npx --yes pnpm --filter @baa-conductor/worker-runner typecheck`
42+- `npx --yes pnpm typecheck`
43 
44 ## result
45 
46-- 待填写
47+- 已实现 `summary`、`git_diff`、`log_tail` 的 checkpoint 类型、序号管理、文件命名约定与渲染接口。
48+- 已为 `git_diff` 增加快照命令计划与未来 `git apply --binary` 回放提示,但未接入真实 Git 执行。
49+- `worker-runner` 现已默认初始化 checkpoint manager,准备阶段写入 `summary` checkpoint,结束阶段生成 `log_tail` checkpoint,并把状态/计划暴露给后续 executor 与恢复流程。
50 
51 ## risks
52 
53-- 待填写
54+- 当前实现只落到类型、计划和内存态记录,尚未把 checkpoint 文件真正写入本地目录。
55+- `git_diff` 仍是骨架接口;真实 diff 捕获、patch 持久化与 D1 上报需要后续任务接入。
56 
57 ## next_handoff
58 
59-- 给 failover 恢复实现提供基础接口
60+- 把 checkpoint manager 接到真实 worker executor、Git 命令执行与本地/D1 持久化链路,为 failover 恢复提供可回放 diff。
61 
62 ## notes
63 
64 - `2026-03-21`: 创建任务卡
65+- `2026-03-21`: 按最新协调要求将基线修正为 `main@56e9a4f`
M packages/checkpointing/src/index.ts
+426, -2
  1@@ -1,4 +1,29 @@
  2 export type CheckpointType = "summary" | "git_diff" | "log_tail" | "test_output";
  3+export type CheckpointFileFormat = "json" | "text" | "patch";
  4+export type CheckpointReplayStrategy = "none" | "git_apply_binary";
  5+export type CheckpointLogTailSource = "worker" | "stdout" | "stderr" | "combined";
  6+
  7+export type CheckpointJsonValue =
  8+  | string
  9+  | number
 10+  | boolean
 11+  | null
 12+  | CheckpointJsonValue[]
 13+  | { [key: string]: CheckpointJsonValue };
 14+
 15+export interface CheckpointFileDescriptor {
 16+  fileName: string;
 17+  relativePath: string;
 18+  storagePath: string;
 19+  format: CheckpointFileFormat;
 20+  mediaType: string;
 21+}
 22+
 23+export interface CheckpointReplayHint {
 24+  strategy: CheckpointReplayStrategy;
 25+  args: string[];
 26+  baseRef?: string;
 27+}
 28 
 29 export interface CheckpointRecord {
 30   checkpointId: string;
 31@@ -8,13 +33,412 @@ export interface CheckpointRecord {
 32   seq: number;
 33   type: CheckpointType;
 34   summary: string;
 35+  createdAt: string;
 36+  file: CheckpointFileDescriptor;
 37+  contentText?: string;
 38+  contentJson?: { [key: string]: CheckpointJsonValue };
 39+  replay?: CheckpointReplayHint;
 40+}
 41+
 42+export interface RenderedCheckpointFile extends CheckpointFileDescriptor {
 43+  content: string;
 44+}
 45+
 46+export interface SummaryCheckpointInput {
 47+  summary: string;
 48+  detail?: string;
 49+  createdAt?: string;
 50+}
 51+
 52+export interface LogTailCheckpointInput {
 53+  summary: string;
 54+  source: CheckpointLogTailSource;
 55+  lines: string[];
 56+  truncated?: boolean;
 57+  createdAt?: string;
 58+}
 59+
 60+export interface GitDiffCheckpointInput {
 61+  summary: string;
 62+  statusShort: string;
 63+  diff: string;
 64+  diffStat?: string;
 65+  baseRef?: string;
 66+  createdAt?: string;
 67+}
 68+
 69+export interface GitCommandPlan {
 70+  purpose: string;
 71+  args: string[];
 72+}
 73+
 74+export interface GitDiffSnapshotPlan {
 75+  checkpointType: "git_diff";
 76+  commands: {
 77+    statusShort: GitCommandPlan;
 78+    binaryDiff: GitCommandPlan;
 79+    diffStat?: GitCommandPlan;
 80+  };
 81+  cadenceSec: {
 82+    min: number;
 83+    max: number;
 84+  };
 85+  replay: CheckpointReplayHint;
 86+}
 87+
 88+export interface CreateGitDiffSnapshotPlanInput {
 89+  baseRef?: string;
 90+  includeStat?: boolean;
 91+}
 92+
 93+export interface CreateCheckpointManagerInput {
 94+  taskId: string;
 95+  stepId: string;
 96+  runId: string;
 97+  directory: string;
 98+  resumeFromSeq?: number;
 99+  supportedTypes?: CheckpointType[];
100+  gitDiffBaseRef?: string;
101+  includeGitDiffStat?: boolean;
102+  note?: string;
103+}
104+
105+export interface CheckpointManagerState {
106+  taskId: string;
107+  stepId: string;
108+  runId: string;
109+  directory: string;
110+  lastSeq: number;
111+  nextSeq: number;
112+  resumeFromSeq?: number;
113+  supportedTypes: CheckpointType[];
114+  records: CheckpointRecord[];
115+  gitDiffPlan?: GitDiffSnapshotPlan;
116+  note: string;
117 }
118 
119 export interface CheckpointManager {
120-  createSummaryCheckpoint(summary: string): CheckpointRecord;
121+  readonly state: CheckpointManagerState;
122+  createSummaryCheckpoint(input: SummaryCheckpointInput): CheckpointRecord;
123+  createLogTailCheckpoint(input: LogTailCheckpointInput): CheckpointRecord;
124+  createGitDiffCheckpoint(input: GitDiffCheckpointInput): CheckpointRecord;
125+}
126+
127+const DEFAULT_SUPPORTED_CHECKPOINT_TYPES: CheckpointType[] = ["summary", "git_diff", "log_tail"];
128+
129+const CHECKPOINT_FILE_LAYOUT: Record<
130+  CheckpointType,
131+  {
132+    suffix: string;
133+    format: CheckpointFileFormat;
134+    mediaType: string;
135+  }
136+> = {
137+  summary: {
138+    suffix: "summary.json",
139+    format: "json",
140+    mediaType: "application/json"
141+  },
142+  git_diff: {
143+    suffix: "git-diff.patch",
144+    format: "patch",
145+    mediaType: "text/x-diff"
146+  },
147+  log_tail: {
148+    suffix: "log-tail.txt",
149+    format: "text",
150+    mediaType: "text/plain"
151+  },
152+  test_output: {
153+    suffix: "test-output.txt",
154+    format: "text",
155+    mediaType: "text/plain"
156+  }
157+};
158+
159+function trimTrailingSlashes(value: string): string {
160+  const trimmed = value.replace(/\/+$/u, "");
161+
162+  return trimmed === "" ? "/" : trimmed;
163+}
164+
165+function joinPath(basePath: string, segment: string): string {
166+  const normalizedBasePath = trimTrailingSlashes(basePath);
167+
168+  if (normalizedBasePath === "/") {
169+    return `/${segment}`;
170+  }
171+
172+  return `${normalizedBasePath}/${segment}`;
173+}
174+
175+function getCheckpointFileLayout(type: CheckpointType) {
176+  return CHECKPOINT_FILE_LAYOUT[type];
177+}
178+
179+function createCheckpointRecordBase(
180+  state: CheckpointManagerState,
181+  type: CheckpointType,
182+  summary: string,
183+  createdAt?: string
184+): CheckpointRecord {
185+  const seq = state.nextSeq;
186+
187+  return {
188+    checkpointId: `${state.runId}:checkpoint:${String(seq).padStart(4, "0")}`,
189+    taskId: state.taskId,
190+    stepId: state.stepId,
191+    runId: state.runId,
192+    seq,
193+    type,
194+    summary,
195+    createdAt: createdAt ?? new Date().toISOString(),
196+    file: createCheckpointFileDescriptor(state.directory, seq, type)
197+  };
198+}
199+
200+function pushRecord(state: CheckpointManagerState, record: CheckpointRecord): CheckpointRecord {
201+  state.records.push(record);
202+  state.lastSeq = record.seq;
203+  state.nextSeq = record.seq + 1;
204+
205+  return record;
206+}
207+
208+function assertSupportedType(state: CheckpointManagerState, type: CheckpointType): void {
209+  if (!state.supportedTypes.includes(type)) {
210+    throw new Error(`Checkpoint type ${type} is not enabled for ${state.runId}.`);
211+  }
212+}
213+
214+function createSummaryPayload(record: CheckpointRecord, detail?: string): {
215+  [key: string]: CheckpointJsonValue;
216+} {
217+  const payload: { [key: string]: CheckpointJsonValue } = {
218+    checkpoint_id: record.checkpointId,
219+    task_id: record.taskId,
220+    step_id: record.stepId,
221+    run_id: record.runId,
222+    seq: record.seq,
223+    checkpoint_type: record.type,
224+    summary: record.summary,
225+    created_at: record.createdAt
226+  };
227+
228+  if (detail !== undefined) {
229+    payload.detail = detail;
230+  }
231+
232+  return payload;
233+}
234+
235+function createLogTailPayload(
236+  source: CheckpointLogTailSource,
237+  lines: string[],
238+  truncated: boolean
239+): { [key: string]: CheckpointJsonValue } {
240+  return {
241+    source,
242+    line_count: lines.length,
243+    truncated
244+  };
245 }
246 
247-export function createCheckpointFilename(seq: number, suffix: string): string {
248+function createGitDiffPayload(input: GitDiffCheckpointInput): { [key: string]: CheckpointJsonValue } {
249+  const payload: { [key: string]: CheckpointJsonValue } = {
250+    status_short: input.statusShort
251+  };
252+
253+  if (input.diffStat !== undefined) {
254+    payload.diff_stat = input.diffStat;
255+  }
256+
257+  if (input.baseRef !== undefined) {
258+    payload.base_ref = input.baseRef;
259+  }
260+
261+  return payload;
262+}
263+
264+function renderTextContent(contentText?: string): string {
265+  if (contentText === undefined || contentText === "") {
266+    return "";
267+  }
268+
269+  return contentText.endsWith("\n") ? contentText : `${contentText}\n`;
270+}
271+
272+export function createCheckpointFilename(
273+  seq: number,
274+  checkpointTypeOrSuffix: CheckpointType | string
275+): string {
276+  const suffix =
277+    checkpointTypeOrSuffix in CHECKPOINT_FILE_LAYOUT
278+      ? getCheckpointFileLayout(checkpointTypeOrSuffix as CheckpointType).suffix
279+      : checkpointTypeOrSuffix;
280+
281   return `${String(seq).padStart(4, "0")}-${suffix}`;
282 }
283 
284+export function createCheckpointFileDescriptor(
285+  directory: string,
286+  seq: number,
287+  type: CheckpointType
288+): CheckpointFileDescriptor {
289+  const layout = getCheckpointFileLayout(type);
290+  const relativePath = createCheckpointFilename(seq, layout.suffix);
291+
292+  return {
293+    fileName: relativePath,
294+    relativePath,
295+    storagePath: joinPath(directory, relativePath),
296+    format: layout.format,
297+    mediaType: layout.mediaType
298+  };
299+}
300+
301+export function createGitDiffSnapshotPlan(
302+  input: CreateGitDiffSnapshotPlanInput = {}
303+): GitDiffSnapshotPlan {
304+  const replayArgs = ["git", "apply", "--binary", "<checkpoint-file>"];
305+
306+  return {
307+    checkpointType: "git_diff",
308+    commands: {
309+      statusShort: {
310+        purpose: "Capture worktree status before diff snapshot.",
311+        args: ["git", "status", "--short"]
312+      },
313+      binaryDiff: {
314+        purpose: "Capture replayable patch content for checkpoint restore.",
315+        args: ["git", "diff", "--binary"]
316+      },
317+      diffStat:
318+        input.includeStat === false
319+          ? undefined
320+          : {
321+              purpose: "Capture concise diff size summary for conductor UIs and D1 summary fields.",
322+              args: ["git", "diff", "--stat"]
323+            }
324+    },
325+    cadenceSec: {
326+      min: 30,
327+      max: 60
328+    },
329+    replay: {
330+      strategy: "git_apply_binary",
331+      args: replayArgs,
332+      baseRef: input.baseRef
333+    }
334+  };
335+}
336+
337+export function createCheckpointManagerState(
338+  input: CreateCheckpointManagerInput
339+): CheckpointManagerState {
340+  const lastSeq = input.resumeFromSeq ?? 0;
341+  const supportedTypes = input.supportedTypes ?? [...DEFAULT_SUPPORTED_CHECKPOINT_TYPES];
342+  const gitDiffPlan = supportedTypes.includes("git_diff")
343+    ? createGitDiffSnapshotPlan({
344+        baseRef: input.gitDiffBaseRef,
345+        includeStat: input.includeGitDiffStat
346+      })
347+    : undefined;
348+
349+  return {
350+    taskId: input.taskId,
351+    stepId: input.stepId,
352+    runId: input.runId,
353+    directory: trimTrailingSlashes(input.directory),
354+    lastSeq,
355+    nextSeq: lastSeq + 1,
356+    resumeFromSeq: input.resumeFromSeq,
357+    supportedTypes,
358+    records: [],
359+    gitDiffPlan,
360+    note:
361+      input.note ??
362+      "Checkpoint manager tracks summary/log_tail payloads and exposes git diff snapshot commands."
363+  };
364+}
365+
366+export function renderCheckpointFile(record: CheckpointRecord): RenderedCheckpointFile {
367+  let content: string;
368+
369+  switch (record.type) {
370+    case "summary":
371+      content = `${JSON.stringify(record.contentJson ?? {}, null, 2)}\n`;
372+      break;
373+    case "git_diff":
374+    case "log_tail":
375+    case "test_output":
376+      content = renderTextContent(record.contentText);
377+      break;
378+  }
379+
380+  return {
381+    ...record.file,
382+    content
383+  };
384+}
385+
386+export function createCheckpointManager(input: CreateCheckpointManagerInput): CheckpointManager {
387+  const state = createCheckpointManagerState(input);
388+
389+  return {
390+    state,
391+    createSummaryCheckpoint(summaryInput): CheckpointRecord {
392+      assertSupportedType(state, "summary");
393+
394+      const record = createCheckpointRecordBase(
395+        state,
396+        "summary",
397+        summaryInput.summary,
398+        summaryInput.createdAt
399+      );
400+
401+      record.contentJson = createSummaryPayload(record, summaryInput.detail);
402+
403+      return pushRecord(state, record);
404+    },
405+    createLogTailCheckpoint(logTailInput): CheckpointRecord {
406+      assertSupportedType(state, "log_tail");
407+
408+      const record = createCheckpointRecordBase(
409+        state,
410+        "log_tail",
411+        logTailInput.summary,
412+        logTailInput.createdAt
413+      );
414+
415+      record.contentText = logTailInput.lines.join("\n");
416+      record.contentJson = createLogTailPayload(
417+        logTailInput.source,
418+        logTailInput.lines,
419+        logTailInput.truncated ?? false
420+      );
421+
422+      return pushRecord(state, record);
423+    },
424+    createGitDiffCheckpoint(gitDiffInput): CheckpointRecord {
425+      assertSupportedType(state, "git_diff");
426+
427+      const record = createCheckpointRecordBase(
428+        state,
429+        "git_diff",
430+        gitDiffInput.summary,
431+        gitDiffInput.createdAt
432+      );
433+
434+      record.contentText = gitDiffInput.diff;
435+      record.contentJson = createGitDiffPayload(gitDiffInput);
436+      record.replay = {
437+        strategy: "git_apply_binary",
438+        args: ["git", "apply", "--binary", record.file.storagePath],
439+        baseRef: gitDiffInput.baseRef
440+      };
441+
442+      return pushRecord(state, record);
443+    }
444+  };
445+}