Bring your own database
Implement the RuntimeStore contract with your existing ORM, schema, and migration system.
Your application does not need to install a database “through Noir.” Create and own the client exactly as you do elsewhere, then adapt its operations to RuntimeStore.
Required records
A typical schema contains:
- inbound jobs keyed by provider event ID
- conversation messages keyed by message ID and conversation key
- tool steps keyed by stable checkpoint key
- approvals keyed by approval ID
- outbound jobs keyed by output event ID
Jobs need status, attempt count, availability time, worker ID, and lease expiration. Store normalized JSON payloads in a format your database can query and migrate safely.
Atomic operations
The contract depends on atomicity:
enqueueInboundinserts once and reports whether it was new.claimInboundselects one due job and records lease ownership in one transaction.renewInboundsucceeds only for the current owner.completeInboundandretryInboundcheck the worker ID.saveToolSteppreserves the first successful result.decideApprovaltransitions only a pending, unexpired approval.- outbound claims and settlement follow the same ownership rule.
Adapter shape
import type { RuntimeStore } from '@noir-agent/agent'
import { db } from './db.js'
export const database: RuntimeStore = {
enqueueInbound: (event, conversationKey, runId) =>
inbound.enqueue(db, { event, conversationKey, runId }),
claimInbound: (options) => inbound.claim(db, options),
renewInbound: (id, workerId, leaseUntil) =>
inbound.renew(db, { id, workerId, leaseUntil }),
// implement the remaining contract with the same ownership checks
}Testing the adapter
Run the same behavioral suite against an empty database and real transactions. Test duplicate enqueue, competing claimers, expired lease takeover, stale-owner completion, checkpoint races, approval double decisions, outbound retry, and clean close.
Included reference
postgresStore() is a working reference and production-ready convenience. Read its schema and transaction queries when implementing another backend. Keep migrations in your application repository so deployment remains under your control.