Skip to main content
Persist and replay decisions | Entity RuntimeChoose a provider, record provenance, prevent lost updates, and understand what replay proves.Entity Runtimehow-toentity-runtimehow-toadopterdeveloperreference

Persist and replay decisions

The kernel returns a value and performs no IO. A shell decides whether to store it, publish its events, or discard it. Durable systems should store the resulting state, decision record, and events as one accepted write.

The write contract

Every commit says what the caller expected:

  • Expect::Absent for creation;
  • Expect::Revision(n) for an operation based on revision n.

The provider checks the expectation before writing. If another writer has already advanced the subject, the commit returns RevisionConflict and writes nothing. Reload and re-decide; never patch the newer state with an older result.

RecordedCommit adds an envelope around the decision. The shell supplies:

  • a globally meaningful record ID used for idempotency;
  • a validated ISO-8601 recorded-at time;
  • an actor, or an explicit statement that there was no actor;
  • optional correlation and causation IDs.

Reusing a record ID for identical bytes succeeds. Reusing it for different bytes is a RecordConflict.

The shared stored runtime, the entity command's stored verbs, the generated CLI, and MCP tools recognize an exact operation retry even after the subject has advanced. Keep the original record ID, metadata, arguments and expected revision; a new request still checks the current revision.

Retry boundaries

Entry pointRevision used for a new operationExact accepted retry
entity execute --storeOptional --expected-revision, defaulting to the revision loaded by that invocationReturns the original commit for matching recorded intent and provenance
Generated domain CLIRequired --expected-revision supplied by the callerReturns the original commit for matching recorded intent and provenance
MCP operation toolRequired expected_revision in the tool inputSame stored-runtime behavior as the generated CLI
Store::commit_recordedCaller supplies Expect with the complete commitIdentical stored bytes are idempotent; a reused ID with different bytes conflicts

Preserve the original expected revision, arguments, and recording metadata when recovering a lost response through entity execute --store, the generated CLI, or MCP. The returned commit describes the original operation; it is not a fresh read of the current subject. Use get to read current state. A new record ID describes a new request and must pass current revision and policy checks.

Generic entity execute --store is convenient for sequential local commands: it omits --expected-revision and decides on the revision the store holds. Rerunning such an invocation after the subject advanced reads the newer revision, so the same --record-id now names a different request and is refused as record_conflict before the kernel runs. Name the revision the request was decided on with --expected-revision whenever a retry must be recoverable rather than refused.

Provider guide

ProviderBest forImportant boundary
MemoryStoretests and process-local experimentsnothing survives the process
FileStorethe entity CLI and local single-root storageone subject document is replaced atomically; use v2 only
SqliteStoreembedded durable applicationsstate, history, and events share a database transaction
PostgresStorecentralized multi-process deploymentsthe caller opens the connection and chooses transport/TLS
RemoteStoreusing a store behind an application-owned transportthis crate defines a versioned JSON protocol, not an HTTP client
Hybridexplicit local/remote authority and offline behaviorauthority, read path, unreachable behavior, and divergence behavior have no defaults

The CLI's --store flag uses FileStore. SQLite, PostgreSQL, Remote, and Hybrid are Rust library integrations; the command does not pretend a filesystem path is a database connection.

MemoryStore, SqliteStore, and PostgresStore also implement AtomicBatchStore for ordered, multi-subject batches that commit completely or roll back completely. File Store atomicity is per subject document, not an arbitrary multi-subject transaction.

File Store 0.17.7 serializes concurrent writers to one root and refreshes cached record identities when another writer changes the store. Upgrade every writer together: older binaries do not take the lock. Use a filesystem that supports advisory locks and atomic rename. Subject data is flushed before replacement; Unix also flushes directories, while Windows does not promise directory-entry persistence across power loss. Abandoned temporary subject files do not block reads or later writes.

Replay and legacy history

A complete decision record contains the normalized command, exact validated definition snapshot, result, changed fields, and events. entity_core::replay executes that command again and compares the complete outcome. Altered input, output, or event evidence is refused.

Legacy event-only history can be folded with rehydrate. The fold holds every revision to the current definition: one operation must emit exactly those events on that transition, accept the recorded arguments under its argument schema and preconditions, write the recorded changed from its set:, and resolve every payload from its templates; the folded fields must pass the schema and the invariants after every revision. What it cannot do is prove that the original commands passed the definitions that decided them at the time — it has no definition snapshot — and it cannot see a decision that emitted nothing. Data imported by the File Store v2 migrator is marked with a legacy snapshot boundary. Replay verification begins with new complete records after that boundary; do not claim verification from genesis.

Observations

Some evidence concerns a subject without changing its lifecycle revision. Recorded observations are stored separately from state-changing decisions and retain their own provenance. Providers return decisions and observations in append order through HistoryProvider. An events read returns domain events, not observation envelopes or every decision: an accepted operation may emit none. The generic CLI, generated CLI, and MCP tools do not expose a general history/observation verb; use the provider library for those capabilities.

Remote and hybrid failures

Unreachable is not Absent. A server that did not answer has said nothing about whether an entity exists. Preserve that distinction in retries, user messages, and agent tools.

A hybrid store makes conflict policy explicit. Divergences survive the process that noticed them and can be replayed later with catch_up; they are never silently treated as synchronized. Catch-up preserves recorded envelopes and observations. If the destination has already passed missing evidence or lacks a legacy prefix needed for replay, the divergence remains visible for explicit repair. Matching current state is insufficient to prove matching history.

For an existing local store, follow the File Store v2 migration before using a 0.15 or newer binary.