black-rainbow-labs / inillucent
Embedded SQL database with full-text and vector search, a sqlite3-shaped shell, and an MCP server.
Package info
github.com/Black-Rainbow-Labs/Inillucent
Language:Rust
pkg:composer/black-rainbow-labs/inillucent
Requires
- php: >=8.1
- ext-json: *
Requires (Dev)
None
Suggests
- ext-ffi: Lets a future in-process binding load the C ABI directly. The CLI wrapper needs nothing beyond ext-json.
Provides
None
Conflicts
None
Replaces
None
README
An embedded database for agents, written in Rust. It runs SQLite's SQL dialect 330% faster than SQLite does, and it holds vector search and keyword search in the same file. A local AI agent can query a body of written material by meaning and by exact term without standing up PostgreSQL, pgvector and an embedding server.
inillucent.com · Documentation · Install · Docs in this repository · Client libraries
It is one library and one file. There is no server to start, no port to configure, no connection
string, and no network hop between your application and its index. One .rdb file holds ordinary
tables, a full text index and a vector index, and all three commit and roll back together.
| 330% faster than SQLite 3.53.4 | the same ten workload families at 100,000 rows | Performance |
| 70% less processor time | 390 ms against SQLite's 1,320 for the same plan | Performance |
| 403 of 416 SQL cases byte for byte, none refused | every case run through both engines and compared byte by byte. Of the thirteen that differ, six are vector search features SQLite has no equivalent for | SQL support |
| Better than pgvector on 15 of 17 graded comparisons, worse on none | both engines reading identical vectors | Retrieval quality |
| 14% more memory than SQLite | 42.4 MiB against 37.2. The one measurement SQLite still wins | Performance |
Performance carries every figure with its 95% interval, and names the six workloads that are slower than SQLite along with what each one costs.
Install
Pick one. Every command here installs the same four programs - inillucent,
inillucent-shell, inillucent-mcp and inillucent-migrate.
Windows
irm https://inillucent.com/downloads/install.ps1 | iex
macOS and Linux
curl -fsSL https://inillucent.com/downloads/install.sh | sh
Both download the archive for the machine, check its SHA-256 against the
published SHA256SUMS, and put the programs on PATH. Nothing is written
outside your home directory and neither needs administrator rights.
From a package manager
| Homebrew | brew install black-rainbow-labs/inillucent/inillucent |
| npm | npm install -g inillucent, or npx inillucent help with nothing installed |
| pip | pip install inillucent - the wheel carries the programs and an in-process driver |
| cargo | cargo install inillucent-cli - builds from source, and works on any platform |
| Go | go install github.com/Black-Rainbow-Labs/Inillucent/packages/go/cmd/inillucent-install@latest && inillucent-install |
| Composer | composer require black-rainbow-labs/inillucent && vendor/bin/inillucent-install |
From a download
Everything is at inillucent.com and on the GitHub release.
| macOS installer | inillucent-<version>.pkg - signed with a Developer ID and notarised by Apple, so it opens with no warning. Universal: Apple silicon and Intel |
| Debian, Ubuntu | inillucent_<version>_amd64.deb, inillucent_<version>_arm64.deb - signed with the project's OpenPGP key |
| Fedora, RHEL | inillucent-<version>.x86_64.rpm, inillucent-<version>.aarch64.rpm |
| Archives | .zip for Windows, .tar.gz for macOS (universal) and Linux (x86-64 and aarch64) |
Checking what you downloaded
SHA256SUMS names every published file, and SHA256SUMS.minisig signs it:
minisign -Vm SHA256SUMS -p inillucent.pub # inillucent.com/downloads/inillucent.pub
sha256sum -c SHA256SUMS --ignore-missing
The install scripts already check the SHA-256; the signature is there for anyone who wants to confirm the checksum list itself came from this project.
Building it yourself
git clone https://github.com/Black-Rainbow-Labs/Inillucent cargo install --path Inillucent/crates/inillucent-cli
Minimum macOS is 13.0. Linux builds need glibc 2.28 or newer.
A first database
inillucent create app.rdb inillucent --db app.rdb exec "CREATE TABLE note (id INTEGER PRIMARY KEY, body TEXT)" inillucent --db app.rdb exec "INSERT INTO note (body) VALUES (?1)" --params '["hello"]' inillucent --db app.rdb query "SELECT * FROM note" --output json inillucent help
Four programs come out of an install or a build:
inillucent |
the command line: 30 verbs, and --output json on every one of them |
inillucent-shell |
an interactive shell shaped like sqlite3, with 63 of its 65 dot commands |
inillucent-mcp |
28 of the same commands served to an AI agent over MCP |
inillucent-migrate |
builds a database from a SQLite file, a running PostgreSQL or MySQL server, or a legacy retrieval index |
Getting started covers all four, the exit codes, and the JSON a binding sees.
A first search
examples/rag-agent/ is a database of Greek philosophy that is
already built and already embedded: 80 Wikipedia articles, 2,661 passages, a 768 dimension vector on
every one of them, and a BM25 index, committed. There is no vector index on that table on purpose:
2,661 passages is an exhaustive cosine over 8 MB of vectors, and that example's readme says when to
build one. Install the embedding model, point a coding agent at that directory, and ask it a
question:
inillucent setup-embeddings all # ONNX Runtime and the weights, about 620 MB, once inillucent --db examples/rag-agent/greek-philosophy.rdb query \ "SELECT title, body FROM passage ORDER BY vector_distance_cos(v, embed('search_query: ' || ?1)) LIMIT 5" \ --params '["who was Seneca"]'
No corpus to download, nothing to index, no embedding server. It is the shortest answer to "show me this doing retrieval".
embed(TEXT) needs a binary built with the embedding feature, and the published 0.1.2 archives
carry it. packaging/release-all.ps1 passes --features inillucent-cli/embed. The 0.1.1 archives
were built without it, and answer no such function: embed with the 620 MB already downloaded, so a
copy installed before 0.1.2 has to be replaced. inillucent --version says which one is installed. A
checkout builds the command line with the feature as well:
cargo build --release -p inillucent-cli --features inillucent-cli/embed
Client libraries
An application calls the engine in its own process through the C ABI. The eight client libraries have their own repository, Black-Rainbow-Labs/inillucent-clients, which is public and holds the source for all eight beside the conformance suite each one is graded against. None of the eight packages below is on a registry yet, so none of these install lines works today. They are what the packages will be named.
The Rust row is the exception, and it names something that exists: the crate is
drivers/inillucent-driver in this repository and it would publish under that
name. The other seven are names nothing answers to yet.
| TypeScript | npm install inillucent-client |
| JavaScript | npm install inillucent-client |
| Python | pip install inillucent-client |
| Rust | cargo add inillucent-driver |
| Go | go get github.com/Black-Rainbow-Labs/inillucent-clients/go |
| Java | com.inillucent:inillucent-client |
| C# | dotnet add package Inillucent.Client |
| PHP | composer require inillucent/client |
What exists today, and is installed by every route in Install:
- The C ABI,
libinillucent_driver_capi, with its header in the archive'sinclude/. The driver documents it. - A reference Python binding at
drivers/bindings/python/inillucent.py, which thepippackage ships as its in process driver. - The Go module at
packages/go. Its tags are pushed andproxy.golang.orgserves it - see From Go.
import { connect } from 'inillucent-client'; const db = connect('app.rdb'); db.execute( 'INSERT INTO person (first_name, last_name, email) VALUES (?1, ?2, ?3)', ['Ada', 'Lovelace', 'ada@example.com'], ); for (const person of db.query('SELECT first_name, last_name, email FROM person')) { console.log(person.first_name, person.last_name, person.email); } db.close();
The API is meant to be the same in all eight: rows come back as objects keyed by column name, values
stay typed, NULL is never the empty string, and a result carries an exact total beside the rows a
limit handed back. A statement the engine has not implemented fails as unsupported and names the
construct, instead of failing as though the SQL were wrong.
drivers/conformance/suite.json is how a binding is graded: the
same 17 cases this repository's own Rust driver runs, so a binding passes when it agrees with the
engine. The driver is the C ABI underneath, for anybody writing one.
What it does
SQLite's SQL, on its own storage. Joins, common table expressions including recursive ones,
triggers, foreign keys with all five referential actions, ATTACH, partial and expression indexes,
RETURNING, ON CONFLICT DO UPDATE, 190 built in function names,
the 68 pragmas this engine recognises. 416 cases were run
through this engine and through a pinned sqlite3 3.53.4 over a fresh database each, and every byte
of both streams compared: 403 produce SQLite's exact bytes, none are refused and 7 answer
differently. Window functions were the last twelve to close: OVER (...), PARTITION BY, the
ROWS, RANGE and GROUPS frame clauses, every EXCLUDE bound and all eleven window-only
functions now match the pinned SQLite exactly.
This figure has read 403 before, so you may remember a different number for it. It was first measured at 403 through a shell that still ran an engine this project has since retired. Re-measured against the engine that ships, it read 391, because twelve window function cases were reaching a pipeline builder that refused them. The window path is connected now, and the probe reads 403 again against the shipping engine. → SQL support
Vector search in the same file. A VECTOR(N) column, vector_distance_cos, vector_distance_l2
and vector_dot, CREATE INDEX ... USING inillucent_hnsw, and a planner that turns
ORDER BY vector_distance_cos(v, ?) LIMIT k into a probe of that index. Recall against an exhaustive
cosine is 1.000. An index minimises cosine unless it was declared WITH (metric = 'l2'), and a
query whose distance function does not match its index's metric plans as a scan rather than answering
out of a structure that ranked by something else. → Vector search
Keyword search that finds the identifier a user typed. BM25 over an inverted index, with
stemming, identifiers kept whole, and weights for coverage, proximity, phrase order and prefix. Fused
with the vector results, so PROJ-1932 and "how does the release process work" are both answerable
by one query. → Vector search
An answer of "nothing here answers that". Every hit carries a confidence computed on absolute bounds, separate from the score that ordered the list. Asked questions the corpus does not answer, PostgreSQL with pgvector returns a confident top result every single time; inillucent does it on about one question in a hundred. → Retrieval quality
The embedding model inside your process. One command installs it on Windows, macOS or Linux.
inillucent setup-embeddings all fetches ONNX Runtime and nomic-embed-text-v1.5, checks every byte
against a pinned digest, and leaves embed(TEXT) answering with nothing exported by hand, in a
binary built with the embedding feature. It runs at full precision, on the processor or across
several GPUs. There is no embedding server, no socket and no second thing to keep alive. Three
profiles decide when the weights are in memory, because loading them costs 800 ms and an embedding
costs 12 ms. → Embeddings
A way in from whatever you already have. inillucent migrate reads a SQLite file, or a running
PostgreSQL or MySQL server over its own wire protocol inside one repeatable read snapshot, so every
table and the schema are as of one instant. The source is never written to, the destination is never
overwritten, and every table is checked by row count and by an order independent digest before
anything is published. → Migrating
For AI agents
AGENTS.md is the front door for an agent, and it splits the two audiences: using
inillucent, and changing it.
agent-skills/ is a task shaped page per job, each a plain SKILL.md
directory that can be symlinked into ~/.claude/skills or read as Markdown by anything else:
| skill | when to open it |
|---|---|
inillucent-quickstart |
install it, make a database, run SQL, read the JSON, understand the exit codes |
inillucent-query |
explore and query a database somebody else built |
inillucent-migrate |
bring in a SQLite file, a PostgreSQL database or a MySQL database, verified |
inillucent-search |
full text and vector search: VECTOR(N), HNSW, FTS5, hybrid |
inillucent-embed |
put it in an application, from Rust, Python, Node, Go, PHP or C |
inillucent-mcp |
give an agent a database over MCP, safely |
inillucent-troubleshoot |
it did something you did not expect |
inillucent-develop |
change this repository |
To serve a database to an agent over MCP:
{
"mcpServers": {
"inillucent": {
"command": "inillucent-mcp",
"args": ["--db", "app.rdb"]
}
}
}
The 28 MCP tools are generated from the same command table the command line reads, so the two cannot
drift apart. --readonly refuses every statement that changes something, decided by the binder
rather than by reading the text. --root DIR refuses every path that resolves outside one
directory, with junctions and symbolic links followed. It is enforced in the VFS, so
ATTACH DATABASE, VACUUM INTO, backup, import and every other file a request opens are covered
by the same decision.
In production
A Gmail assistant with 598,560 passages across 64,172 messages and 2,287 attachments ran on PostgreSQL with pgvector and PostgreSQL full text search. It now runs on inillucent, with no other database in the process:
| before | after | |
|---|---|---|
| median semantic search | 33.7 ms warm, 80.6 ms cold | 4.41 ms |
| recall of the correct top 100 | 0.899 | 1.000 |
| real questions the keyword branch answered with nothing | 57% | 0% |
| index inside the database | 3,167 MB of a 5,849 MB database | deleted |
The table above is the retrieval move. A second move took the record off PostgreSQL as well: 16 tables and 1.63 million rows, with no other database left in the process. By then the same mailbox had grown to 602,022 passages. That one found six query shapes that go quadratic on this engine, and one recovery failure to read before a migration. Both are written up in full, with what each cost and how each was fixed: Removing PostgreSQL from a 5.8 GB Gmail assistant.
Documentation
docs/README.md is the index. The pages, in the order a new reader wants them:
| Product overview | what inillucent is, who it is for, and the case for it against PostgreSQL with pgvector |
| Glossary | every word this documentation uses that a general programmer would not know, one sentence each |
| Getting started | install, the four programs, a first database, the exit codes |
| Architecture in one page | both engines in one diagram, one query across both halves, where the bytes live |
| SQL support | what runs, what differs from SQLite, and what is refused by name |
| Pragmas | every pragma this engine recognises, generated from the register |
| Vector search | VECTOR(N) columns, HNSW indexes, inillucent_search, hybrid ranking |
| Embeddings | the embedding pipeline, running it on GPUs, comparing models |
| Migrating | from a SQLite file, a PostgreSQL server or a MySQL server |
| The retrieval engine | how searching by meaning works, from first principles |
| The relational engine | the SQL half: storage, transactions, the log, recovery, backup, budgets |
| Performance | against SQLite: speed, processor time, memory, disk |
| Feature comparison | the full 416-case probe, feature by feature |
| Retrieval quality | against pgvector, and how the grading decides a verdict |
| Where the vectors live | held in memory or read from the file, and what each costs |
| Roadmap | what is not there yet, in the order it is being worked |
| Closed items | what came off the roadmap, with the measurement that closed each |
| Repository | the crates, building it, and running the tests |
| Dependency policy | what a production crate may link, and why the list is short |
| Synthetic corpus | building the public corpus every retrieval number is measured on |
Limits
- One writer at a time, and the readers never block it. Several processes can share one file
under
PRAGMA locking_mode = normal; the default isexclusive, because releasing the file between statements has to re-read the meta record before every one. Measured when the headline stood at 3.78x,normaltook it to 3.03x. Threads inside one process are not supported. - The file format is this engine's own. SQLite files are imported, not opened. A SQLite
application moves its data across once with
inillucent migrate. - Six of the thirty workloads are slower than SQLite: building an FTS5 index (69% slower),
compiling
SELECT 1on every call (100% slower), a 2,000 row insert batch (43% slower), a join over an index range (11% slower), the same shape as a plain range scan (8% slower) andjson_extract(4% slower). Performance says what each one costs and what is being done about it. 2,000 updates in one transaction used to lead this list at 669% slower; it is now 270% faster. - On Linux the same binary measured 53% faster where Windows measured 279% at the time. That difference was traced to what SQLite pays the operating system on each platform rather than to anything this engine does differently there, and the finding is in Performance. The Linux arm has not been re-measured since the Windows headline reached 330%.
- Publishing a retrieval generation costs the whole corpus. Adding content folds each new row
into the published generation. Writing the generation still reads and writes the full index,
however few rows changed, because a generation is one serialised structure. A build from scratch,
which is what
INSERT INTO t(t) VALUES('compact')asks for, is 132.6 s over 185,078 passages on one thread. - There is no macOS archive yet, because each platform's archive is built on that platform.
Building it
cargo build --release
Then Repository for the crate layout, the test runner, and how every number in this documentation is reproduced.
Licence
MIT. See LICENSE.