Skip to content

Persistence

Graphorin is local-first: by default, every byte the framework persists lives in a single SQLite database on the user's machine. The default storage adapter (@graphorin/store-sqlite) is built on:

  • better-sqlite3 (MIT) — the synchronous SQLite driver.
  • sqlite-vec (Apache-2.0 OR MIT) — the vector-search extension that backs semantic memory.
  • FTS5 — the SQLite-bundled full-text index that powers hybrid search.

Architecture

A single SQLite database file holds every Graphorin table. The adapter exposes typed sub-stores for memory, sessions, embeddings, workflow, audit, and triggers. Each sub-store is the implementation of a contract declared in @graphorin/core/contracts — your application can swap in a different adapter without touching the rest of the framework.

Wiring the default

ts
import { createSqliteStore } from '@graphorin/store-sqlite';

const sqlite = await createSqliteStore({
  path: './assistant.db',
  pragmas: { /* defaults are sane; override only when you need to */ },
});

await sqlite.init(); // run pending migrations

createSqliteStore({...}) returns a typed object with memory, sessions, embeddings, workflow, audit, and triggers sub-stores plus a top-level close() method.

Migrations

Every Graphorin package owns its own SQL migrations and registers them through the migration registry convention. On sqlite.init() the registry runs pending migrations in dependency order; each migration is wrapped in a transaction and recorded in the migration_state table.

Migrations are forward-only in the v0.1 line. Down-migrations are not supported until the framework reaches 1.0.

The default is Reciprocal Rank Fusion with k=60. A different reranker (e.g. cross-encoder, LLM judge) is one call away — see Memory system for the swap.

Bi-temporal storage

Fact rows in semantic memory are bi-temporal:

ColumnMeaning
validFromWhen the fact became true (defaults to write time).
validToWhen the fact ceased to be true (NULL = still valid).
recordedAtWhen the row was written (immutable).
supersededByPointer to the row that replaced it (when applicable).

Old facts are superseded, never silently overwritten — every change is auditable.

Optional encryption-at-rest

@graphorin/store-sqlite-encrypted is an opt-in companion that pulls in better-sqlite3-multiple-ciphers (MIT) — a drop-in fork of better-sqlite3 that bundles the SQLite3MultipleCiphers extension (SQLCipher v4 compatible). The audit log is always encrypted (mandatory); the main database is encrypted on demand by passing an encryption block to createSqliteStore:

ts
import { resolveSecret } from '@graphorin/security';
import { createSqliteStore } from '@graphorin/store-sqlite';

const sqlite = await createSqliteStore({
  path: './assistant.db',
  encryption: {
    enabled: true,
    cipher: 'sqlcipher',
    passphraseResolver: async () => {
      const value = await resolveSecret('keyring:graphorin_db_key?service=graphorin');
      return value.reveal();
    },
  },
});

await sqlite.init();

Installing @graphorin/store-sqlite-encrypted registers the cipher peer driver. The package also exposes encryptDatabase(...), rekeyDatabase(...), and cipherIntegrityCheck(...) — the runners that back graphorin storage encrypt / rekey / integrity-check. The passphrase is resolved through the same SecretRef pipeline as every other secret. See Secrets.

Embedder model storage

Embeddings produced by @graphorin/embedder-transformersjs are tagged with the canonical embedder id ('<provider>:<model>@<dim>') at write time. The storage layer's EmbeddingMetaRepository enforces a lock-on-first policy by default — silent embedder swaps fail-fast with an actionable error pointing at the planned migration. See Memory system § Embedder migration.

File layout

text
./assistant.db           — main database (memory, sessions, workflow, triggers, embeddings)
./assistant.db-wal       — write-ahead log
./assistant.db-shm       — shared-memory file
./.graphorin/audit.db    — encrypted audit log
./.graphorin/secrets.db  — encrypted-file secrets store (when used)
./.graphorin/triggers/   — trigger artifacts
./.graphorin/replays/    — replay artifacts

.graphorin/ lives next to the database by default; the path is configurable per sub-store.

Process hardening

The CLI command graphorin doctor audits POSIX file modes on the database, the audit log, the secrets store, and the systemd unit template (where applicable):

bash
graphorin doctor

Recommended defaults are 0600 for the secrets store and the audit log, and 0640 for the main database when running as a daemon under a dedicated service account.

Pluggable adapters

The contracts in @graphorin/core/contracts are deliberately small. Build a non-SQLite adapter (Postgres, libSQL, DuckDB, in-memory) by implementing the MemoryStore, EmbeddingStore, SessionStore, CheckpointStore, AuditStore, and TriggerStore interfaces. Existing packages depend only on the contracts.

Next steps


Graphorin · v0.1.0 · MIT License · © 2026 Oleksiy Stepurenko