Yoke.sh
Runtime

Storage

Per-instance SQLite persistence — pg_sqlite BYTEA in Postgres and S3-backed actor state.

Actor instances store private durable state in per-instance SQLite databases. The Rust kernel abstracts two storage backends behind a StorageBackend trait — selected by YOKE_STORAGE at runtime start.

YOKE_STORAGE

ValueBehavior
pg_sqliteSerialized SQLite files stored as BYTEA in Postgres actor_sqlite table
s3SQLite files in S3; local cache directory for rusqlite execution only

YOKE_STORAGE is required. There is no local-file-only production mode.

pg_sqlite is not the PostgreSQL pg_sqlite extension. Yoke serializes entire SQLite database files into a BYTEA column (actor_sqlite.db). SQLite executes locally in the worker process; persistence is the serialized blob in Postgres.

Postgres actor_sqlite table

actor_sqlite (
  tenant_id,
  instance_id,
  store_id,     -- default instanceId; supports sqliteStoreId segments
  db BYTEA,     -- serialized SQLite file
  version,      -- optimistic lock for state_cas
  updated_at
)

On instance start: load BYTEA → open rusqlite in memory/temp → serve sqlite_exec and state_* ops.

On write: execute SQL → serialize → upsert BYTEA.

S3 backend

Objects stored at:

{YOKE_S3_PREFIX}/instances/{instance_id}/state.db
EnvPurpose
YOKE_S3_BUCKETRequired for S3 mode
YOKE_S3_PREFIXKey prefix (optional)
YOKE_S3_SYNClazy (default) or strict

Sync modes

YOKE_S3_SYNCBehavior
lazyUpload after writes; flush_all() on shutdown
strictfsync local state.db before each S3 put_object

Guarantee: With strict, successful state_set / sqlite_exec / state_cas implies local file durability before upload begins. S3 durability follows AWS semantics. Crashes between local write and upload may lose data in lazy mode.

SDK surface (identical in both modes)

APIBacking
ctx.state.get/set/update__yoke_state table via state_get, state_set, state_cas ops
ctx.sqlite.execArbitrary SQL via sqlite_exec op

Never write __yoke_state directly — use ctx.state.

state_cas

Atomic compare-and-swap for optimistic locking:

state_get → { data, version }
state_cas(store_id, expected_version, new_data) → { data, version } | conflict

The worker RPC exposes state_cas (not state_update). ctx.state.update() wraps this.

sqliteStoreId

Actors can partition SQLite into logical stores per invocation:

defineActor({
  sqliteStoreId: (ctx) => `${ctx.instanceId}:${ctx.config.shardKey}`,
  // ...
});

Each store_id gets its own row in actor_sqlite or S3 object.

Blob storage (conversations)

Conversation media blobs use S3 when YOKE_STORAGE=s3:

POST /v1/tenants/:tenant_id/blobs

Requires YOKE_S3_BUCKET. Metadata in Postgres blobs table; bytes in S3.

Choosing a backend

Best for: single-tenant dev, smaller deployments, strongest single-system durability. One Postgres connection handles stream, queues, and actor state.

Implementation layers

LayerRole
Storage dispatchRoutes ops to the active backend (pg_sqlite or s3)
SQLite enginerusqlite open, state_cas, exec
Postgres backendBYTEA read/write for pg_sqlite mode
S3 backendS3 get/put, local cache

On this page