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

nordisk/nornir

.nornir/nornir-for-dummies.md

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:

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:

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:

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