# SurrealDB Storage KOGRAL uses SurrealDB 3.0 as its scalable backend, enabled via the `surrealdb-backend` Cargo feature. The integration is built on `surrealdb::engine::any::connect(url)`, which selects the engine at runtime from a URL scheme — no recompilation required when switching between embedded, in-memory, or remote deployments. ## Dual Hot/Cold Layout `SurrealDbStorage` maintains two independent database connections: | Connection | Default engine | URL | Purpose | |---|---|---|---| | `graph_db` | SurrealKV (B-tree) | `surrealkv://.kogral/db/graph` | Nodes, edges, graph metadata | | `hot_db` | RocksDB (LSM) | `rocksdb://.kogral/db/hot` | Embeddings, session logs, append data | SurrealKV's B-tree layout favours point lookups and range scans (node/graph queries). RocksDB's LSM tree favours sequential writes (embedding vectors, event logs). Separating them avoids write-amplification cross-contamination. ## Supported Engines All four engines are compiled in when the `surrealdb-backend` feature is active: | Nickel `engine` | URL scheme | Cargo feature | Use case | |---|---|---|---| | `mem` | `mem://` | `kv-mem` | Tests, ephemeral dev sessions | | `surreal_kv` | `surrealkv://path` | `kv-surrealkv` | Embedded production (default graph) | | `rocks_db` | `rocksdb://path` | `kv-rocksdb` | Embedded production (default hot) | | `ws` | `ws://host:port` | `protocol-ws` | Remote team / shared deployments | ## Configuration ### Embedded (default production) ```nickel storage = { primary = 'filesystem, secondary = { enabled = true, type = 'surrealdb, surrealdb = { graph = { engine = "surreal_kv", path = ".kogral/db/graph" }, hot = { engine = "rocks_db", path = ".kogral/db/hot" }, namespace = "kogral", }, }, } ``` ### In-Memory (tests, CI) ```nickel storage = { primary = 'memory, secondary = { enabled = true, type = 'surrealdb, surrealdb = { graph = { engine = "mem" }, hot = { engine = "mem" }, namespace = "test", }, }, } ``` ### Remote WebSocket (team/shared deployment) ```nickel storage = { primary = 'filesystem, secondary = { enabled = true, type = 'surrealdb, surrealdb = { graph = { engine = "ws", url = "ws://kb.company.com:8000" }, hot = { engine = "ws", url = "ws://kb.company.com:8000" }, namespace = "engineering", }, }, } ``` ## Building with SurrealDB Support ```bash # Debug build cargo build -p kogral-core --features surrealdb-backend # All features (SurrealDB + NATS + orchestration) cargo build -p kogral-core --all-features # Justfile shortcut just build::core-db ``` ## CRUD Pattern All CRUD operations route through `serde_json::Value` as the intermediary type (SurrealDB 3.0 removed `IntoSurrealValue`/`SurrealValue`). The key format for nodes is `("{graph_name}__{node_id}")` on the `nodes` table: ```rust // upsert let row = serde_json::to_value(node)?; let _: Option = graph_db .upsert(("nodes", format!("{graph_name}__{}", node.id))) .content(row) .await?; // select let raw: Option = graph_db .select(("nodes", format!("{graph_name}__{node_id}"))) .await?; // delete let _: Option = graph_db .delete(("nodes", format!("{graph_name}__{node_id}"))) .await?; // list by graph (query API) let nodes: Vec = graph_db .query("SELECT * FROM nodes WHERE project = $g") .bind(("g", graph_name.to_string())) .await? .take(0)?; ``` `.bind()` parameters require owned `String` values — `&str` slices do not satisfy the `'static` bound in SurrealDB 3.0's bind API. ## Hot Data Methods `SurrealDbStorage` exposes direct methods on `hot_db` that are outside the `Storage` trait: ```rust // Store embedding vector for a node pub async fn save_embedding(&self, node_id: &str, vector: &[f32]) -> Result<()> // Append a session event to the log pub async fn log_session(&self, entry: &serde_json::Value) -> Result<()> ``` These operate on the `embeddings` and `sessions` tables in `hot_db`. ## NATS Event Integration When the `nats-events` feature is enabled and `config.nats` is present, the storage factory wraps `SurrealDbStorage` (or any other backend) with `EventingStorage`. Every mutation emits a NATS JetStream event: ```text kogral..node.saved → NodeSaved { graph, node_id, node_type } kogral..node.deleted → NodeDeleted { graph, node_id } kogral..graph.saved → GraphSaved { name, node_count } ``` See [ADR-007: NATS Event Publishing](../architecture/adrs/007-nats-event-publishing.md) for design rationale. ## Feature Matrix | Feature | Includes | |---|---| | `filesystem` (default) | `FilesystemStorage` only | | `surrealdb-backend` | `SurrealDbStorage` + all four engines | | `nats-events` | `EventingStorage`, `KogralEvent`, NATS JetStream client | | `orchestration` | `nats-events` + `stratum-orchestrator` bridge | | `full` | All of the above | ## Related - [ADR-003: Hybrid Storage Strategy](../architecture/adrs/003-hybrid-storage.md) - [ADR-006: SurrealDB 3.0 Engine Abstraction](../architecture/adrs/006-surrealdb-v3-engine-abstraction.md) - [storage/factory.rs](../../crates/kogral-core/src/storage/factory.rs) - [storage/surrealdb.rs](../../crates/kogral-core/src/storage/surrealdb.rs)