Skip to content

Writing a Store ​

A store is any object that implements IStore from @chamberu/core. Pass it to new RunEngine({ store }); the engine needs nothing else.

Prove it with the contract suite ​

@chamberu/test-kit ships describeStoreContract, the Vitest suite every store must pass. It is the same suite MemoryStore runs.

bash
npm install --save-dev @chamberu/test-kit vitest
ts
// test/unit/store.spec.ts
import { MemoryStore } from '@chamberu/core';
import { describeStoreContract } from '@chamberu/test-kit';

describeStoreContract((clock) => new MemoryStore({ clock }));

The factory gets a fresh ControllableClock per test and must return a fresh, empty store that uses that clock for every expiry decision. The factory may be async, for example to create a database schema.

The suite covers:

  • atomicity under concurrency: parallel claimKey, claimRun, acquire and createRun calls have exactly one winner,
  • expiry: keys, claims and locks expire after their TTL, and exactly at expiresAt they count as expired,
  • updateRun guards: expectStatus (one or several allowed statuses) and expectOwner, RunNotFoundError for a missing run, untouched fields preserved,
  • claimRun and renewClaim on a missing run resolve false,
  • checkpoints: the highest stepIndex wins, an equal index replaces,
  • locks: exclusive, released by their owner only, taken over after expiry,
  • listRuns filters (agent, status, concurrencyKey), total, and the createdAt/id order,
  • effect and state round trips, including where filters,
  • no aliasing of values passed in or returned.

Implementation notes ​

  • Do atomic work in one statement. In SQL, a conditional UPDATE ... WHERE ... RETURNING or INSERT ... ON CONFLICT instead of a read followed by a write in JavaScript.
  • Pass the clock's time in. Compare against clock.now() passed as a parameter, not the database's now().
  • Revive dates. Run, Step, Checkpoint and WaitPoint carry Date values (createdAt, trigger.receivedAt, waiting.expiresAt, claim.expiresAt, ...). Return them as Date objects.
  • Store messages as they are. Checkpoint messages are AI SDK ModelMessage arrays; store them as JSON without reshaping.
  • Only the latest checkpoint is read. Keeping older ones is optional.

Time in tests ​

ControllableClock implements IClock and moves only when you call advance(ms), firing due timers in order. Pass the same clock to the store and to the engine to test leases, timeouts and approval expiry without real timers. See the test-kit reference.