khive-db
SQLite storage backend for the khive knowledge graph runtime: entity, note, event, and
edge storage, FTS5 text search, and optional sqlite-vec vector storage over a
WAL-mode connection pool.
Features
- WAL-mode connection pool — one writer, N concurrent readers (
ConnectionPool) - Capability-trait factories —
StorageBackendhands outArc<dyn EntityStore>,GraphStore,NoteStore,EventStore,VectorStore,SparseStore,TextSearch, andSqlAccessfromkhive-storage - Forward-only versioned migrations —
run_migrationsappliesMIGRATIONSin order, tracked in_schema_migrations - Legacy pack-scoped schema plans —
ServiceSchemaPlan/apply_schema_planfor pack-auxiliary tables tracked in_schema_versions - FTS5 trigram search (CJK-safe) via
text()/text_with_tokenizer() sqlite-vecvector storage (featurevectors) — per-modelvec0virtual tables with a namespace-scoped embedding-model registry- Periodic WAL checkpoint task (
checkpointmodule)
Usage
use ;
// File-backed (WAL mode, 1 writer + N readers) or StorageBackend::memory() for tests.
let backend = sqlite?;
let entities = backend.entities?; // Arc<dyn khive_storage::EntityStore>
let graph = backend.graph?; // Arc<dyn khive_storage::GraphStore>
let text = backend.text?; // Arc<dyn khive_storage::TextSearch>
let sql = backend.sql; // Arc<dyn khive_storage::SqlAccess>, for pack-owned tables
Each capability accessor (entities, graph, notes, events, vectors, sparse,
text) applies its own DDL idempotently on first call, so callers never need a
separate "create schema" step per store. Namespace-scoped variants
(entities_for_namespace, graph_for_namespace, …) validate that the namespace is
non-empty; the store itself remains namespace-agnostic — callers pass namespace on
each query.
Migrations
Two migration systems coexist, both defined in migrations.rs:
- Versioned (
MIGRATIONS: &[VersionedMigration], applied byrun_migrations) — the forward-only pipeline for core substrate tables (entities, notes, edges, events).V1is the consolidated fresh-start baseline loaded fromsql/schema.sql;V2..V5are incremental.sqlfiles applied in order and tracked in_schema_migrations. A database whose recorded version is ahead of the latest known migration fails loudly rather than silently skipping the baseline. - Legacy per-service (
ServiceSchemaPlan/apply_schema_plan) — used by packs that declare their own auxiliary DDL, tracked per-service in_schema_versions.
Schema DDL is authored in crates/khive-db/sql/*.sql and pulled in via
include_str! — never hand-written as inline Rust string literals. Adding a
migration means a new .sql file plus a new VersionedMigration entry; V1 itself
is never edited on an existing database.
Vector storage
vectors_for_namespace(model_key, embedding_model, dimensions, namespace) creates a
vec_<model_key> virtual table (via sqlite-vec, feature vectors) sized to
dimensions, with cosine distance. model_key must be ASCII
alphanumeric/underscore. Tables predating the field/embedding_model columns
(pre-v0.2.8) are rejected with an explicit error rather than silently dropped —
vector data is a cache, so callers re-embed after recreating the table.
Where this sits
khive-db implements the khive-storage capability traits
(ADR-005)
against SQLite, sitting directly above khive-storage/khive-score/khive-types and
below khive-query and khive-runtime in the storage dependency chain:
types -> score -> storage -> db -> query -> runtime -> pack-* -> mcp
khive-runtime's KhiveRuntime wraps StorageBackend and layers namespace
authorization and pack dispatch on top. Schema evolution follows
ADR-015.
License
Apache-2.0.