LatticeDB: an embedded graph database that also does vector and text search
Embedded single-file knowledge graph database with vector search and full-text search for AI/RAG apps
At a glance
- What is it?
- LatticeDB is a single-file embedded property-graph database written in Zig, with HNSW vector similarity and BM25 full-text search in the same Cypher query layer as graph traversal, plus a durable event log in the same file. It suits an application that wants retrieval and relationships in one local store, and it does not suit a workload that needs a server, or several writers at once.
- Who is it for?
- LatticeDB fits an application that keeps a knowledge graph, its embeddings and its searchable text in one local file, and that wants a single query language over all three, and it does not fit a service with concurrent writers, a distributed deployment, or a requirement to share a database across processes, because the engine is designed for one owning process on one machine with an embedded single-writer model.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 8 days ago.
- What is it written in?
- Mainly Zig, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One file, one query language, one writer
LatticeDB is an embedded property-graph database with native vector and full-text indexing, and the README summarises it in a single sentence: it is a single-file local database for connected, semantic and textual data, letting you traverse relationships, run vector similarity search and do BM25 full-text search over the same dataset in one engine and one query layer.
The features are listed as five promises, and the first two are the ones that shape everything else. One file means the entire database is a single portable file, with no server and no configuration. One query layer means graph traversal, HNSW vector similarity and BM25 full-text search all go through the same query language.
The third promise is the event log. Durable named streams and a built-in graph changefeed share the same transaction and write-ahead log path as graph writes. That is a design choice with real consequences: because application events and graph changes go through the same WAL, a consumer can subscribe to both from one file and see them in one order, and a crash recovers both.
The fourth promise is the scope limit, stated as the local-first design for one owning process on one machine with WAL-backed durability. The fifth is a performance claim, and it is a claim rather than a measurement, since the README lists 0.13 microsecond node lookups and 0.83 millisecond vector search at one million vectors with 100 percent recall without describing the hardware. Treat those as the publisher's figures rather than as something you can assume on your own machine.
The intended users are the ones the README names: Graph RAG, agent memory and local knowledge tools, and it says these are examples built on the primitives rather than the definition of the engine.
Cypher that mixes a vector distance with a text match and a traversal
The single query layer is best shown by the example the README opens with, which finds chunks similar to a query vector, traverses to their document and then to the author, and filters on a full-text match at the same time.
// Find chunks similar to a query, traverse to their document, then to the author
MATCH (chunk:Chunk)-[:PART_OF]->(doc:Document)-[:AUTHORED_BY]->(author:Person)
WHERE chunk.embedding <=> $query_vector < 0.3
AND doc.content @@ "neural networks"
RETURN doc.title, chunk.text, author.name
ORDER BY chunk.embedding <=> $query_vector
LIMIT 10Three things are happening in one WHERE clause. The `<=>` operator is the vector distance, and the same operator is reused in the ORDER BY to rank by closeness, which is the usual HNSW pattern of combining an approximate nearest neighbour predicate with a similarity ordering. The `@@` operator is the full-text match, here on the document content. The MATCH pattern does the traversal.
That combination is the point of the project. A typical retrieval setup for an LLM application keeps a vector store and a relational store side by side, or a graph store and a vector index, and then the application code has to decide which one answers and how to reconcile the two sets of identifiers. Here the chunk, the document and the author are nodes with edges, the embedding is a property of the chunk, and the text index was declared on the same property, so one statement can require a match, a semantic neighbourhood and a keyword.
The cost of that convenience is that the query planner now has three kinds of predicate to combine, and a cheap graph filter is not always cheap when it has to be evaluated across a vector index. The README does not document how the engine orders those filters, so anyone with a large graph and a selective vector search should test the shape of their query rather than assume it behaves like a pure graph engine.
Five language bindings, one native library
LatticeDB is written in Zig and ships bindings for Python, TypeScript, Java and Go, alongside a CLI. The install path for each is a one-liner.
pip install latticedbnpm install @hajewski/latticedbcurl -fsSL https://raw.githubusercontent.com/jeffhajewski/latticedb/main/dist/install.sh | bashThe Python and npm packages are named latticedb and @hajewski/latticedb, and the CLI comes from a shell script in the dist directory of the repository.
All of these bind to the same native library, liblattice. The README says published wheels are expected to bundle liblattice on supported platforms, and that source installs can also bundle a staged native library during wheel builds with LATTICE_BUNDLE_LIB_DIR=/path/to/lib. The TypeScript package has the equivalent behaviour, with the additional step LATTICE_BUNDLE_LIB_DIR=/path/to/lib npm run bundle:native for a source checkout.
Two platform notes are specific enough to matter before you commit to a binding. Electron applications run the bindings in the main process and must unpack the native library from app.asar, with a link to the Electron section of the TypeScript binding README. And the Java binding requires JDK 21 or later, with a Maven build that compiles the JNI bridge and stages liblattice from zig-out/lib, plus a runnable knowledge graph example in the Java source tree.
The Go binding is the one with the least specified path. The README refers to the Go binding README for the current cgo workflow, says the default consumer path uses installed pkg-config metadata, and notes that in-repository development can use -tags repolocal against zig-out/lib. In other words, the Go path is a from-source build with a local tag, and the other languages are package installs. A Go user should read that README before assuming a pip-like experience.
The hash embedding in the examples is a placeholder, and it shows
The Python example in the README is worth reading carefully, because the project is unusually candid about a trap in its own sample code.
The examples use the built-in hash_embed, hashEmbed or HashEmbed helper so that they run with no external service, and the README says what that means: it is a deterministic placeholder, not a semantic embedding. Similar text does not produce nearby vectors, so a distance threshold is arbitrary and a similarity query may match nothing.
from latticedb import Database
from latticedb.embedding import hash_embed
with Database("knowledge.db", create=True, enable_vectors=True, vector_dimensions=128) as db:
# --- Build the graph ---
db.create_node_fts_index("Chunk", "text")The instruction that follows is explicit: use a real embedding model for anything where the results should mean something, with a link to the Working with Embeddings guide.
That warning is a good sign about the documentation and a bad sign about copy and paste. A reader who copies the example, replaces the placeholder with their own embedding function, and forgets to reindex the existing vectors gets a database that returns confidently wrong neighbours. The engine has no way to detect the mismatch, because a hash embedding and a model embedding are both just float vectors of the right dimension. Any evaluation of this project should therefore use a real embedding model from the start rather than starting with the placeholder and migrating later.
The example also shows the shape of the API. The database is opened as a context manager with create, enable_vectors and vector_dimensions flags, a full-text index is declared on a node label and property with create_node_fts_index, writes happen inside a db.write() transaction that is committed explicitly, and reads happen through db.query with named parameters. There is a separate db.fts_search method for pure full-text search, and the aggregations use a RETURN clause with count and an ORDER BY on the aggregate.
The single-writer model is the boundary everything else sits against
Two design decisions constrain what you can build on LatticeDB, and both are stated rather than buried.
The first is the single-writer model. The README describes the engine as embedded with a single-writer model and designed for one owning process on one machine. That is the natural consequence of the one-file design, and it is also what makes the zero-configuration claim true, since there is no coordination protocol to run. It also means the usual answer to a database scaling question does not apply here. There is no reader scaling story and no failover story, because there is no second process involved.
For a desktop application, an embedded tool, a local agent, or a service that owns its own data, that is a feature. For anything with several writers, it is a disqualification, and no amount of tuning changes it.
The second decision is the durability model. The README says WAL-backed durability, and the event log and the graph changefeed share the same transaction and WAL path as graph writes. The practical consequence is that the file is the unit of trust. If the file is copied, the copy is a consistent database, and if the file is corrupted, everything in it is gone. There is no separate changefeed service to reconnect to, and no separate stream to rebuild from.
That design is coherent, and it is a real difference from the usual architecture where a database server, a change stream and a search index are three pieces of infrastructure to deploy. It is also a difference in what you give up. A deployment that needs its search index rebuilt from the source of truth, or needs another service to replay events, is not the shape this engine is for.
Versioning, bindings churn, and what a project this young still has to prove
The release history shows a project in active development. The recent versions are v0.15.0 from 2026-08-29, v0.14.0 from 2026-08-26 and v0.13.0 from the same day, so three releases inside four days, all at the 0.x level, and the last push was on 2026-10-15.
Two consequences. The first is that the version number carries no compatibility promise in the usual sense, and a 0.15 to 0.16 step can carry the kind of change that would be a major version elsewhere. The second is that the bindings move fast. The README states that recent binding surface cleanups moved embedding helpers into dedicated modules and subpackages, and points to a client API migration document for the preferred imports and the current compatibility aliases. That is a project being explicit about its own churn, which is helpful, but it also means the import path you write today is not necessarily the one the next release wants.
The project is MIT licensed, the homepage is latticedb.org, and the source is Zig with a build.zig and a build.zig.zon, so a from-source build goes through the Zig build system rather than through the package manager of the target language.
The repository layout also suggests a serious approach to a young project. There is a conformance directory, a tests directory, a book directory, a website, a docs directory, a scripts directory, and an AGENTS.md and CONTRIBUTING.md. A conformance suite is the notable one, because it is the kind of thing a project adds when it intends to claim that its query semantics match a specification, and that is a much better answer to the question of whether the engine behaves predictably than any performance figure.
Editorial conclusion
LatticeDB fits an application that keeps a knowledge graph, its embeddings and its searchable text in one local file, and that wants a single query language over all three, and it does not fit a service with concurrent writers, a distributed deployment, or a requirement to share a database across processes, because the engine is designed for one owning process on one machine with an embedded single-writer model. Before adopting it, read the query example in the README and check the syntax against your own plan, and decide early whether the hash embedding helper in the examples is acceptable, because the documentation states plainly that it is a deterministic placeholder rather than a semantic embedding, so a distance threshold against it is arbitrary and a similarity query may match nothing.
Frequently asked questions
What is LatticeDB and what does it store?
It is an embedded single-file property-graph database with native vector and full-text indexing, built for connected, semantic and textual data in one engine and one query layer. The README names Graph RAG, agent memory and local knowledge tools as example workloads built on those primitives.
How do I install LatticeDB?
The Python package installs with pip install latticedb, the TypeScript package with npm install @hajewski/latticedb, and the CLI with the curl command that fetches the install.sh script from the repository. Java bindings require JDK 21 or later, and the Go binding uses a cgo workflow documented in its own README.
Does the hash_embed helper in the LatticeDB examples produce real semantic vectors?
No. The README states it is a deterministic placeholder rather than a semantic embedding, so similar text does not produce nearby vectors, a distance threshold is arbitrary and a similarity query may match nothing. It exists so the examples run with no external service, and the README points to a real embedding model for anything where the results should mean something.
Can multiple processes write to a LatticeDB database at the same time?
The engine uses an embedded single-writer model and is designed for one owning process on one machine, with WAL-backed durability. Durability comes from the write-ahead log, and durable named streams and the graph changefeed share that same transaction and WAL path as graph writes.
Which query languages does LatticeDB support?
A single query layer covers graph traversal with MATCH patterns, HNSW vector similarity through the <=> operator, and BM25 full-text search through the @@ operator. The example in the README combines all three in one WHERE clause and reuses the vector distance operator in the ORDER BY to rank results.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jeffhajewski-latticedb)