SDK tutorial · 10 of 10

Use your own storage

Local JSON is the zero-configuration default. Hosted products can inject an asynchronous revisioned session repository; the current artifact repository is synchronous and is not a direct adapter boundary for ordinary remote object-store SDKs.

Customization depth

Level 5 · Production persistence

Hosting depth

Local files, session databases, and synchronous artifact stores

Documentation status

Supported SDK boundary

Assumptions

  • The product already owns a trusted server-side persistence and tenancy model.
  • The database adapter can perform atomic compare-and-swap updates.
  • The deployment has a durable path for the remaining trace and memory state.
Heddle owns
  • Own session records, messages, turns, leases, compaction state, optimistic mutation semantics, and artifact metadata behavior.
  • Use injected repositories consistently across session lifecycle, turn persistence, artifact tools, and result capture.
  • Provide file-backed repositories for local and single-machine products.
Your product owns
  • Bind repository instances to authenticated tenant scope and implement production retention, encryption, backup, and monitoring.
  • Guarantee atomic session revision checks, deterministic catalog ordering, and a synchronous durable artifact boundary where one is used.
  • Provide process routing or broker infrastructure separately when active runs span more than one process.

Begin with the local default

createConversationEngine uses file-backed session and artifact repositories when none are injected:

TypeScript
import { createConversationEngine } from '@roackb2/heddle'

const engine = createConversationEngine({
  workspaceRoot,
  stateRoot: '/var/lib/my-agent',
  model: 'gpt-5.4',
})

Use a durable application-data directory. The file session adapter serializes writers and uses immutable revision bodies plus atomic catalog replacement. It expects ordinary local filesystem locking and rename semantics; do not place it on an eventually consistent object-store mount.

Inject session persistence

Hosted products implement the asynchronous, record-oriented ChatSessionRepository port:

TypeScript
import {
  createConversationEngine,
  type ChatSessionRepository,
} from '@roackb2/heddle'

const sessionRepository: ChatSessionRepository = {
  list: async (input) => listSessionPage(input),
  read: async (sessionId) => readSessionRecord(sessionId),
  create: async (session) => createSessionRecord(session),
  update: async (input) => compareAndSwapSession(input),
  delete: async (input) => compareAndSwapSessionDelete(input),
}

const engine = createConversationEngine({
  workspaceRoot,
  stateRoot,
  model,
  sessionRepository,
})

The repository persists records; Heddle retains conversation policy. Updates and deletes include an expected revision. Your adapter must atomically compare and increment that revision so concurrent turns cannot silently overwrite each other.

Construct the adapter with trusted server-side tenant scope. Never accept a browser-provided tenant ID and then trust it inside repository methods. Heddle intentionally keeps product account and row-level-security fields out of ChatSession.

Inject artifact persistence

TypeScript
import type { ArtifactRepository } from '@roackb2/heddle'

const artifactRepository: ArtifactRepository = {
  readCatalog: () => synchronousArtifactStore.readCatalog(),
  writeCatalog: (store) => synchronousArtifactStore.writeCatalog(store),
  contentKey: (id, extension) => 'artifacts/' + id + '.' + extension,
  contentExists: (key) => synchronousArtifactStore.contentExists(key),
  writeContent: (key, content) => synchronousArtifactStore.writeContent(key, content),
  readContent: (key) => synchronousArtifactStore.readContent(key),
}

const engine = createConversationEngine({
  workspaceRoot,
  stateRoot,
  model,
  sessionRepository,
  artifactRepository,
})

contentKey owns addressing. Its return value is stored as RuntimeArtifact.path, but it is an internal content key, not a public download URL.

Every ArtifactRepository method is synchronous today. Heddle does not await catalog or content operations, so do not place an ordinary asynchronous S3, database, or network client behind this interface. Use the file-backed repository or another genuinely synchronous durable store, keep remote upload outside this port, or wait for an asynchronous artifact repository contract before treating remote blob storage as a drop-in adapter.

Required session-adapter behavior

Before production, verify that:

  1. two writers using the same revision cannot lose one another's changes;
  2. missing, already-existing, and revision-conflict records remain distinct outcomes;
  3. cursor pages neither skip nor repeat rows when timestamps tie;
  4. tenant, workspace, and archive filters run inside the authorized scope;
  5. a second engine process can reopen a session with messages, turns, queue, lease, and compaction state intact;
  6. storage failures reach the host as failures instead of a successful turn.

The public guide includes a practical PostgreSQL JSONB table and compare-and-swap query. Reuse the product's existing driver or ORM rather than adding a database stack only for the example.

Remaining path-backed state

Session and artifact repositories are injectable today. Traces and Heddle-managed memory still persist beneath stateRoot, so hosted deployments must provide a suitable durable path or explicitly operate within that limitation.

Shared database persistence also does not make active-run replay cross-process. ConversationRunService keeps bounded active events in one process; multi-process routing, draining, and broker delivery remain product infrastructure.

Canonical sources