Skip to main content
NTL is multi-database by design. It does not ship a database and does not privilege one. It specifies what a node must remember and leaves where to a backend you choose. If you run PostgreSQL, point NTL at it. If you are deploying to a phone, SQLite needs no configuration. If your data is already a graph, use a graph database and let the storage graph and the synapse topology be the same graph.
The contract every backend satisfies is spec/storage-interface. Its Rust expression is the NodeStore trait in ntl-core. Any database that satisfies it is a conforming backend — including one you write yourself.

The Matrix

“Community” means the trait is sufficient and nobody has published one yet.
Choosing, briefly. Not sure? Use SQLite — it handles both edge and full-node deployments, and moving later is a config change plus a migration, not a rewrite. Already run Postgres and want NTL state under the same backup and access control? Use PostgreSQL. Is your domain data already a graph? A graph backend lets topology queries be traversals instead of joins.

What Every Backend Must Store

Six categories, regardless of family. This is the part that makes the interface portable: Signal bodies are optional and off by default. See spec/storage-interface for which of these may be memory-only per deployment class.

SQLite

Zero configuration. ntl init creates the database and applies migrations on first run.
Full options:
A single file with no server is exactly what a constrained device needs. WAL mode lets the propagation path read synapse weights while the learning loop writes them, without either blocking the other. On flash-backed devices synchronous = "normal" trades a narrow power-loss window for materially fewer write cycles. Use full where power loss is likely and the hardware can take the wear — and note that only full lets the backend honestly report Durable.
Do not put the database on a network filesystem. SQLite’s locking is unreliable over NFS and SMB, and the failure mode is corruption rather than an error.

PostgreSQL

For operators who already run PostgreSQL and want NTL state where their backups, monitoring, and access control already reach. That is the case for it — an operational convenience, not a performance argument. SQLite is faster for a single node.
What ships today: the schema, the migrations, and a full MCP server that creates and inspects it. That means you can stand up an NTL Postgres store and query it from an agent right now. The Rust NodeStore implementation is the remaining piece; use SQLite for a running node until it lands. Because NodeStore is synchronous while Postgres drivers are asynchronous, ntl-store-postgres wraps its calls in a blocking pool at its own crate boundary. That keeps ntl-core free of an async runtime — see spec/storage-interface for the reasoning, and the crate’s own docs for the two traps in that pattern.

In-Memory

MemoryStore in ntl-core is the reference implementation — the semantics every other backend is checked against. Nothing survives process exit, so a node re-bootstraps from its configured peers and relearns its weights.

Graph Backends

A node’s synapse topology is a weighted, directed graph. A graph-native backend can store it as one, which turns questions like “which paths within three hops have carried Query signals successfully” into a native traversal rather than a recursive join. Where the backend supports it, the storage graph and the synapse topology can be the same graph: signals deposit state as they propagate, and state changes emit signals. That is a capability such backends offer, not an assumption NTL makes.

SiafuDB

SiafuDB is an embedded property graph database for device, edge, and Web3 environments, with an offline-first design. Like NTL it is a project of The Bundu Foundation. It is one backend option among several, not a dependency. NTL’s roadmap does not wait on it, and nothing in the protocol privileges it.
Its offline-first design lines up well with NTL’s intermittent-connectivity target: both assume disconnection is normal rather than exceptional. Its sync acknowledgements are also a natural source of the delivery outcomes the routing model trains on — a good worked example of the general pattern in research/02, rather than a special case.
Status: planned, behind the siafu feature flag. SiafuDB is itself in development.

SiafuDB-Kuzu

A high-performance C++ graph database with Cypher queries and vector search, also a Foundation project. Where SiafuDB targets the device, SiafuDB-Kuzu targets analytical work: Cypher over accumulated topology, and vector search over signal payload embeddings. The interesting fit is offline analysis of a learned network — asking why routing converged the way it did, across a fleet, after the fact.
Status: planned.

Other Graph Databases

Neo4j, Memgraph, or anything else with a driver can back NTL by implementing NodeStore. Nothing in the protocol privileges one graph database over another, and the conformance suite will tell you whether yours is correct.

KV Stores

The deduplication cache is the one piece of node state that is naturally a TTL-keyed KV workload, and Redis-style stores do it better than a relational table. A backend MAY serve the dedup cache from a KV store while keeping synapse and journal state elsewhere. The atomic check-and-set requirement in spec/storage-interface §3 still applies:
  • SET key value NX EX ttl satisfies it — one round trip decides presence and inserts.
  • GET followed by SET does not. The race it admits is duplicate propagation, which is the exact failure the rule exists to prevent.

Mixing Backends

Nothing requires one backend for everything. A plausible full-node split: This is a composition you build by implementing NodeStore over the pieces, not a configuration NTL ships. If you build one, the conformance suite still applies to the composite.

Writing a Backend

Implement NodeStore and run the conformance suite:
The suite is public API precisely so out-of-tree backends can hold themselves to the same semantics as the in-tree ones. It checks the two requirements backends most often miss:
  1. Deduplication is one atomic check-and-set. A read then a write leaves a race, and that race is duplicate propagation.
  2. Outcome resolution is idempotent. The first receipt wins; a second must not overwrite it. At-least-once delivery makes duplicate receipts routine.
Passing is necessary but not sufficient — the concurrency and durability requirements are not fully mechanically checkable. Read spec/storage-interface before shipping, and report durability() honestly.

Migrating Between Backends

There is no migration tool yet. What works today:
  1. Stop the node.
  2. Export with the new backend’s own tooling, or accept the relearning cost.
  3. Update [storage] and restart.
Relearning is survivable but not free: the node keeps its identity and configured peers, and reconverges on weights over hours to days depending on traffic. Plan for a quiet period, and expect degraded routing quality until the model catches up.
Last modified on September 11, 2026