The whole knowledge base in one file

Urna stores text, images and references, already turned into vectors, inside a single .urna. Your app copies that file and answers questions citing the source, with no server, no internet and no API key.
v0.5.1 · open source under MIT · Rust + Python
.csv
.jsonl
.sqlite
.jpg
Island
Basic Land · Island
corpus/
01001001 01110011 01101100 01100001 01101110 01100100IslandBasic Land · Island256 numbers per chunk, from potion
corpus.toml
[source] kind = "sqlite"
[[models]] preset = "potion"
[[models]] preset = "clip-vit-b32"
Island
JPEGAV1
Basic Land · Island
JPEGAV1
103 kB → 13 kBaverage per cardJPEG, source · AV1, in the file
drift p50 0.961
card scans4 GB
.urna533 MB
smaller
100% of exact61% of exact34% of exact26% of exact21% of exact
model_hashclip-vit-b32
HNSW + BM25 → cosine
Island
Forest
Mountain
p50, text to image
Island
Basic Land · Island
urna validateheadersectionsfilecontent
The runtime never opens a socket

A folder and a few lines of TOML become one .urna: chunks, vectors, index, media and manifest, each section signed.

The card scans go into the file as AV1 and the vectors move to a smaller preset. The file shrinks, the citation stays the same.

The model_hash gate checks the model, HNSW walks the graph, BM25 matches the word and exact cosine sets the order.

The answer carries a citation back to the source, and urna validate checks every layer before the first query, offline.

how it works

From a folder of documents
to a file that answers

1
Gather the sources
Text, PDFs, spreadsheets, images. You describe where each thing comes from and which model will read it, in a TOML of a few lines.
2
Generate the .urna
One call does the rest: urna.build in Python, or urna build --spec from a checkout of the repo. It splits into chunks, computes the vectors, builds the indexes and signs every section with SHA-256. The result is a single file, identical byte for byte on any machine.
3
Ask from inside the app
The Rust runtime opens the file with mmap and returns the closest chunks, each with a urna:// citation that leads back to the original text.
Open to audit, the way every database should be
Self-contained, verifiable, reproducible, offline.
The file is the whole database
Chunks, vectors, indexes, media and manifest in the same .urna. Copy it the way you'd copy a SQLite file, version it in git, ship it inside the installer. There's no service to spin up and no index to keep in sync.
Every answer points to a chunk you can check
Every section has a SHA-256, so does the whole file, and so does the decoded content. The citation urna://content_hash/chunk_id stays the same after the text is compressed, and urna cite resolves it to the stored chunk.
Offline, no exceptions
The runtime never opens a socket. The default text model comes in the Python package, or urna setup downloads it once when you install. If the query's model doesn't match the file's, the search stops on the spot instead of returning garbage dressed up as an answer.
in practice

Public benchmark, same ruler

urna against usearch, hnswlib, sqlite-vec and lancedb, on the same machine, with the data and the command to reproduce
0.72 ms
p50
1.02 ms
p99
recall 1.0
0.72 ms per query, recall 1.0
100,000 vectors of 384 dimensions, 200 queries, one thread. The hybrid preset answered with a p50 of 0.72 ms and a p99 of 1.02 ms. It's the only one in the table that checks its own bytes before the first query; that costs about 360 ms at open.
See in the docs: 0.72 ms per query, recall 1.0
38,000 Magic cards in 533 MB
4 GB of JPEG scans became a file 7.46 times smaller, with text, vectors, index and the AV1 images inside, and the search finds the card as often as it does on the lossless file.
See in the docs: 38,000 Magic cards in 533 MB
30,725
chunks
7
datasets
FakeRecogna11.9K
Fake.br7.2K
FakeTrue.Br3.6K
factck-br1.3K
30,000 fact-checking chunks
truw scores claims in Portuguese against seven public fake news datasets. The corpus is a .urna of 30,725 chunks: 120 MB with the exact preset, 31 MB with tiny.
See in the docs: 30,000 fact-checking chunks

Benchmark from 2026-09-10 on arm64, single thread. What Urna doesn't do: in-place update and delete, metadata filters, concurrent writes. See the full table.

benchmarks

The urna benchmarks

Measures compression, search and latency on the 38,627 Magic cards (3.98 GB of JPEG) stored in a .urna file, and urna against usearch, hnswlib, sqlite-vec, and lancedb on the same vectors and the same machine

Compression against search by name

7 files · 1,000 queries each
compression index with no search loss7.46x

times smaller than the 3.98 GB of source JPEG. The file at crf50 weighs 533 MB and finds the card by name as often as the 3.61 GB lossless file: txt@1 at +0.001, within the standard error.

lossless · 3.61 GB
neardup · 1.37 GB
crf50 · 533 MB

What this metric measures

txt@1 is the fraction of the 1,000 queries in which the right card came first. The query is the card's name as text ("artwork of the card {name}"), searched in clip's image space: the model only sees the art, which is why the level is low in every file; what matters is where it drops.

drift is the cosine between the compressed image's vector and the original's: 1.00 is identical, and the lower it goes, the more the codec changed what the model sees. The worst decile (p10) is the card at the edge of the 10% that changed the most; the median (p50) is the card in the middle.

