Checkpointing
Every run is event-sourced: the session is persisted as an append-only log of SessionEvents through the Checkpointer contract. That buys you durable sessions (resume after a restart), and time travel — roll a session back to any earlier checkpoint, undoing both the conversation and the file changes the agent made. Persistence is on by default via fileCheckpointer; swap in the SQLite backend when several processes must share sessions.
Use it
With the default file backend there is nothing to configure — createLiteAgent persists every session, and LiteAgent owns a current one:
Session management on LiteAgent:
Set sessions: false to disable persistence entirely (session methods then reject).
Time travel
Checkpoints are rewind anchors — one per user prompt. restore(id, seq) rolls a session back to just before a checkpoint: it reverts snapshotted files and/or truncates the conversation, then sets the current session to id.
Time travel works because the file tools snapshot every file before modifying it: restore replays those snapshots to undo changes on disk, then truncates the event log.
Switching to the SQLite backend
The default fileCheckpointer is single-process. The @lite-agent/checkpoint-sqlite package provides sqliteCheckpointer — a SQLite (WAL) backend for single-host, multi-process setups: a server or worker pool where several processes resume and append the same sessions, with optimistic concurrency instead of silent clobbering.
Depends on better-sqlite3 — a native module that compiles on install. @lite-agent/core is required at runtime.
Pass the checkpointer to createLiteAgent (or query); it overrides the default file store:
Options
SqliteCheckpointerOptions:
Concurrency model
- WAL journaling —
journal_mode = WALis set on open: concurrent readers never block, and there is a single writer at a time. BEGIN IMMEDIATEwrites —appendandtruncatetake the write lock up front, so a competing writer waits onbusy_timeoutinstead of failing withSQLITE_BUSY_SNAPSHOT, then reads a fresh head and conflicts cleanly.- Atomic seq allocation — each
appendis one transaction that reads the session head and insertsseq = head + 1…n; the database serializes concurrent appends, so seqs never interleave. - Reads never lock —
read({ sinceSeq })is a pure forward scan; it is also the primitive a server layer can use to tail events over SSE.
Conflict handling
When two processes hold the same session, the second writer's append fails fast with CheckpointConflictError — optimistic concurrency, surfaced never swallowed:
Multi-host concurrency (networked FS, distributed writers) is out of scope for SQLite. The Checkpointer interface is backend-agnostic, so a future Postgres backend can cover that without kernel changes.
Checkpointer contract
SqliteCheckpointer implements the full core Checkpointer interface and passes core's checkpointerConformance test suite — the same suite the default fileCheckpointer runs against:
Operations: checkIntegrity(): { ok, detail } runs PRAGMA quick_check on demand; close() closes the database handle (call it on shutdown — a closed checkpointer cannot be reused). The database carries a user_version schema marker: opening a file written by a newer, incompatible version throws immediately instead of misreading the data.
See also
- Observability — recording the same event stream for audit and debugging.
- Background tasks —
background_completedevents land in the same session log. - Core strategies — the
Checkpointerstrategy interface.