HeliosDB Nano is an embedded SQL engine with native HNSW vector search that runs inside your process and can serve the same data directory over the PostgreSQL and MySQL wire protocols. Branching, time-travel queries and AES-256-GCM encryption are part of the engine; encryption stays off until you enable it in the configuration and supply a key. Written in Rust and licensed Apache 2.0; current source release v4.41.1.
Last updated
Nano is a single self-contained binary that you can link into your application, run as an interactive REPL, or start as a PostgreSQL- and MySQL-speaking server against the same data directory. Relational SQL and HNSW vector search share one engine, so retrieval-heavy applications do not have to run and synchronise a separate vector service.
Written in Rust, Apache 2.0 licensed. Database branching, time-travel queries, and AES-256-GCM encryption are part of the engine rather than add-ons. Nano is young — the changelog shows an active correctness campaign — so this page states the current limits alongside the capabilities.
A real embedded Nano instance per browser session — no signup, no install.
Both figures are the current release binaries, uncompressed and as compressed downloads, and vary by platform; they are default-feature builds, so measure your own build if you add features.
// Embedded: open a data directory in-process
use heliosdb_nano::EmbeddedDatabase;
let db = EmbeddedDatabase::new("./mydata")?;
db.execute("CREATE TABLE docs (
id INTEGER PRIMARY KEY,
content TEXT,
embedding VECTOR(768)
)")?;
// Relational + vector in one query
let rows = db.query_params("
SELECT content, embedding <=> $1::vector(768) AS dist
FROM docs ORDER BY dist LIMIT 10
", &[query_vec])?;
Ships an AGENTS.md aggregate plus a .claude/skills/ catalogue covering the CLI, REPL meta-commands, public API, and MCP tools. Read the guide →
cargo install heliosdb-nano
Run Nano embedded when one process owns the data, and as a server when several callers need their own sessions or network access. Nano separates two independent choices: where the data lives (storage mode) and how callers reach it (access mode). Both are chosen when the database is opened or the server is started.
| Access mode | How you start it | What it is for |
|---|---|---|
| Embedded library | EmbeddedDatabase::new("./mydata") |
One process owns the data. No network hop, no daemon to supervise. |
| Interactive REPL | heliosdb-nano repl --data-dir ./mydata |
Ad-hoc SQL, inspection, migrations. Single-user, direct access. |
| Foreground server | heliosdb-nano start --data-dir ./mydata |
Several clients over PostgreSQL wire (add --mysql for MySQL). Each connection gets its own session. |
| Background daemon | heliosdb-nano start --daemon --pid-file ./heliosdb.pid |
Same as above, detached. heliosdb-nano status / stop manage it. |
Persistent (a data directory on disk) or in-memory (--memory, or EmbeddedDatabase::new_in_memory()). Any access mode can use either. In-memory can still be dumped on shutdown with --dump-on-shutdown so an ephemeral database survives as a file.
One data directory is owned by one process at a time. Moving from embedded to server means stopping the embedded process and starting a server against the same directory — not running both at once. Keep the Nano version identical across the switch, and add a PostgreSQL or MySQL client to your application; the embedded calls do not carry over unchanged.
Planning that transition? See the migration guides and the concurrency and transactions deep dive.
Nano is good at four things: local AI memory, agent experiments on database branches, historical investigation with time-travel queries, and serving one dataset to both PostgreSQL and MySQL clients.
Relational tables and HNSW vector search live in the same engine, and the vector-search feature is in the default build — so the join between metadata and embeddings is an ordinary SQL join, with no separate vector service to run and keep in sync. Product Quantization is in the default build too: add WITH (quantization='product') to an HNSW index. Restart-surviving PQ-HNSW indexes need the vector-persist build; the default build rebuilds the index in memory.
Branching is real and cheap: CREATE DATABASE BRANCH, USE, MERGE, DROP, scoped to the whole database. MERGE moves rows but has no conflict detection — it is last-writer-wins. For anything conflict-sensitive, fork, inspect, then discard and re-apply validated SQL to main rather than merging.
Any read can be anchored to a past moment with AS OF TIMESTAMP. By default Nano keeps all history; set a retention window (for example version_retention = "7d") and a watermark-based collector reclaims older versions, with VACUUM VERSIONS to force a pass. Time travel answers "what did this look like then" within the retained window. It is not a backup.
PostgreSQL and MySQL wire compatibility is about client connectivity — your existing drivers, ORMs and admin tools connect — not full semantic parity with either server. Two concrete gaps today: collation is byte-order (C) only, with no locale support; and in the stock server row-level security is enforced through the embedded API and the REPL, while enforcement for wire and REST clients is planned. See current limitations.
Vector search is part of Nano's SQL engine rather than an extension: vector columns, HNSW indexes and distance operators use the same implementation on the embedded and server paths. The vector-search feature ships in the default build, and so does Product Quantization: add WITH (quantization='product') to an HNSW index. Indexes are unquantized unless you ask, and durable restart-surviving PQ-HNSW indexes need the vector-persist build.
Durable PQ-HNSW (vector-persist build):
open(); no rebuild after a process restartremove() + compact() keep recall stable under churnsearch_filtered() evaluates row predicates inside the graph walk, not afterThroughput depends on dimensionality, index parameters and hardware — measure on your own fixtures. Deep dive: Building a Persistent, Quantized, Deletable HNSW in Rust · benchmarks: vector benchmarks · how-to: Build a RAG App on Nano.
-- Create a table with vector column
CREATE TABLE documents (
id INTEGER PRIMARY KEY,
title TEXT,
content TEXT,
embedding VECTOR(768)
);
-- Create an HNSW index. For Product Quantization
-- add quantization = 'product' to the WITH list
CREATE INDEX idx_docs_embedding
ON documents USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);
-- Semantic similarity search.
-- A bound parameter needs an explicit dimension:
-- $1::vector(768), not a bare ::vector.
SELECT title, content,
embedding <=> $1::vector(768) AS distance
FROM documents
ORDER BY distance
LIMIT 10;
A Nano branch is a copy-on-write clone of the whole database, made at the storage layer, so you can test a schema change, run an A/B experiment or give an agent a sandbox without duplicating the dataset.
CREATE DATABASE BRANCH, USE, MERGE, DROP SQL syntaxAS OFMERGE has no conflict detection. It moves rows, but where both sides changed the same row the result is last-writer-wins, with no conflict report. The conflict_resolution= option now fails loudly rather than silently doing nothing. For anything conflict-sensitive, treat a branch as disposable: fork, inspect, discard, then re-apply the SQL you validated to main.
A branch is not a security boundary. Branching isolates storage through copy-on-write. It does not sandbox an agent's external side effects — network calls, file writes, API requests — and nothing in the engine claims an isolation guarantee beyond storage.
-- Fork main into a disposable sandbox
CREATE DATABASE BRANCH feature_auth
FROM main;
USE DATABASE BRANCH feature_auth;
-- Try the change
ALTER TABLE users
ADD COLUMN role TEXT DEFAULT 'user';
INSERT INTO users (name, role)
VALUES ('admin', 'superuser');
-- Inspect, then throw the branch away and
-- re-apply the validated SQL to main.
-- Safer than MERGE, which is last-writer-wins.
USE DATABASE BRANCH main;
DROP DATABASE BRANCH feature_auth;
A time-travel query reaches back as far as the row versions Nano still retains: all history by default, or the retention window you set (for example version_retention = "7d") once version GC is reclaiming older versions. Anchor a read to a past moment with the AS OF clause. Useful for audit questions, for finding out what changed before a bug, and for pulling back rows an UPDATE or DELETE overwrote — as long as the versions are still retained.
AS OF NOW, AS OF TIMESTAMP, AS OF TRANSACTION, and AS OF SCN anchorsversion_retention to turn it onVACUUM VERSIONS forces a reclaim passTime travel does not replace backups. It can only reach versions the engine still retains, and it lives inside the same store as your live data — so it does not survive losing that store. Keep a real backup using heliosdb-nano dump and restore. The two solve different problems.
-- Query data as it existed earlier today
SELECT * FROM orders
AS OF TIMESTAMP '2026-09-11 09:00:00';
-- Or with an explicit offset
SELECT * FROM orders
AS OF TIMESTAMP '2026-09-11 09:00:00 -03:00';
-- Compare current against a past snapshot
SELECT a.id, a.balance,
b.balance AS prev_balance
FROM accounts a
JOIN (
SELECT id, balance FROM accounts
AS OF TIMESTAMP '2026-09-10 09:00:00'
) b ON a.id = b.id;
-- Reclaim superseded versions
VACUUM VERSIONS;
HeliosDB Nano ranks full-text matches with BM25 through ts_rank_cd and can order the same rows by vector distance in one SQL query. A standalone BM25 index and hybrid search with Reciprocal Rank Fusion (RRF) or Maximal Marginal Relevance (MMR) ship as MCP tools in the mcp-endpoint source build; SQL functions for BM25 indexes and hybrid fusion, and PREPARE COMPILED, are planned.
ts_rank_cd (and ts_rank) score a tsvector against a tsquery with BM25.@@ and order by embedding <=> $1, or blend ts_rank_cd with vector similarity in the ORDER BY.heliosdb_bm25_index builds an in-memory BM25 index and heliosdb_hybrid_search fuses BM25 and vector hits with RRF, MMR or weighted-linear fusion (mcp-endpoint source build).PREPARE COMPILED for cached hot-path plans.tsvector, tsquery, @@ and ts_rank / ts_rank_cd with BM25 ranking. Matching today is any-term over lowercase tokens; language stemming, boolean tsquery operators and phrase/proximity search are planned.-- Keyword search ranked with BM25
SELECT title,
ts_rank_cd(to_tsvector(content), to_tsquery('database')) AS rank
FROM articles
WHERE to_tsvector(content) @@ to_tsquery('database')
ORDER BY rank DESC LIMIT 10;
-- Keyword + vector in one query
SELECT title,
0.7 * (1.0 - (embedding <=> $1::vector(768)))
+ 0.3 * ts_rank_cd(to_tsvector(content), plainto_tsquery($2)) AS score
FROM articles
ORDER BY score DESC LIMIT 10;
-- RRF / MMR fusion: heliosdb_hybrid_search MCP tool
-- (mcp-endpoint build); SQL functions planned
Nano's graph operations ship as MCP tools in the mcp-endpoint source build, and graph expansion over tables ships with Graph-RAG in the graph-rag source build. SQL functions for neighbourhood, shortest-path and connected-component queries are planned.
heliosdb_graph_add_edge adds directed, labelled edges to an in-memory graph store; heliosdb_graph_traverse walks it breadth-first with a depth bound, an edge-label filter and a direction.heliosdb_graph_path finds a path with BFS, Dijkstra or bidirectional BFS._hdb_graph_nodes and _hdb_graph_edges, and WITH CONTEXT (HOPS n, EDGES a|b) expands SQL seed rows over typed edges (see Graph-RAG below).# mcp-endpoint source build: graph tools
# (in-memory store; node ids are UUIDs)
curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"heliosdb_graph_traverse",
"arguments":{"start":"<node-uuid>",
"edge_label":"CALLS",
"direction":"out","depth":2}}}'
# Shortest path: bfs, dijkstra or bidi
{"jsonrpc":"2.0","id":2,"method":"tools/call",
"params":{"name":"heliosdb_graph_path",
"arguments":{"from":"<uuid-a>","to":"<uuid-b>",
"algorithm":"dijkstra"}}}
Most code-search tools live outside the database, with their own storage to scan and re-index. Nano makes the AST a first-class table: CREATE EXTENSION hdb_code; CREATE AST INDEX src_ast ON repo (content) USING tree_sitter(lang);, and your codebase becomes queryable in SQL. Build from source with --features code-graph to enable it and the heliosdb-nano code-graph subcommand; the prebuilt binaries and the GHCR image do not include it.
lsp_definition, lsp_references, lsp_call_hierarchy, lsp_hover, lsp_document_symbols, helios_lsp_rename_preview / helios_lsp_rename_apply. The last two land write-side refactors with sha256 conflict checking.register_grammar + register_extractor) for plugging in any tree-sitter grammar.ON BRANCH '<name>' per-call override and AS OF combine in either order. RAII branch guard restores state on every early-return path.hdb_code.pause() / resume() for migrations.--features code-embed and Nano embeds locally with fastembed-rs + BGESmallENV15 (384-dim, ~30 MB model cache, no on-disk impact on the binary). External HttpEmbedder remains an option.helios_lsp_references_diff, helios_lsp_body_diff, helios_ast_diff. Accept any AS OF ref: {"now": true}, {"commit": "sha"}, {"timestamp": "iso"}.-- Index your repo as a queryable AST
CREATE EXTENSION hdb_code;
CREATE AST INDEX src_ast ON repo (content)
USING tree_sitter(lang);
-- Where is this function defined?
SELECT path, line FROM lsp_definition('execute_query');
-- Time-travel + branch on the same call
SELECT * FROM lsp_definition('execute_query')
ON BRANCH 'feat/refactor'
AS OF TIMESTAMP '2026-04-15T00:00:00Z';
-- Rename refactor with conflict check
SELECT * FROM helios_lsp_rename_preview(
'old_name', 'new_name'
);
-- Diff helpers
SELECT * FROM helios_lsp_references_diff(
'main', 'feat/refactor'
);
-- Languages registered (system view)
SELECT name, source FROM hdb_code_languages;
RAG pipelines usually glue together a vector store, a graph database, an entity linker, and a re-ranker — four services to deploy and keep in sync. Nano collapses them into one engine. WITH CONTEXT is a SQL clause; _hdb_graph_nodes and _hdb_graph_edges are ordinary, queryable tables; code symbols are projected into the graph as they are indexed, and the entity linkers add MENTIONS edges. Build from source with --features graph-rag; the prebuilt binaries and the GHCR image do not include it.
centrality_rerank in the Rust API boosts hits by (1 + α·centrality). RERANK BY in WITH CONTEXT is parsed today; applying it in SQL is planned.CREATE SEMANTIC HASH INDEX builds subtree hashes over the code-graph symbols, so only what changed needs re-embedding.graph_rag_ingest_docs, _email, _issues, _qa, plus _pdf, _office, _audio and _image through an external Docling service. They are EmbeddedDatabase methods, not SQL functions.graph_rag_link_vector emits MENTIONS edges with weight = similarity; threshold-gated cosine top-k.// Rust API: ingest a Markdown table, then link
db.graph_rag_ingest_docs(&IngestDocsOptions {
source_table: "docs".into(),
id_col: "id".into(),
text_col: "body".into(),
title_col: Some("title".into()),
chunk_by: ChunkStrategy::Headings,
})?;
db.graph_rag_link_exact(&[])?; // MENTIONS edges
-- SQL: select seed rows, then expand the graph
SELECT node_id, node_kind, title
FROM _hdb_graph_nodes
WHERE title LIKE '%auth%'
WITH CONTEXT (
HOPS 3,
EDGES IMPORTS|REFERENCES|MENTIONS,
DIRECTION both,
LIMIT 50
);
-- Incremental re-embed via semantic-Merkle
CREATE SEMANTIC HASH INDEX IF NOT EXISTS src_merkle;
Coding agents — Claude Code, Cursor, Continue, Codex, Aider — speak Model Context Protocol. Nano serves it from the same process: tool discovery (tools/list), execution (tools/call), and a JSON-RPC 2.0 handshake, without a wrapper process or glue server. Build from source with --features mcp-endpoint; the prebuilt binaries and the GHCR image do not include it.
POST /mcp (HTTP JSON-RPC), /mcp/ws (WebSocket), /mcp/sse (Server-Sent Events). For stdio, use the separate HeliosDB CodeKB MCP binary; the engine has no stdio subcommand.--mcp-token); a non-loopback bind is refused without auth unless you pass --allow-remote-mcp.mcp_tool! macro registers via the inventory crate. BM25, hybrid search, graph operations, and helios_graphrag_search surface automatically.notifications/progress messages flow over WS when the client opts in via _meta.progressToken.tools/list?verbose=true, helios/info JSON-RPC method, GET /mcp/info route. One round-trip for serverInfo + capabilities + verbose tool catalogue + resource list.Mcp-Session-Id. POSTs paired with an open SSE channel get progress events forwarded while the POST returns the final response.# Discover tools (verbose)
curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list",
"params":{"verbose":true},"id":1}'
# helios/info: one-shot discovery
GET http://localhost:8080/mcp/info
# Streaming progress over WebSocket
ws://localhost:8080/mcp/ws
> {"jsonrpc":"2.0","method":"tools/call",
"params":{"name":"helios_graphrag_search",
"arguments":{"seed_text":"auth flow"},
"_meta":{"progressToken":42}},"id":2}
< notifications/progress {"token":42, "message":"seeding"}
< tools/call response <final>
# Or via Claude Code / Cursor MCP config
{
"mcpServers": {
"heliosdb-nano": {
"url": "http://localhost:8080/mcp",
"auth": {"type":"bearer","token":"$HDB_TOKEN"}
}
}
}
Nano's binary includes a PostgREST-style REST API, email and OAuth sign-in with JWT sessions, and a realtime WebSocket, so the backend runs in the same process as the database with no external service to deploy. The /rest/v1 table routes are a known issue in the current release. Nano accepts realtime WebSocket subscriptions in the Supabase message shape. Known issue: realtime events are not delivered in the current release; tracked by the Nano team. File storage (buckets and signed URLs) and edge functions are on the HeliosDB roadmap; Nano stores data in its data directory.
eq, neq, gt, gte, lt, lte, like, ilike, is and in, with not. negation; cs, cd, ov, full-text and or/and filters are planned/docs + OpenAPI 3.0 spec at /openapi.jsonIn the stock Nano server, row-level security is enforced through the embedded API and the REPL; SQL CREATE POLICY and enforcement for REST and wire clients are planned. Enforce per-user access in your service layer — see current limitations.
# Auth — built-in signup and JWT
$ curl -X POST localhost:8080/auth/v1/signup \
-d '{"email":"alice@example.com","password":"s3cret"}'
# OAuth — redirect to Google login
$ curl localhost:8080/auth/v1/authorize?provider=google
# Realtime — WebSocket subscription
wscat -c ws://localhost:8080/realtime/v1/websocket
> {"event":"phx_join","topic":"realtime:public:orders"}
No. Encryption is compiled into the default build, but it is off until you enable [encryption] in the configuration and supply a key. Installing Nano does not encrypt your data on its own — that is a deliberate activation step.
enabled = true under [encryption] in the config file and supplying a keyHELIOSDB_ENCRYPTION_KEY environment variable (the default source; 64 hex characters) or a key file; cloud KMS key sources are plannedfips compile-time feature--tls-cert / --tls-key for the PostgreSQL listener and --mysql-tls-cert / --mysql-tls-key for MySQL; the PostgreSQL listener offers TLS but cannot require it yet. When TLS is on, both listeners offer the X25519MLKEM768 hybrid post-quantum key exchange by default (--tls-post-quantum / --mysql-tls-post-quantum)CREATE POLICY and enforcement for REST and wire clients are planned# Encryption is compiled in but off by default.
# Enable it in the config file, supply a key.
$ openssl rand -hex 32 > tde.key && chmod 600 tde.key
$ cat > heliosdb.toml <<'EOF'
[encryption]
enabled = true
key_source = { Environment = "HELIOSDB_ENCRYPTION_KEY" }
EOF
# The key: 64 hex characters
$ export HELIOSDB_ENCRYPTION_KEY="$(cat ./tde.key)"
$ heliosdb-nano start --config ./heliosdb.toml \
--data-dir ./mydata \
--auth scram-sha-256 --password "$PGPASS" \
--tls-cert ./server.crt --tls-key ./server.key
# FIPS 140-3 mode is a build-time choice
$ cargo install heliosdb-nano --features fips
Standard PostgreSQL and MySQL clients, drivers, ORMs and admin tools connect to Nano directly, because it implements both wire protocols natively. This is client connectivity, not full semantic parity with PostgreSQL or MySQL — expect to test your queries rather than assume every behaviour matches.
--mysql, with 5.7/8.0 compatibility modes and prepared statementsSHOW DATABASES / TABLES / COLUMNS / VARIABLES and information_schema for tool compatibilityFor the current state of SQL-surface compatibility, see the compatibility documentation rather than a single headline percentage.
# Start one server with both listeners
$ heliosdb-nano start --data-dir ./mydata --mysql
# Connect with a PostgreSQL client
$ psql -h 127.0.0.1 -p 5432
# Connect with a MySQL client — same server,
# same tables, a different protocol
$ mysql -h 127.0.0.1 -P 3306
mysql> SELECT * FROM users;
+----+-------+-------------------+
| id | name | email |
+----+-------+-------------------+
| 1 | Alice | alice@example.com |
+----+-------+-------------------+
A single running Nano server accepts PostgreSQL and MySQL clients at the same time, against the same tables. That is genuinely useful for mixed-language teams — and it is a different thing from running the embedded API alongside a server.
# One server process owns ./mydata
$ heliosdb-nano start --data-dir ./mydata --mysql
# Concurrent clients, independent sessions
$ psql -h 127.0.0.1 -p 5432
$ mysql -h 127.0.0.1 -P 3306
# The embedded API is the OTHER way to open
# the same directory — stop the server first.
let db = EmbeddedDatabase::new("./mydata")?;
Internal tests, Nano 3.19 built with --features code-graph,graph-rag vs the acceptance targets, see benchmarks.html. Single-machine, release build, criterion methodology. These are acceptance thresholds from the Graph-RAG work, not a competitive benchmark — reproduce them on your own hardware and report variance.
Yes. WordPress expects to talk to MySQL, and Nano serves the MySQL wire protocol from the same binary that holds the data, so a WordPress install can run against it without a separate MySQL server: 37/37 tests pass, no db.php drop-in, standard wpdb. The MySQL listener is trust-only, so keep it on a trusted network.
Deploy WordPress with one database process instead of a separate MySQL instance to provision, credential and supervise.
CREATE DATABASE BRANCH staging gives a copy-on-write clone for testing plugins and themes without a mysqldump-and-restore cycle. Discard it when you are done — remember MERGE is last-writer-wins.
SELECT * FROM wp_posts AS OF TIMESTAMP '...' reads a post or setting as it was, within the retention window. Useful for finding out what a plugin changed — not a substitute for backups.
Native HNSW vector search means WordPress content can be searched by meaning without adding Elasticsearch or Algolia to the stack.
AES-256-GCM TDE is handled by the database, so no WordPress plugin is involved. It is off until an operator enables it in the configuration and supplies a key — it is not automatic for every install.
Back up with heliosdb-nano dump (zstd by default, --append for incrementals, or --dump-schedule on a cron expression) and recover with restore --verify. Copying a live data directory is not a consistent backup — use the dump path.
Any framework that speaks MySQL or PostgreSQL can point its connection string at a Nano server.
| Framework | Protocol | HeliosDB Nano |
|---|---|---|
| WordPress / Drupal / Joomla | MySQL | Native |
| Laravel / Symfony (PHP) | MySQL / PG | Both |
| Ruby on Rails | MySQL / PG | Both |
| Django (Python) | MySQL / PG | Both |
| Express / Fastify (Node.js) | PG / MySQL | Both |
| Rust (SQLx / Diesel) | PG / Embedded | Both |
Both run inside your process; Nano adds built-in vector search, branching, time-travel reads and PostgreSQL and MySQL wire servers, while SQLite brings decades of production use and a ubiquitous file format. SQLite is an excellent, extraordinarily well-tested embedded database, and for a great many applications it is the right answer. Here is the short version of how Nano differs; the full comparison, including when SQLite is the better fit, is on its own page.
| Capability | SQLite | HeliosDB Nano |
|---|---|---|
| Server access | No built-in network listener; third-party wrappers exist | Embedded, plus built-in PostgreSQL and MySQL wire servers |
| Independent explicit transactions | One write transaction at a time; concurrent readers in WAL mode | Per-connection in server mode. Not on the plain embedded API — see concurrency |
| Vector search | Not in core; extensions exist — check the specific extension and version for its index type | HNSW in the engine, in the default build, same in both modes, including Product Quantization (WITH (quantization='product')). Restart-surviving PQ-HNSW indexes need the vector-persist build |
| Branching & historical queries | Not built in | Copy-on-write branches (MERGE is last-writer-wins) and AS OF within the retention window |
| File format & ecosystem | Its own dialect and a mature, ubiquitous file format with decades of tooling | PostgreSQL-flavoured SQL with a SQLite-dialect shim. Not file-format compatible — import via an external converter |
| Maturity | Decades of production use across an enormous install base — a real reason to choose it | Young, with an active correctness campaign visible in the changelog. Evaluate it on your workload |
Open a database in your own process with the Python binding, or start the server and connect a stock PostgreSQL client. Both examples use the shipped CLI and the published binding — run them against your own install and tell us if anything differs.
The published Python binding is heliosdb-nano-embedded, which imports as heliosdb_nano. It is released separately from the engine and can lag the current engine release (known limitations). No server, no wire protocol — the engine runs inside your process.
# pip install heliosdb-nano-embedded
import heliosdb_nano
db = heliosdb_nano.EmbeddedDatabase("./mydata")
db.execute("""
CREATE TABLE IF NOT EXISTS docs (
id INTEGER PRIMARY KEY,
title TEXT,
embedding VECTOR(4)
)
""")
db.execute_many(
"INSERT INTO docs (id, title, embedding) VALUES ($1, $2, $3)",
[
(1, "vector search", "[1.0,0.0,0.0,0.0]"),
(2, "backups", "[0.0,1.0,0.0,0.0]"),
(3, "hnsw tuning", "[0.9,0.1,0.0,0.0]"),
],
)
# A bound parameter needs an explicit dimension
rows = db.query("""
SELECT id, title, embedding <=> $1::vector(4) AS distance
FROM docs ORDER BY distance LIMIT 3
""", ("[1.0,0.0,0.0,0.0]",))
for r in rows:
print(r)
# {'id': 1, 'title': 'vector search', 'distance': 0.0}
# {'id': 3, 'title': 'hnsw tuning', 'distance': 0.0061162710}
# {'id': 2, 'title': 'backups', 'distance': 1.0}
Executed against the heliosdb-nano-embedded package from PyPI; the output above is the real result. The equivalent Rust API is EmbeddedDatabase::new(path) / new_in_memory() with execute, execute_params, query_params.
When callers need independent sessions or network access, run the same binary as a server. Each connection gets its own transaction state.
# cargo install heliosdb-nano
# Start a server on the loopback interface
$ heliosdb-nano start --data-dir ./mydata \
--listen 127.0.0.1 --port 5432
# Or detached, managed by the CLI
$ heliosdb-nano start --data-dir ./mydata --daemon
$ heliosdb-nano status
$ heliosdb-nano stop
# Connect with a stock PostgreSQL client
$ psql -h 127.0.0.1 -p 5432 -U postgres -d heliosdb
heliosdb=> CREATE TABLE docs (
id INTEGER PRIMARY KEY,
title TEXT,
embedding VECTOR(4));
CREATE TABLE
heliosdb=> SELECT id, title,
embedding <=> '[1,0,0,0]'::vector AS dist
FROM docs ORDER BY dist LIMIT 3;
id | title | dist
----+---------------+-------------
1 | vector search | 0.0
3 | hnsw tuning | 0.006116271
2 | backups | 1.0
(3 rows)
# Back it up — dump, don't copy a live data dir
$ heliosdb-nano dump --data-dir ./mydata \
--output ./backup.heliodump --compression zstd
$ heliosdb-nano restore --input ./backup.heliodump \
--target ./restored --verify
Exercised end-to-end with a stock psql client against a locally started server, and the dump/restore round-trip verified into a fresh data directory. A literal vector string can use a bare ::vector cast; a bound parameter needs ::vector(N).
Yes, in server mode: each PostgreSQL or MySQL connection gets its own session and transaction state, while callers sharing one embedded handle do not get independent explicit transactions. Picking the wrong one is the most common way to be surprised by this engine, so here is the whole picture.
Storage is MVCC, so concurrent reads are fine, and single-statement autocommit writes are fine. For the large class of applications that read a lot and write one statement at a time, the embedded API is straightforward.
Independent, overlapping explicit transactions are not safe on the plain embedded API. Explicit transaction state lives in a single process-global slot per handle, shared across clones — so a second caller that issues BEGIN while another transaction is open does not get its own transaction.
The PostgreSQL and MySQL wire daemon keeps real per-connection transaction state. Two clients can hold two independent explicit transactions at the same time, each committing or rolling back on its own. This is the supported way to get independent sessions today.
BEGIN through every commit, rollback and error path — not just around the statement that opens it. A lock that covers only the start or only the end of a transaction will not make this safe.Raw per-session primitives do exist inside the engine, but they are internal building blocks rather than a documented, supported public API — they are not a workaround, and you should not build on them. A proper public embedded Session API is roadmap, not shipped.
The local crash-recovery WAL is always on, backed by RocksDB, and is what makes an unclean shutdown recoverable. The logical WAL is the replication stream that feeds standbys. They are separate mechanisms, and only the second one is about replication.
Writes made inside explicit transactions do reach the logical WAL and replicate — this was broken in earlier releases and is fixed. One known limitation remains: a standby can transiently observe a partially-applied transaction, because atomic-apply markers are not emitted yet. If a standby is serving reads that must never see a half-applied transaction, account for that.
Full detail, including worked examples: Concurrency and transactions →
Collation is byte-order only, the stock server enforces row-level security only through the embedded API and the REPL, embedded callers cannot run independent explicit transactions, and branch merges have no conflict detection; the cards below cover these and more. We would rather you hit them on this page than in production. The known limitations page tracks every open issue with its workaround.
Sorting and comparison use C / byte-order semantics everywhere. There is no locale-aware collation. If your application depends on linguistic sort order, this will be visible.
In the stock Nano server, row-level security is enforced through the embedded API and the REPL; SQL CREATE POLICY and enforcement for REST and wire clients are planned. Do not treat it as a network-facing tenancy boundary today.
Independent overlapping explicit transactions are not safe on the plain embedded API. Use server mode for independent sessions. See concurrency and transactions.
MERGE moves rows, but concurrent edits to the same row resolve last-writer-wins with no conflict report. Prefer discard-and-reapply for anything conflict-sensitive.
Nano does not open .sqlite files, and the format is not shared. It accepts many SQLite SQL idioms through a dialect compatibility layer, so moving an existing database means exporting it and reloading it into Nano, then verifying the result yourself. See the migration guides.
Nano runs plain-SQL DO blocks and simple SQL/PL/pgSQL functions; PL/pgSQL control flow and trigger bodies are planned.
Geospatial functions and indexes (ST_DWithin, ST_Distance) are planned; they are not in the current release.
Full-text matching is any-term over lowercase tokens. Language stemming, boolean tsquery operators and phrase/proximity search are planned.
The /rest/v1 table routes are a known issue in the current release. Reach tables over the PostgreSQL wire protocol. Keep the HTTP and MySQL listeners on loopback (the default): API-key authentication for the HTTP API is planned, and a smaller listening surface is good practice anyway.
Explicit-transaction writes replicate (fixed in an earlier release), but a standby can transiently see a partially-applied transaction because atomic-apply markers are not emitted yet.
Two earlier releases could lose data when they opened a store written by an older release; current releases are fixed, and the CHANGELOG names the affected releases. Take a dump before any version change, and do not combine a version bump with a mode switch.
For the current SQL-surface compatibility detail, see the Nano documentation.
No. It is an independent engine written in Rust. It includes a SQL-dialect compatibility layer that accepts many SQLite idioms, which is a different thing from sharing SQLite's code or file format.
No. There is no direct .sqlite file access and the two file formats are unrelated. Moving an existing database is a one-time migration — export the schema and data from SQLite, reload it into Nano, and verify row counts and checksums on both sides independently. After that the SQLite file is no longer the live database. See the migration guides for the current recommended workflow.
Over the wire, yes — the PostgreSQL and MySQL server listeners handle multiple clients, each with its own session and its own transaction state. Embedded is different: explicit transactions are not independent across callers sharing a handle. See concurrency and transactions.
No — not on the plain embedded API. Explicit transaction state is a single process-global slot per handle, shared across clones, so two threads cannot each hold their own open transaction. Concurrent reads and single-statement autocommit writes are fine. For independent transactions, use server mode.
When callers need independent transaction sessions, or when they need to reach the database over a network. The daemon gives you both today. Remember it is a stop-then-start handoff onto the same data directory, not a live switch.
No. It means your PostgreSQL clients, drivers and tools can connect. Two concrete gaps today: collation is byte-order only with no locale support, and in the stock server row-level security is enforced through the embedded API and the REPL, with enforcement for wire clients planned. Test your queries rather than assuming parity.
Yes. Writes inside explicit transactions reach the logical WAL and replicate — this was broken in earlier releases and has been fixed. The remaining caveat is that a standby can transiently observe a partially-applied transaction, because atomic-apply markers are not emitted yet.
No. Branching isolates storage through copy-on-write and nothing more. It does not sandbox an agent's external side effects — network calls, file writes, API requests all still happen. If you need a containment boundary for agent actions, build it outside the database.
No. Time travel only reaches versions the engine still retains — all history by default, or the retention window you set, after which version GC reclaims older ones — and it lives inside the same store as your live data, so it does not survive losing that store. Use heliosdb-nano dump and restore for real backups. Copying a live data directory is not a consistent backup.
Often. SQLite's maturity, ecosystem and file format are real advantages, and plenty of applications need nothing Nano adds. Rather than compress that into a sentence here, we wrote it out properly: see when SQLite is the better fit →
Use Nano for applications that want a real SQL engine and vector search close to the code, without running a database server for it — and with the option to add one later.
Embed a full SQL engine with vector search and branching directly in a desktop app, shipped as part of your binary with no install step for the user.
Run on edge devices and constrained hosts with offline-first behaviour. Check the binary size against your own target — measure the build you actually ship.
Build RAG pipelines, semantic search and agent memory without provisioning a vector service. Metadata and embeddings join in ordinary SQL.
Run a workload on one host with MVCC, crash-recovery WAL and optional encryption, without cluster overhead. Evaluate it against your workload first.
Serve a PHP CMS over the MySQL wire protocol from the same process that holds the data, removing the separate MySQL server from the deployment.
Nano is Apache 2.0 and developed in the open. The changelog is the most honest record of where the engine is — including its bug fixes.
github.com/HeliosDatabase/HeliosDB-Nano — source, releases and the full CHANGELOG. Current release: v4.41.1.
Rust / CLI: cargo install heliosdb-nano (or cargo add heliosdb-nano to embed it). Python embedded binding: pip install heliosdb-nano-embedded, imported as heliosdb_nano.
Nano documentation, the quickstart, the concurrency and transactions guide, and the REST API reference.
Nano vs SQLite for the full comparison, comparisons across the tier range, and the migration guides for moving an existing application.
Start embedded, add server access when you need it. Apache 2.0, source on GitHub.
Or ask your AI coding agent to install it for you
Copy this into Claude Code, Codex CLI, ChatGPT, Gemini, OpenCode, or any agent with shell access.