nornir for dummies — the whole thing, copy-paste
What is nornir? A companion to
cargo. It builds, tests, benchmarks and releases a multi-repo Rust workspace, keeps every fact (deps, tests, benches, releases, symbols) in an Iceberg warehouse, and shows it back in a visualizer and over MCP (so an AI agent can ask it for exact facts). One tool, one warehouse, three ways to look at it.
You point nornir at your Rust repos; it remembers everything it ever learned and draws you the picture. That's it.
Get started in 3 steps
# 1. install it (the visualizer ships with the `viz` feature)
cargo install nornir --features viz # → nornir, nornir-server, nornir-ui
# 2. see where it keeps data, and what repos it knows
nornir root # prints ~/.nornir — that's the whole data root
nornir repos # lists the configured repos
# 3. look at everything you've got
nornir viz # opens the time-travel visualizer
Run nornir viz from your home dir (cd ~) and it shows all your workspaces at once (it talks to your local server). Run it from inside a workspace_<name>/ and it shows just that one, read straight off your disk.
The handful of verbs you actually use
nornir test # run the repo's tests, persist per-test pass/fail to the warehouse
nornir bench # run/record benchmarks (history kept, not just the latest number)
dwarves release <ws> # advise/rehearse/publish — release ORCHESTRATION lives in dwarves now
nornir release trace <repo> # read-only: bisect a regression over recorded release history
nornir release events <repo> # read-only: dump the release-event DAG from the warehouse
nornir release impacted-crates <repo> # read-only: crates impacted by a change (dep-graph reverse walk)
nornir workspace add <name> --descriptor workspace_<name>/nornir-workspace.toml
# register + clone members + build the warehouse, all in one
nornir viz # the visualizer (see above)
nornir root # "where does nornir keep my data?" → ~/.nornir
Everything else (docs, mimir, security, funnel, bakeoff…) is the same shape: a verb, persisted to the warehouse, viewable in the viz. Start with these.
Asking the code "who calls this?" (the knowledge map)
nornir remembers every symbol and every call in your repos, so you can ask it instead of grepping:
nornir knowledge query <repo> callers <name> # who calls this function?
nornir knowledge query <repo> callees <name> # what does this function call?
nornir knowledge query <repo> path <from> <to> # a call chain from one to the other
Two flavours of map answer those, and you don't have to choose — nornir picks the best one it has:
- The instant map (syn). Built from a fast parse of your source on every scan; always there. It matches by name, so two different
name()s look the same and calls across crate boundaries (a binary calling into its library) can be missed. - The resolved map (SCIP). When you've run
nornir scip ingest <repo>(it drives rust-analyzer in-process — minutes the first time, no internet, no shelling out), nornir stores an exact map: each call points at the one real function it resolves to, even across crates. It's stored per git-SHA, so you can ask the same questions about the code as it was at any past commit.
When the resolved map exists, every query above quietly prefers it (it'll say source: resolved/scip); otherwise you get the instant syn answer. Same for the AI agent over MCP and the viz — they read the same map. Picture in design.md; the full story is scip-resolved-knowledge.md.
The 3 modes, in two sentences
Fat = everything runs on your laptop against your local ~/.nornir (no daemon, you pull the trigger). Server = a nornir daemon clones your repos and keeps the warehouse fresh by itself; thin = you run nornir viz (or any verb) with no local workspace and it just talks to that server.
The verbs are identical in all three — only where the source lives and who runs it changes.
Want the daemon without babysitting it? nornir install add turns the server into a systemd service — two flavours, picked by whether you use sudo:
# As YOU (no sudo) — a `systemctl --user` daemon over your own ~/.nornir.
# Stop it → you're fat again; start it → served. Same warehouse, no token to copy.
nornir install add --monitor mine=workspace_mine/nornir-workspace.toml --poll 60s
systemctl --user enable --now nornir-server
# On a server box (sudo) — runs as the dedicated `nornir` system user.
sudo nornir install add --monitor mine=… --addr 0.0.0.0:7878 --poll 60s
sudo systemctl enable --now nornir-server
Reachable from other machines? The server binds whatever NORNIR_SERVER_ADDR says, and that env beats the --addr flag. So after a server-box install, verify it's actually on 0.0.0.0 (not loopback): ss -ltnp | grep 7878 should show 0.0.0.0:7878, not 127.0.0.1:7878. If it's loopback, set the env in the unit to 0.0.0.0:7878 and restart — then nornir viz --server http://<host>:7878 works remotely (loopback-only → Connection refused). The token must be the server user's (sudo cat /home/nornir/.nornir/token for the system service).
The bearer token is just <home>/.nornir/token (the same file the client reads — no /etc/nornir, no env file). nornir install update swaps in a newer binary + restarts (it never fetches from crates.io — you build it); nornir install remove tears it down.
Uninstalling: it nukes the data but KEEPS your key. Always. nornir install remove — no flags, nothing to remember — erases everything nornir stored: warehouses, workspace registry, caches, funnel, jobs. A reinstall starts genuinely clean. What it does not touch is the ssh/ deploy key you registered on Codeberg/GitHub, or the bearer token. That is on purpose: you should never have to go re-register a deploy key on every repo just because you reinstalled. If you genuinely want the key gone too, you have to say so out loud:
nornir install remove # data gone, key + token KEPT
nornir install remove --wipe-identity # ⚠ key + token destroyed as well
After --wipe-identity a reinstall makes a brand-new key, and nothing can be cloned or pushed until you register that new key on every remote. (--purge and --keep-identity still parse but do nothing — erasing the data and keeping the key is unconditional now.)
Full guide: running-as-a-server.md.
Where the data lives
~/.nornir — always, for everyone. No path env-var to set; it's derived from $HOME. One folder per workspace under ~/.nornir/workspaces/<name>/ (its git/, its builds/warehouse/, its funnel/). Run nornir root if you ever forget.
~/.nornir/
workspaces/
registry.redb # the index: which workspaces exist
<name>/
git/ # member source
builds/warehouse/ # this workspace's Iceberg warehouse
funnel/ # this workspace's idea→plan funnel
The built-in crates cache (holger, married in)
nornir has holger baked in — a crates.io mirror + a release-rehearsal registry, running in-process (no separate holger-server to launch). Two endpoints:
/cache— a transparent crates.io mirror. cargo builds through it; misses are fetched upstream once and written into a local znippy archive, so the next build is offline-fast./sparring— a fake crates.io you publish into to rehearse a release (does the version cascade resolve from a registry?) without touching real crates.io.
nornir cache up # bring up /cache + /sparring in-process (Ctrl+C stops).
# Shows as a `holger-proxy` job in the nornir job list + viz.
Point cargo at it (it prints the exact snippet) and your builds go through the mirror. It's the same binary — holger is linked in (the embed-holger feature, on by default; cargo install nornir --no-default-features … drops it for a lean client-only build). The long-running server can host it too: set NORNIR_HOLGER=1 (see running-as-a-server.md).
Under the hood the release flow (now the dwarves release tool — release orchestration moved out of nornir, move=delete) rehearses against /sparring before the real crates.io push: advise → stage --execute (publish into /sparring + build each dependent back out) → publish (ship), advise-then-rehearse in one shot. See release-for-idiots.md (short) or release-rehearsal.md (full + the nordisk runbook).
The viz, pane by pane (what each tab is for)
nornir viz is the time-travel visualizer; the panes worth knowing:
- 🧬 nornir (release dashboard) — the publish graph with every release problem painted ON the node:
⛔held by a transitive pin ·✗edition-2024 fail (both red, blocking) ·▼behind ·🟡dirty (amber, advisory). Each dependency cycle shows a dashed✂ cutedge with the suggested fix. Click a repo → it scopes the release and lights its downstream blast radius. The 🚀 Release button (green only when everything's a go) opens… - 🧙 the release wizard — a gated Advise → Fix → Rehearse → Promote walk. You can't advance past a red step without ticking an override; Fix lists the concrete moves (
bump X,cut A → B,cargo fix --edition); Rehearse runs the/sparringdry-publish; Promote is irreversible and makes you typePROMOTE. - 🧪 Test → 🎬 Saga Sessions — recorded user journeys of your apps. Hit ▶ Replay and nornir re-drives the journey as a headless robot test, one matrix row per step. If a step's button has gone missing, that row goes RED — a UI regression caught with no human looking at a screen (see the worked example below).
- 📦 Holger — the built-in crates registry:
/cache(mirror) +/sparring(rehearsal) with their crate/version counts and the recent/sparringpublishes. A button brings the in-process pair up live. - 🏛 Architecture — six lenses over your code: 🔗 Dep Graph, 🏛 Wiring (the static UI↔gRPC↔core↔table board) vs 📡 Live system (the same board, but auto-refreshing), 📍 Hållpunkter (just the boundary landmark functions), 🕸 Call Graph (the full caller→callee graph), and 🚇 Metro — which is a call-PATH map, not the call graph: pick a button and it draws the single subway line from the click to the gRPC handler it hits.
Worked example — a saga replay filling the matrix
A recorded njord session — navigate to OffertForm, click Submit — becomes a replayable plan and then a robot run. From tests/saga_roundtrip.rs (real, green):
recorded events: nav /offert/7 OffertForm · log "loaded form" · click Submit
→ ReplayPlan: 2 steps (the log line is context, not a step)
→ robot run: suite "saga-replay:sess-rt", aspect "robot-ui", 2 PASS rows
Break the app (delete the Submit widget) and re-run: the nav step still passes, the Submit step turns into a RED matrix row — widget "Submit" not found. That red row IS the regression report; nobody eyeballed a window.
Go deeper
- Where data lives, in full: data-root.md
- The built-in holger cache + release rehearsal: release-for-idiots.md · release-rehearsal.md
- How
nornir vizpicks fat vs thin vs server: viz-modes.md - The three modes — capability matrix: mode-matrix.md
- Running the daemon (install, systemd, tokens): running-as-a-server.md
- Adding a workspace, eagerly populated: workspace-add.md