Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

.nornir/README-full.md

nornir — complete overview

The real README: the full pitch, quickstart, design, example and measured numbers. The repo-root README.md is the slim face (title · mashup · pointers); this chapter is the heavy detail, and it is also folded into the manual docs/book.pdf by nornir 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

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

Documentation

The full reference — every subsystem chapter — is rendered by nornir docs book into a single manual under docs/ and historized in the warehouse:

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.

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):

  1. NORNIR_ROCM_LIBS — explicit colon-separated lib dirs (highest priority);
  2. $ROCM_PATH / $HIP_PATH → …/lib (+ …/lib64);
  3. 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.