How to read this chart

Each column is a file with the same 38,627 cards, from lossless (3.61 GB) to AV1 at crf60 (230 MB), in order of compression.

At the top, both drift lines fall the whole way and cross the gate floor as early as the second file. At the bottom, search stays flat through crf50: the green bars are within one standard error of lossless; the dashed line marks where search drops, and the red bars are the files past it.

filesizedrift p10txt@1±1 standard errorz vs lossless
lossless3.61 GB0.99680.0920.00910.00
neardup1.37 GB0.96660.0960.00930.31
avif q481.2 GB0.96800.0960.00930.31
crf40981 MB0.95940.0890.0090-0.23
crf50533 MB0.93890.0930.00920.08
crf55370 MB0.92110.0740.0083-1.46
crf60230 MB0.88660.0690.0080-1.89

1,000 queries per file, the same cards in all of them; binomial standard error; z is the distance from each file's txt@1 to lossless in combined standard errors: through crf50, none goes past 1.

measured on 2026-09-12 · benchmark/experiments/14-utility-ladder/results.json

presets
Pick the size. The content stays the same
Five presets combine text compression, vector precision and indexes, plus micro, which cuts dimensions. Three here, all of them in the docs
For questions with acronyms, proper names, rare terms:
Text compressed with zstd
Vectors in float32
HNSW + BM25 as candidates
Rerank by exact cosine
See the preset
Measured on 30,725 chunks. Recall here measures how stable the ranking stays under quantization, not accuracy on real questions. See all five presets and micro.
integration
Installs in one command. Integrates in five lines
A Rust binary with three groups of verbs (engine, agent, setup) and a Python API. After the install, everything below runs with no network
$
Install
installer
$
Build
urna build
$
Ask
urna ask
The installer checks the SHA-256 before writing. urna setup fetches the offline embedder once and ends on urna doctor, which tells you which layer failed, if any did.
curl -sSf https://raw.githubusercontent.com/hoffresearch/urna/main/scripts/install.sh | sh urna setup
Sources, media and model in one TOML, run from a checkout of the repo. The same spec produces the same database on any machine.
urna build --spec corpus.toml
Cited chunks, right in the terminal.
urna ask corpus.urna "does it work offline?" -k 3
JSONL with chunk, source, score and citation, ready for your app or agent.
urna retrieve corpus.urna "does it work offline?" -k 5 --format jsonl
Opens with mmap, returns cited hits. The expected model_hash stops a wrong model before the search.
import urna from urna.embed_potion import potion_embedder emb, db = potion_embedder(), urna.open("corpus.urna") qvec = emb.embed_texts(["does it work offline?"])[0] hits = db.retrieve(qvec, 5, expected_model_hash=emb.model_hash())
Every checksum before shipping: header, sections, file, content.
urna validate corpus.urna
From the citation back to the stored text, with the hashes that prove it.
urna cite corpus.urna "urna://content_hash/chunk_id"
Latency and recall on your machine, on your corpus.
urna benchmark corpus.urna -q 100 -k 10 --ann 100
channels

One release, every package manager

The same urna 0.5.1 on crates.io, PyPI, npm and Homebrew, with a checksum and an attestation next to every file
The same binary everywhere
cargo, pip, npm, bun, pnpm, yarn, brew, the curl one-liner and PowerShell install the same build: macOS and Linux on arm64 and x86_64, Windows on x86_64. npx and uvx run it without installing anything.
See in the docs: The same binary everywhere
tar.xz
zip
sha256
sigstore
SBOM
ssh tag
A release you can check
Every archive comes with its own .sha256 and a sigstore attestation that gh attestation verify checks, plus a CycloneDX SBOM and binaries built with cargo-auditable. Release tags are ssh-signed.
See in the docs: A release you can check
Inside your app, no CLI needed
urna-format and urna-runtime are on crates.io for Rust, and the Python package opens the same file. The repo has fastapi, flask and jupyter examples and a Dockerfile, and cargo install urna --no-default-features leaves an engine-only CLI of 2.1 MB.
See in the docs: Inside your app, no CLI needed
Without installingnpx @urna/cli validate corpus.urnauvx --from urna urna validate corpus.urnacargo binstall urna
in the terminal

Three groups of verbs and a screen to use them

urna tui opens a .urna, asks it questions and checks the install without leaving the terminal. urna setup shows the plan before writing anything and ends on the doctor checks
urna tui on the ask tab: ten cited hits for a question, the stored text of the selected one and its citation
urna tui
urna setup on the verify step, with every check passing
urna setup
engine

A file and a vector in, hits out. No Python.

  • inspect
  • validate
  • stats
  • media
  • search
  • search-ann
  • search-graph
  • search-space
  • search-text
  • benchmark
  • cite
  • doctor
agent

Text or a spec in, cited answers out, through the offline Python embedder.

  • ask
  • retrieve
  • build
setup

The installer every channel ends in, and the terminal explorer.

  • setup
  • tui
Before you start
What usually gets in the way on a first read. The rest is in the docs, with command and output
Read the docs

Your knowledge base, ready to travel

Install, build the corpus, copy the file into the app. The rest is mmap
Install
v0.5.1 · MIT · macOS, Linux and Windows