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.