Durable Interrupt & Resume
The personaforge/checkpoint module lets any graph node pause execution via interrupt(), persist a checkpoint, and resume later when a human (or external system) provides a value. Fork-from-checkpoint clones any saved state into a new thread for time-travel exploration.
ts
import {
DurableExecutor, InMemoryCheckpointStore, InterruptSignal,
} from 'personaforge/checkpoint';Quick start
ts
import type { NodeFn } from 'personaforge/checkpoint';
const askApproval: NodeFn = (input, ctx) => {
const value = ctx.interrupt({ question: 'Approve this transfer?' });
return { input, approved: value };
};
const execute: NodeFn = (data) => ({ ...data, done: true });
const exec = new DurableExecutor({
nodes: [['ask', askApproval], ['execute', execute]],
});
// Run — pauses at the interrupt
const r1 = await exec.run({ amount: 500 });
// r1.interrupted === true
// r1.interruptPayload === { question: 'Approve this transfer?' }
// Resume — passes a value back into the paused node
const r2 = await exec.resume(r1.threadId, { ok: true });
// r2.output === { input: { amount: 500 }, approved: { ok: true }, done: true }How it works
interrupt(payload)throws anInterruptSignalthat the executor catches.- The executor persists a
Checkpoint(state, history, pending input) to theCheckpointStore. resume(threadId, value)re-runs the graph from the interrupted node, but this timeinterrupt()returns the resume value instead of throwing.- Execution continues past the pause point with no side-effect replay.
Fork-from-checkpoint
Clone any saved checkpoint into a new thread:
ts
const forkedThread = await exec.fork(originalThread);
await exec.resume(forkedThread, { ok: false }); // explore a different branchPluggable CheckpointStore
The default InMemoryCheckpointStore is suitable for development. For production, implement the interface:
ts
interface CheckpointStore {
save(cp: Checkpoint): Promise<void>;
load(threadId: string): Promise<Checkpoint | null>;
loadById(checkpointId: string): Promise<Checkpoint | null>;
list(threadId: string): Promise<Checkpoint[]>;
delete(threadId: string): Promise<void>;
}A SQLite or Postgres implementation follows the same pattern as SqliteSessionStore.
Checkpoint shape
ts
interface Checkpoint {
id: string;
threadId: string;
node: string; // which node paused
interruptPayload: unknown; // data passed to interrupt()
state: Record<string, unknown>; // accumulated per-node outputs
history: Array<{ node; output }>;
pendingInput: unknown; // input that was flowing into the paused node
createdAt: number;
}Related pages
- Graph Engine — event-sourced, replayable execution.
- Human-in-the-Loop — approval-based pauses.