urna ask
Ask a .urna file a question in plain text and get the stored text of each hit with its urna:// citation, embedded offline and model-gated.
urna ask takes a text query, embeds it offline with the embedder the corpus manifest calls for, checks that embedder against the corpus model_hash, and prints the stored canonical text of each hit with its urna:// citation. It is the verb for a person at a terminal; urna retrieve returns the same hits as JSON for a program.
Usage
urna ask [OPTIONS] <FILE> <QUERY>Arguments
| Argument | Description |
|---|---|
<FILE> | Path to the .urna file. |
<QUERY> | The question, as one argument. Quote it in the shell. |
Options
| Option | Default | Description |
|---|---|---|
-k, --k <K> | 10 | Number of hits to print. |
--disclose <DISCLOSE> | answer | answer: the cited text and its urna:// citation only. explain: also the route, the candidate counts per path, the rerank source and the recall line. |
--embedder <EMBEDDER> | routed by the manifest model | Path to a query-embedder script to run instead of the routed one. It must speak the query embedder protocol. |
--candidates <CANDIDATES> | 4*k, at least 64 | The HNSW beam on an hnsw file, the candidates per path on a hybrid file. Ignored on an exact file. |
--model-path <MODEL_PATH> | none | Local model directory, passed to the embedder as --model-path. For a potion corpus it is the potion table directory; for a registry model, the model snapshot directory. |
-h, --help | Print help (-h prints the summary). |
Behavior
- Opens the file and validates it completely: header and section checksums, footer hash, manifest contract, the NaN and Inf walk over the embeddings, and the index payloads. A file that fails here stops before any Python runs.
- Reads
embedding_model,embedding_dimandmodel_hashfrom the manifest. - Picks the query embedder. A model name that starts with
minishlab/potionrunsembed_query_potion.py; any other name runsembed_query_model.py, the registry embedder.--embedderreplaces the choice. When the manifest recordsfull_dim(a corpus built withmrl_dim), the CLI also passes--mrl-dim <embedding_dim>. - Runs the script under the resolved Python interpreter and prints the choice on stderr as
[urna] embedder interpreter: <path>. Both lookups are described in Query embedder protocol. - Applies the model gate in order: the reported model name equals the manifest name, the reported dim and the vector length equal the manifest dim, the manifest
model_hashis not the all-zero placeholder, and the reportedmodel_hashequals the manifestmodel_hash. Any failure exits 1 before the search runs.askhas no flag to skip the gate. - Routes by the manifest
index_type:hnswruns the HNSW path with--candidatesas the beam,hybridruns the vector and BM25 legs with the query text,exactruns the exact scan. Every path ends in an exact cosine rerank, so every score is a real cosine value. See Search paths and the exact rerank. - Prints the stored canonical text for each hit, highest score first. This is the same text
urna citereturns, never a reopen of the original source bytes.
The installed binary answers potion corpora only
The embedder payload that urna setup and the one-line installers lay down carries embed_query_potion.py and the potion table, and not embed_query_model.py. On a corpus built with a registry model (wemm, jina, clip, siglip2), an installed binary stops with embedder script not found: python/forge/embed_query_model.py (override with --embedder). Run ask from a checkout of the repository, with the model's Python dependencies installed, or pass --embedder pointing at that script in a checkout. See Open a corpus you downloaded and Known limits.
Preset-built files take the HNSW route
Files built with the hybrid preset (the default [build] preset of urna build) declare index_type = "hnsw", so ask never runs their BM25 section. The HNSW beam is also never smaller than the ef_construction the file was built with (400 by default in urna.build and urna build), so a --candidates value below that changes nothing. See Known limits.
Output
stdout carries the answer. For each hit, the stored text, then an indented line with the citation and the source_uri in parentheses, then a blank line. With no hits, ask prints no hits..
stderr carries the interpreter line and, on failure, Error: <message>.
With --disclose explain, four lines and a blank line come before the answer:
| Line | Values |
|---|---|
route: | the path that ran: exact, hnsw or hybrid |
candidates: | exact=, ann=, bm25=, graph= counts, and fusion=none or fusion=rrf for hybrid |
rerank_source: | real cosine when the rerank read float32 vectors, real cosine at stored precision when it read float16, int8 or int4 |
recall: | 1 on the exact path; (not computed; rerank guarantees real cosine) on the others |
Real output on the quickstart corpus:
[urna] embedder interpreter: /Users/nn/.local/share/urna/venv/bin/python
route: hnsw
candidates: exact=0 ann=12 bm25=0 graph=0 fusion=none
rerank_source: real cosine
recall: (not computed; rerank guarantees real cosine)
to keep that promise for a brand-new user, the default embedder is a static, offline embedder that ships with the tool. it needs no model download and no network round-trip on first use, and it is deterministic, so a build is byte-identical and reproducible. a power user can bring a stronger embedding model instead, and the model's fingerprint is recorded so the corpus and the query embedder must agree or the search fails loudly.
-- urna://sha256:1147b2560863331b21bd9d60fe6bdd99507dc34e17108444dc38194f8e6f09df/sha256:eed9a60b68133464e91c831f8af5960491f6c444cf1645fc5e4864435ab4bd44 (demo/04-offline-sovereignty.md)The corpus has 12 chunks, so the HNSW shortlist holds all 12 (ann=12).
Exit codes
| Code | Meaning |
|---|---|
0 | The search ran. This includes a run that printed no hits.. |
1 | Any error: the file failed to open or validate, the embedder script was not found, the embedder exited non-zero or printed invalid JSON, the model gate refused, or the search rejected the query. The message is on stderr. |
2 | Usage error: a missing argument, an unknown flag, or a --disclose value other than answer or explain. |
Examples
Ask for the single best hit:
urna ask examples/quickstart/out/quickstart.urna "can I use this offline" -k 1Show how the answer was found:
urna ask examples/quickstart/out/quickstart.urna "can I use this offline" -k 1 --disclose explainKeep only the answer, without the interpreter line:
urna ask corpus.urna "how do citations work" -k 3 2>/dev/nullPin the interpreter and the potion table for a sealed run:
URNA_PYTHON=/opt/urna-env/bin/python \
urna ask corpus.urna "how do citations work" --model-path /opt/urna/potion-base-8MFor a walkthrough of ask, retrieve and cite together, see Ask and retrieve from the terminal.
CLI overview
The 17 verbs of the urna command line in three groups, the five that cover the whole loop, the global flags, and what a bare urna does on a terminal.
urna retrieve
Query a .urna file with text and get JSON or JSONL of cited spans, each with its exact cosine score, stored text, citation and file hashes.