EventStore
and large bytes in an ArtifactStore, then reopen the same directory and
fold the same state. Use this page when you write a host or a storage
provider. For what the record is and why a stopped run carries on from it,
read The record; this page stays at the storage layer
and doesn’t run graph work, resume a run, list runs or manage a workspace.
The smallest use sets a policy, opens the local storage and writes one
artifact with the event that carries its reference:
examples/durable-storage.ts (excerpt)
createLocalRunStorage returns a live RunStorageBinding: one frozen
record, the known secret values used for write checks, an event store and
an artifact store. Every artifact operation names a namespace and run, and
every event operation names a namespace and stream. A storage binding
needs:
- A directory, the storage root.
- A namespace, 1 to 128 ASCII characters, starting with a letter or digit, then letters, digits, dots, underscores or hyphens. The same rule names streams and runs.
- A policy, the size limits and the secret handling below.
Set limits
examples/durable-storage.ts (excerpt)
The local stores reject a value over a limit before it becomes visible. A
second receipt for the same digest doesn’t charge the artifact bytes again,
and accepted artifacts stay until
deleteRun removes that run. Call
preflightAppend with the exact event batch before a run writes its
artifacts, and preflightWrite once with the complete same-run artifact
batch; it returns the references that later writes must issue. Neither
call changes anything or reserves a stream revision or quota, so the real
append or write checks again and can still lose a later race.
Handle secrets
contentMode says how the bytes are used, and the store checks them that
way:
exactandstatebytes reject a configured known secret. JSON state also checks decoded keys and values, includingapplication/*+json. The store never changes these bytes.free-textbytes can have configured known secrets replaced before the store calculates the digest and byte size. The replacement is checked again, and the write fails if it would create another configured secret.sensitive: truerejects the artifact instead of storing it.
Reopen and fold
Open a fresh binding over the same directory and the same state folds back:examples/durable-storage.ts (excerpt)
validateArtifactReference before it reads the reference inside its event.
Run the file with npx tsx durable-storage.ts. It writes one 65 KB
synthetic artifact, appends the event that carries its 194-byte reference,
reopens, folds, and runs both public conformance kits:
Output
Failure
REVISION_CONFLICT: an append supplies the expected stream revision, and when another writer wins, the losing append gets this and no part of its batch becomes visible. A retry with the same bytes returns the revision of its earlier commit; a retry with different bytes conflicts again.- A read fails closed. An artifact read checks that the exact receipt
was admitted for the run, then checks the bytes against it. Missing or
changed data fails with a typed
StorageError. A fresh local event store checks segment order, revisions and checksums when it reads. - A crash can leave bytes behind. Before admission, temporary or blob bytes that aren’t readable and don’t count against the quota. After admission, a verified, quota-charged artifact without an event reference. Hosts own cleanup and reconciliation; the contract doesn’t promise a transaction across two independent ports.
deleteRunremoves only the artifacts in its scope. The event stream and its references stay in history, so later reads through them fail closed. Hosts coordinate event and artifact cleanup.
Bring another storage provider
Implement the rootEventStore and ArtifactStore ports, then pass fresh
provider instances to runEventStoreConformance and
runArtifactStoreConformance. The framework-free kits check durable
reopen, revision conflicts, namespace and run isolation, size limits,
secret handling, content addressing, provider preflight, policy identity
and read integrity. A provider only records and reopens data; the executor
reads a stored run definition and resumes an unfinished node when asked to,
so that work doesn’t belong in a provider.
Full file
Full file
examples/durable-storage.ts
Next steps
- Safe node attempts: what one engine turn records, and how a crash mid-attempt is reconciled.
- The record: the event log as the single source of truth, and what a resumed run repeats.
- Supervised local runs: the runner’s store and how a killed run carries on from it.