nornir — complete overview
The real README: the full pitch, quickstart, design, example and measured numbers. The repo-root
README.mdis the slim face (title · mashup · pointers); this chapter is the heavy detail, and it is also folded into the manualdocs/book.pdfbynornir docs book.
nornir is project-management with time-travel — your vegvísir (Old Norse: wayfinder) for a multi-crate Rust workspace: ONE append-only, git-SHA-time-travelled warehouse that unifies code-intelligence, releases, tests and a knowledge graph, fronted by a self-discovering completeness gate that goes green only when every surface is test-wired. The crossover of what Maven + Jira should have been.
| Past · Urðr | Present · Verðandi | Future · Skuld |
| the warehouse — all history | guard locks + the step running now | the plan DAG, gates, the release ahead |
Why nornir — it does three tools' jobs (code-intel · dev-portal · build-graph) out of one warehouse — and the rows marked NA are things none of those tools have a concept for at all:
| capability | nornir | Sourcegraph | Backstage | Bazel / Nx |
| Autonom completeness GATE — green ⟺ every surface (CLI · UI · MCP · gRPC · fn) is test-wired | ✅ | NA | NA | NA |
| One warehouse unifying code-intel + releases + tests + knowledge graph, keyed by git SHA | ✅ | NA | NA | NA |
| Time-travel — query or restore the whole workspace as of any past git SHA | ✅ | NA | NA | NA |
| SCIP-resolved call graph living in the warehouse (time-travellable) | ✅ | ◐ | NA | NA |
Robot-testable viz — state_json + inject-assert, a UI a test can drive headless |
✅ | NA | NA | NA |
| CLI ⟺ UI ⟺ MCP parity — every capability in all three surfaces | ✅ | NA | NA | NA |
| Cross-repo code search / intelligence (full-text + semantic) | ✅ | ✅ | ✗ | ✗ |
| Service catalog / dev portal (ownership, docs hub) | ◐ | ✗ | ✅ | ✗ |
| Build graph + remote cache at scale | ◐ | ✗ | ✗ | ✅ |
| Airgap-friendly, single Rust binary, local-first AI over MCP | ✅ | ✗ | ✗ | ✗ |
Rows marked NA: the rival has no equivalent concept — not a missing feature, a missing category. Measured numbers below.
What it does
- Accelerate airgapped AI (models/agents) on large codebases. Every index and the Plan-DAG is served over MCP, so a local, offline agent works a huge workspace by query — no full-repo scans, and no code ever leaves the machine.
- Remember & time-travel. Historize every fact on Iceberg, keyed by git hash — the plan DAG, the full-text index, the dependency graph, bench/release lineage — then restore or query the workspace as of any past release.
- Explore. Full-text (Tantivy BM25) and semantic/vector search side by side — and hybrid, fusing the two by Reciprocal-Rank-Fusion (RRF), with a Graph-RAG expansion along the call graph. The exact-flat cosine index is the oracle; a vendored, rayon-free HNSW ANN (
znippy_zoomies::vann) takes the live dense path at scale. Plus asyn/DWARF knowledge map (symbols, call graph) and dependency blast-radius across the workspace. Code is embedded withjina-v2-base-code(768-dim) and the vectors are materialized in the warehouse, so semantic search at any past git SHA is a warehouse read — never a re-embed or a git walk. Loading a repo's vector index is single-digit milliseconds (≈7 ms for ~120 vectors, ≈15 ms for ~430); embedding runs on CPU (pure-Rusttract) or GPU (ort+ CUDA, ~100× faster) behind oneEmbedderinterface. - Diagrams from data. Because the whole workspace lives in the warehouse, any graph in it can be drawn as static SVG via typst (no Mermaid, no JS): the dependency graph below is generated, and the
nornir-uivisualizer weaves release timelines and cross-repo dependency edges into time-travel threads. - Plan. Connect real artifacts (source files, built binaries) and the inputs that drive them — a prompt or a written requirement (a krav, in any language) — into a Plan-DAG;
topo_readyhands the next ready node to a human or an agent. - Guard & revert. Lock release-critical files (
chmod -w) against chaotic AI agents, models, or teammates, and detect tampering against a recorded manifest. Because every version is keyed by git SHA in the warehouse, time-travel to find the culprit and roll back to a known-good release — the revert itself rides on git. - Release & document. Build, test, and bench-gate every crate, then
cargo publishin dependency order — and (re)generate the README/CHANGELOG and a static website (PDF/HTML via typst) describing exactly what shipped.
Quickstart
# stand in a workspace_<name>/ dir (or add --workspace <name> to any command).
# index a repo into the warehouse — symbols, call-graph, full-text, all keyed by git SHA:
nornir map nornir
# the autonom completeness GATE — exits green ⟺ every surface (CLI/UI/MCP/gRPC/fn) is test-wired:
nornir test coverage nornir
# regenerate THIS README + its dependency diagram, straight from the warehouse:
nornir docs render nornir
Design
- 100% Rust. git via
gix, Apache Iceberg viaiceberg-rustover skade-katalog. - One warehouse, append-only. Every fact is an Iceberg row keyed by git SHA: plan-DAG events, dependency edges, bench results, test outcomes, release lineage, full-text index snapshots, and code facts — symbols / call-edges from
syn, and DWARF symbols read out of built binaries viagimli. - One library, four binaries.
nornir(CLI) ·nornir-mcp(MCP/stdio, featuremcp) ·nornir-server(gRPC/tonic, featureserver) ·nornir-ui(egui time-travel visualizer, featureviz). Build the CLI withcargo build; add--features mcp,server,vizas needed. The CLI runs embedded (opens the warehouse in-process, the default) or as a client of a runningnornir-server— mutually exclusive per warehouse, since one process owns the write lock.
Documentation
The full reference — every subsystem chapter — is rendered by nornir docs book into a single manual under docs/ and historized in the warehouse:
- 📖
docs/book.pdf— the complete manual (PDF) - 📝
docs/book.md— the same, as Markdown
It covers the design overview, docs generation, the Iceberg warehouse, the bench framework, vector/semantic search, and the release & history flow (how a release captures a complete SHA-keyed snapshot of the workspace, and how to read any past release back). The rendered docs get their own full-text index — nornir docs search <repo> "<query>" — kept separate from the code index and time-travelable at any past git SHA.
Example
nornir manages owned repos — the holger artifact server and the znippy compression library — and maps their full build-dependency DAG down through the external crates they don't own, such as Apache Arrow. Inside a repo, cargo owns the workspace (build order and layout); nornir works at the repo level — it finds each repo's root Cargo.toml and only orders across repos. The nornir.toml lists the owned repos and their crates.io publish order; the dependency graph is derived from cargo metadata, internal crates kept separate from external ones:
[repo.holger] # owned — artifact server
remote = "https://codeberg.org/nordisk/holger"
[repo.znippy] # owned — compression library
remote = "https://codeberg.org/nordisk/znippy"
# crates.io upload order: members go up in dependency order — published
# versions are immutable, so it's sequential, not atomic.
publish_order = [
["znippy-common"],
["znippy-compress", "znippy-decompress"], # phase 2, uploaded in parallel
]
nornir release run # test → bench-gate → publish the owned crates, in dep order
nornir docs render holger # regenerate README + dependency diagram (owned + Apache Arrow, …)
Status
Working today: the warehouse, the plan DAG (funnel_* MCP tools), guard, config, bench history + regression gate, dependency-graph oracle, the knowledge map (syn), the Tantivy index + time-machine restore, semantic/vector search (materialized embeddings + git-SHA time-travel, hybrid RRF + HNSW at scale), binary analysis (nornir bin analyze|bloat|checksec|diff|gate|sbom over compiled ELF, persisted into Mimir), the world-coderoom (nornir coderoom ingest|compat|what-breaks|snapshot-diff — Mímir generalized to the crates.io world at symbol granularity, in a dedicated warehouse), docs generation, and the CLI / MCP / gRPC surfaces.
Vector search — measured
Full vectorization of real repos, then time-travel "load a vector blob" (reconstruct the in-memory index for a git SHA straight from the warehouse). Threadripper 3975WX (AVX2) + RTX 4090, f32 model:
| step | holger (121 vec) | znippy (430 vec) |
| load vector blob @SHA (warehouse read) | 6.9 ms | 14.7 ms |
| semantic query (embed + search) | 7.9 ms | 15.0 ms |
| re-index a never-pushed 1-line change | only the touched chunk re-embeds | only the touched chunk re-embeds |
Embedding throughput (same f32 model, one Embedder interface, two backends):
| backend | per chunk | full holger | speedup |
tract CPU (pure Rust) |
~1900 ms | 147 s | 1× |
ort CPU (ONNX Runtime) |
~337 ms | 41 s | ~5.6× |
ort CUDA (RTX 4090) |
13 ms | 1.6 s | ~110× |
Loading a blob is GPU-independent (it's a columnar read); the GPU only accelerates embedding. Unchanged chunks are never re-embedded — a one-line edit at a fresh SHA re-embeds a single chunk and still loads in the same ~7/15 ms.
Enabling GPU embedding (CUDA / AMD ROCm)
Embedding is the one GPGPU-compute step in nornir (populate/nornir vector index). The ort (ONNX Runtime) backend dynamically loads a GPU execution provider at runtime; nothing links against a GPU SDK at build time (ort/load-dynamic), and the backend is chosen at runtime, so ONE binary can carry CPU and GPU paths and fall back to CPU when no GPU is present.
-
NVIDIA / CUDA — in the default build already (
embed-ortis a default feature). Install the CUDA + cuDNN runtime libs;ortrequests the CUDA EP automatically when it finds them, else falls back to CPU. No rebuild needed. -
AMD / ROCm — not default (it turns on
ort'smigraphx+rocmexecution providers, which only make sense on an AMD box). Build with theembed-ort-rocmfeature:
cargo install --path . --features embed-ort-rocm # adds the AMD MIGraphX/ROCm EP
# or, from a checkout:
cargo build --release --features embed-ort-rocm
Runtime prerequisite — ROCm must be discoverable. The ROCm runtime libs (libamdhip64, libMIOpen, librocblas, libmigraphx_c, …) are dlopened lazily; nornir preloads them with RTLD_GLOBAL so the EP's NEEDED sonames resolve. Discovery order (first hit wins):
NORNIR_ROCM_LIBS— explicit colon-separated lib dirs (highest priority);$ROCM_PATH/$HIP_PATH→…/lib(+…/lib64);- known system dirs:
/opt/rocm*/lib,/usr/lib/x86_64-linux-gnu.
ROCm is commonly installed but not on PATH/loader path (e.g. /opt/rocm-7.2.4). Point nornir at it before running:
export ROCM_PATH=/opt/rocm-7.2.4
export PATH="$ROCM_PATH/bin:$PATH"
export LD_LIBRARY_PATH="$ROCM_PATH/lib:$LD_LIBRARY_PATH"
# or, most targeted: export NORNIR_ROCM_LIBS=/opt/rocm-7.2.4/lib
The AMD EP is only requested when both the embed-ort-rocm feature is compiled and the runtime probe finds ROCm installed — on any other box the embedder requests CUDA-then-CPU exactly as before, so a non-AMD machine is unaffected. EP precedence: AMD ROCm → NVIDIA CUDA → CPU (MIGraphX is tried ahead of plain ROCm because it graph-compiles the model). Missing libs are not fatal: ort silently falls back to CPU.