Skip to content

Storage and State

Verist keeps state in your database. The kernel never holds state for you.

The state layers

LayerDescription
computedDerived from step outputs
overlayHuman overrides

Effective state is { ...computed, ...overlay }. Overlay always wins.

What to store per step

Every successful step should result in three writes:

WritePurpose
OutputComputed state update
EventsAudit log
CommandsFor your runner to execute

WARNING

If you skip any of them, you break replay guarantees.

Storage adapters

@verist/storage defines the RunStore contract and provides createMemoryStore() for dev and tests:

ts
import { createMemoryStore, effectiveState } from "@verist/storage";

const store = createMemoryStore();

For production, use @verist/storage-pg which adds:

  • Optimistic concurrency via Postgres
  • Persistent computed + overlay state
  • Audit event persistence
  • Command outbox hooks

You can also implement the RunStore contract yourself.

Minimal commit flow

ts
const result = await run(step, input, ctx);

if (result.ok) {
  await store.commit({
    workflowId: result.value.workflowId,
    runId: result.value.runId,
    stepId: result.value.stepName,
    expectedVersion: currentVersion,
    output: result.value.output,
    events: result.value.events,
  });

  for (const cmd of result.value.commands ?? []) {
    await queue.enqueue(cmd);
  }
}

See Reference Runner for a full loop with artifact capture.

Concurrency and retries

Steps are idempotent by design, so retries are safe.

Use optimistic locking (version column or compare-and-swap) to prevent two workers from committing different outputs for the same run.

Anti-patterns

  • Writing state inside steps
  • Storing computed state in memory between runs
  • Dropping audit events to save space

LLM context: llms.txt · llms-full.txt
Released under the Apache 2.0 License.