Stately
PackagesAgent

Where state lives

The two durable artifacts an agent run produces, what each one holds, and how to restore from them.

Alpha: @statelyai/agent 2.0 is in alpha. APIs can change between releases; pin an exact version. Feedback: github.com/statelyai/agent.

Two durable artifacts

An agent run produces exactly two things worth writing to disk, plus run state that only exists while the process does.

  • The event log is the authoritative journal: an ordered array of every external input the machine received, and the only artifact from which the run can be fully reconstructed.
  • The persisted snapshot is a compaction cache: the machine's serialized state at one quiescent point, so a resume can skip replaying the log from index 0.
  • In-memory run state (the live actor, in-flight effects, stream chunks) is derived and disposable; runAgent stops its actor on every settle path, so nothing durable ever depends on it.

The log is the source of truth. A snapshot is an optimization over it, and can always be thrown away.

The artifact table

ArtifactWhat it holdsWhen writtenSurvivesRestore withFull guide
Event log entries (AgentLogEntry[])One envelope per external input: the @agent.init event, effect completions and errors with outputs inline, externally sent events, timer firings, plus identity, timestamp, machine version, and verification hashesPer accepted external input, via result.events, onEvent, or a step-path appendProcess death, redeploy, machine-version change (with an explicit machineVersion), forkingreplay(machine, entries), or runAgent(machine, { events }) with no snapshotThe event log
Persisted snapshotThe machine's serialized state at a quiescent point: state value, context, and restored pending invokesAt a settle point (result.snapshot) or from an actor's getPersistedSnapshot()Process death, redeploy, days of waitingrunAgent(machine, { snapshot, event })Human in the loop
Messages and contextConversation history and any accumulated data live in machine context, so they ride inside both artifacts. Nothing separate to storeWith whichever artifact you writeSame as its carrierRestoring either artifactMessages
In-flight effect stateWhich model calls are owed right nowNever snapshot-able mid-flightOnly the log survives thisreplay, which re-derives owed effects including still-owed dynamic spawnsThe step path
StoresWhere the two artifacts landOn append / saveWhatever the backing database doesstore.read(threadId) / store.load(id)Stores

Two rules follow from the table:

  • Snapshot only at quiescent points. A mid-flight snapshot cannot carry in-flight effect state; the log can. Compact at idle, resume from the snapshot plus the entries appended since.
  • Context must be JSON-serializable. Both artifacts round-trip through JSON.stringify. Keep sessions, db clients, and sockets in closures and store only their ids.

Stores

Two protocols, both interface-first so a userland store interoperates:

  • AgentEventLogStore: append-only with optimistic concurrency on log length. append({ threadId, expectedIndex, entries }), read, length, fork. A stale writer loses with AgentEventLogConflictError. See the store contract.
  • AgentSnapshotStore: load(id) / save(id, snapshot). A key-to-JSON upsert.

Shipped implementations:

StoreImportNotes
In-memory event logcreateInMemoryEventLogStore() from @statelyai/agentThe reference implementation and conformance baseline
SQLite event logcreateSqliteEventLogStore({ database }) from @statelyai/agent/sqliteNode's built-in node:sqlite, no dependencies, Node >= 22.18
SQLite snapshotscreateSqliteSnapshotStore({ database }) from @statelyai/agent/sqliteShares one DatabaseSync handle with the event log store

Both SQLite stores take a file path, ':memory:', or an existing handle, and create their tables on demand. Postgres and Redis adapters are on the roadmap, not shipped; write your own against the protocol and prove it with assertEventLogStoreConformance.

Recipes

Each of these is written up in full on its owning page:

  • Crash recovery: resume from the log alone, with recorded model calls replayed rather than re-billed. See log-only resume, and append before continue for the step-path guarantee.
  • Resume with an event (human in the loop): run to idle, store the snapshot, load it later and deliver the human's event. See persist and resume across processes.
  • Time travel: replay(machine, entries.slice(0, n)) rebuilds the state as of any point in the log, without executing anything. See crash recovery and resume.
  • Fork and branch: store.fork({ threadId, newThreadId, atEventId }) copies a prefix onto a new thread; diffEventLogs reports what diverged. See fork and diff.
  • Verify a log: verifyReplay requires verification hashes on every entry and fails at the first mismatch. See strict replay verification.

On this page