.agent/notes/actor-kv-issues-and-sqlite-resolution.md
Date: 2026-02-24
Full audit of actor KV usage across:
rivetkit-typescript/packages/rivetkitrivetkit-typescript/packages/workflow-enginerivetkit-typescript/packages/sqlite-vfsrivetkit-typescript/packages/tracesrivetkit-typescript/packages/cloudflare-workersengine/packages/pegboard* and API surfacesLimits used as baseline:
kv put batch entries: 128kv put batch payload: 976 KiB (keys + values)rivetkit-typescript/packages/rivetkit/src/drivers/file-system/kv-limits.ts:3rivetkit-typescript/packages/rivetkit/src/drivers/file-system/global-state.ts:1333DEFAULT_MAX_CHUNK_BYTES = 1024 * 1024 and target 512 * 1024.driver.set.max value size = 128 KiB.rivetkit-typescript/packages/traces/src/traces.ts:63rivetkit-typescript/packages/traces/src/traces.ts:546rivetkit-typescript/packages/rivetkit/src/actor/instance/traces-driver.ts:44writeChain is a promise chain with no rejection recovery (writeChain = writeChain.then(...)).driver.set fails, subsequent queued writes are never attempted and flush() keeps failing.rivetkit-typescript/packages/traces/src/traces.ts:545rivetkit-typescript/packages/traces/src/traces.ts:767putBatch and deleteBatch.xWrite can write many chunks + metadata in one batch.#delete and xTruncate can delete many chunk keys in one batch.rivetkit-typescript/packages/sqlite-vfs/src/vfs.ts:856rivetkit-typescript/packages/sqlite-vfs/src/vfs.ts:908rivetkit-typescript/packages/sqlite-vfs/src/vfs.ts:979storage.flush builds unbounded writes then calls driver.batch(writes) once.recover() similarly accumulates metadata rewrites and sends one batch.rivetkit-typescript/packages/workflow-engine/src/storage.ts:270rivetkit-typescript/packages/workflow-engine/src/storage.ts:346rivetkit-typescript/packages/workflow-engine/src/index.ts:695rivetkit-typescript/packages/workflow-engine/src/index.ts:722entry.dirty and metadata.dirty are set to false before driver.batch(writes).rivetkit-typescript/packages/workflow-engine/src/storage.ts:296rivetkit-typescript/packages/workflow-engine/src/storage.ts:308rivetkit-typescript/packages/workflow-engine/src/storage.ts:346savePersistInner aggregates actor + all changed connections into one entries batch.connsWithPersistChanged before kvBatchPut; if put fails, changed flags are lost.rivetkit-typescript/packages/rivetkit/src/actor/instance/state-manager.ts:422rivetkit-typescript/packages/rivetkit/src/actor/instance/state-manager.ts:503rivetkit-typescript/packages/rivetkit/src/actor/instance/state-manager.ts:512kvBatchDelete(keys).128 KiB value cap.maxQueueMessageSize values above this cap will fail at KV write time.rivetkit-typescript/packages/rivetkit/src/actor/instance/queue-manager.ts:520rivetkit-typescript/packages/rivetkit/src/actor/instance/queue-manager.ts:530rivetkit-typescript/packages/rivetkit/src/actor/config.ts:226rivetkit-typescript/packages/cloudflare-workers/src/actor-kv.ts:14rivetkit-typescript/packages/cloudflare-workers/src/actor-driver.ts:226rivetkit-typescript/packages/cloudflare-workers/src/actor-driver.ts:251deletePrefix paths list all matching keys, then issue one unsplit kvBatchDelete.rivetkit-typescript/packages/rivetkit/src/workflow/driver.ts:155rivetkit-typescript/packages/rivetkit/src/workflow/driver.ts:166rivetkit-typescript/packages/rivetkit/src/actor/instance/traces-driver.ts:56rivetkit-typescript/packages/rivetkit/src/actor/instance/traces-driver.ts:65connDisconnected catches KV delete errors, logs, and continues.rivetkit-typescript/packages/rivetkit/src/actor/instance/connection-manager.ts:372rivetkit-typescript/packages/rivetkit/src/actor/instance/connection-manager.ts:379nextId/size before kvBatchPut.size before delete/metadata writes.rivetkit-typescript/packages/rivetkit/src/actor/instance/queue-manager.ts:163rivetkit-typescript/packages/rivetkit/src/actor/instance/queue-manager.ts:168rivetkit-typescript/packages/rivetkit/src/actor/instance/queue-manager.ts:523rivetkit-typescript/packages/rivetkit/src/actor/instance/queue-manager.ts:531Promise.all([workflow batch put, saveState]).rivetkit-typescript/packages/rivetkit/src/workflow/driver.ts:189rivetkit-typescript/packages/rivetkit/src/workflow/driver.ts:192ws_to_tunnel_task sends err.to_string() for KV errors, with TODO noting concern.engine/packages/pegboard-runner/src/ws_to_tunnel_task.rs:224engine/packages/pegboard-runner/src/ws_to_tunnel_task.rs:246engine/packages/pegboard-runner/src/ws_to_tunnel_task.rs:321engine/packages/pegboard/src/actor_kv/utils.rs:63engine/packages/pegboard/src/actor_kv/mod.rs:273current_total + payload_size and do not subtract replaced key/value sizes.engine/packages/pegboard/src/actor_kv/utils.rs:63engine/packages/pegboard/src/actor_kv/utils.rs:70rivetkit-typescript/packages/rivetkit/src/drivers/file-system/kv-limits.ts:45rivetkit-typescript/packages/rivetkit/src/drivers/file-system/kv-limits.ts:55#putKvEntriesInDb without validateKvEntries.kvPut.rivetkit-typescript/packages/rivetkit/src/drivers/file-system/global-state.ts:263rivetkit-typescript/packages/rivetkit/src/drivers/file-system/global-state.ts:468rivetkit-typescript/packages/rivetkit/src/drivers/file-system/global-state.ts:590rivetkit-typescript/packages/cloudflare-workers/src/actor-handler-do.ts:433rivetkit-typescript/packages/cloudflare-workers/src/actor-handler-do.ts:435api-peer path forwards actor_kv::get errors via ?.engine/packages/api-peer/src/actors/kv_get.rs:44engine/packages/api-peer/src/actors/kv_get.rs:87rivetkit-typescript/packages/rivetkit/src/manager/router.ts:346rivetkit-typescript/packages/rivetkit/src/remote-manager-driver/mod.ts:387engine/packages/api-peer/src/actors/kv_get.rs:72Promise.allSettled returns only successful IDs; failed deletes are not surfaced loudly.rivetkit-typescript/packages/workflow-engine/src/storage.ts:117rivetkit-typescript/packages/workflow-engine/src/storage.ts:131engine/packages/pegboard/tests/kv_operations.rs:1engine/packages/engine/tests/actors_kv_crud.rs:1SQLITE_OK only if all KV sub-batches succeed.SQLITE_IOERR_* so WAL/journal rollback semantics remain intact.putBatch and deleteBatch fanout paths.