NOIR
Guides

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:

  • enqueueInbound inserts once and reports whether it was new.
  • claimInbound selects one due job and records lease ownership in one transaction.
  • renewInbound succeeds only for the current owner.
  • completeInbound and retryInbound check the worker ID.
  • saveToolStep preserves the first successful result.
  • decideApproval transitions 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.

On this page