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

nordisk/nornir

.nornir/cli-client-modes.md

nornir CLI — fat (embedded) vs thin (client) mode

How the nornir CLI decides where its data comes from, and how to drive it as a thin client against a running nornir-server. The CLI mirrors the viz rule (see viz-modes.md) — same gRPC API, same workspace_<name> convention. Added with Rickard, 2026-06-14 (global --server/--token/--workspace

TL;DR

The mode rule

A server target makes it thin; otherwise it's fat:

  1. --server <url> (global flag, any position) or $NORNIR_SERVER set → THIN. A bare host:port gets http:// prepended.
  2. Neither set → FAT/embedded: the verb opens the local warehouse (<home>/.nornir/workspaces/<name>/builds, see data-root.md).

server_target() is just NORNIR_SERVER (the global --server flag sets it in main before dispatch); ~20 verbs check it and call their *_remote gRPC path.

Thin-client invocation

nornir <cmd> --server http://oden:7878 --token <tok> [--workspace <name>]

Workspace resolution (which workspace the command targets)

The nornir-workspace gRPC header is resolved in this order (client_workspace):

  1. --workspace <name> (global flag) / $NORNIR_WORKSPACE — the canonical name = the workspace_<name> suffix (e.g. nordisk, not workspace_nordisk, not the mono-repo dir git). Same names the server's workspaces_list and the viz picker use.
  2. The cwd's workspace_<name> component — standing anywhere under …/workspace_nordisk/… targets nordisk (works even before it has a descriptor).
  3. Unresolved → on a TTY, an interactive numbered picker (from Workspaces.List); non-interactive (pipe/CI), an error listing the served names — never a hang, never a bogus name.

If a resolved name isn't served, the gRPC NotFound is enriched with the served list + a hint (on_server), so a typo points you at the real names.

The full-screen TUI workspace picker (mirroring the viz dropdown over the same gRPC API) is a separate, future front-end — the CLI stays scriptable.

Why a server at all (fat-vs-thin trade-off)

The unified job ledger (jobs.redb)

Every long-running nornir operation (~1s–minutes) — release runs, workspace fetch/republish, docs book/export/render, knowledge scan, deepscan, bench, test matrix, arch generate, index build, vector embed, bakeoff, airgap — records its lifecycle (running → done|failed + kind · target · start/end/elapsed + a result_ref link to the domain result) into one durable list. Added with Rickard, 2026-06-18 (consolidates what used to be ad-hoc per-op tracking).

This is orthogonal to the result tables: Iceberg (bench_runs, test_results, architecture_wiring, doc_exports, clone_events, …) records what an op produced; the ledger records that an op ran. result_ref (e.g. release_events:<run_id>, bench_runs:<uuid>) links the two.

Storage = redb, not Iceberg. A job record is mutable (overwritten in place as it advances) and short-lived, so a redb key/value file is the right tool — no append-only coalescing, no per-event Iceberg snapshot (that small-files antipattern is why Iceberg is wrong here). It's a distinct jobs.redb instance, separate from skade-katalog's catalog.redb and the server's registry.redb, living beside the warehouse. Retention prunes terminal jobs (older than 30d / over a row cap) so a long-lived server stays bounded.

Access mirrors the warehouse exactly — fat vs thin:

The 🧬 nornir viz tab shows the list: the reusable, nornir-agnostic facett-jobview component (a Windows-Task-Manager-style table — kind · target · status · elapsed · detail), fed by mapping jobs::JobRecord → facett_jobview::JobEntry on a ~1.5 s poll (swappable to skade streaming-on-commit later). Closing the viz while jobs are still running pops an "ask at close" confirm.

Source: src/jobs.rs (JobStore / JobHandle / JobRecord / retention), src/bin/nornir.rs (start_job — the fat/thin sink helper), the Ops.* handlers + Telemetry.SubmitJobs + Viz.Jobs in src/bin/nornir-server.rs, src/viz/nornir_tab.rs (the tab), and the facett-jobview crate (the widget).

Source map

See the copy-paste recipes in cli-thin-client-for-idiots.md.