Skip to main content

Checkpointers

Normative: MODEL.md §7.

A checkpointer is the memory port — one interface, many backends. It is what makes a pause or a resumable run outlive the process that created it. Give a run a threadId and a checkpointer and every superstep persists atomically.

import { graph, channel, START, END, run, resume, InMemoryCheckpointer } from "@ilmek/core";
import type { ChannelMap, StateOf } from "@ilmek/core";

// Stand-in services — swap for your real order + payment code.
const Orders = { create: (cart: string[]) => ({ id: "ord-9001", total: 249.9 }) };
const Payments = { charge: (order: { id: string; total: number }, ok: string) => "charged" };

/** Checkout state — the cart in, an append-only log out. */
const CheckoutState = {
cart: channel.lastWrite<string[]>([]),
log: channel.append<string>(),
} satisfies ChannelMap;

type CheckoutState = StateOf<typeof CheckoutState>;

const g = graph("checkout", CheckoutState)
.node("checkout", async (state, ctx) => {
const order = await ctx.step("create_order", () => Orders.create(state.cart));
const ok = await ctx.interrupt<string>({ question: `Charge ${order.total}?` });
await ctx.step("charge", () => Payments.charge(order, ok));
return { log: ["done"] };
})
.edge(START, "checkout").edge("checkout", END)
.compile();

const checkpointer = new InMemoryCheckpointer();
const opts = { threadId: "conv-42", checkpointer };
await run(g, { cart: ["coffee", "mug"] }, opts); // persists as it goes
await resume(g, "yes", opts); // picks up from the last checkpoint

The port​

put(threadId, checkpoint) -> void
get(threadId, checkpointId | null) -> checkpoint | null // null = latest
list(threadId, opts) -> checkpoint[] // newest first
putJournal(taskId, entries) -> void
getJournal(taskId) -> entry[]
deleteThread(threadId) -> void

A checkpoint records { id, parentId, threadId, channels, next, pending, step, ts }. Because every checkpoint names its parent, a thread is a tree, not a line: resuming from a non-latest checkpoint forks a branch (time travel / what-if). Implementations must not assume a single chain.

:::note ilmek checkpoints are ilmek's own They hold engine state — channels, journals, and pending pauses — not a host's transcript or conversation store, which a host keeps independently. :::

Available backends​

PackageBackendUse whenStatus
built into @ilmek/coreInMemoryCheckpointertests, demos, single runTS ✅ · .NET ✅
@ilmek/checkpoint-sqliteone SQLite filesingle-process durabilityTS ✅ · .NET ✅
@ilmek/checkpoint-postgresPostgresthreads shared across processesTS ✅ · .NET ⬜

InMemoryCheckpointer is enough to feel the model. Swap in SQLite or Postgres and nothing else in your code changes.