11 — dwarves on oden: the door the other lanes can ASK
Date 2026-09-20 · Box oden (32 cores, 499 GB RAM) · Branch stinky/dwarves
The owner: "få gärna liv i dwarves … med nordisk_workspace och mcp-verktyg — den analyserar allt du behöver", then, the same day: "om jag bad dig få igång dwarves på loki — så stäng av den. Istället: refresha (bygg om, cleana allt) dwarves på oden."
The reason it was worth the hour: on this one day several lanes each re-derived the constellation by hand — which repo declares which variant, what a row resolves to, which repos still have no code recipe — by opening .nornir/build-variants.toml files one at a time. dwarves buildthing inventory has answered all three in one second since August. The capability was there; the door an agent takes was not. It is now.
Everything below is labelled MEASURED (I ran it) or READ (I read it).
1. WHAT RUNS ON ODEN NOW
| thing | as bra | detail |
dwarves-server.service |
done | systemd SYSTEM unit, User=dwarves, ExecStart=/home/dwarves/.dwarves/software/current/dwarves-server/dwarves-server, DWARVES_SERVER_ADDR=0.0.0.0:7900, warehouse /home/dwarves/.dwarves/warehouse |
| version | new | was v0.1.7 (4c748370f1fd), generation g4c74837, installed 2026-09-10 · now v0.1.7 (0088060f3640), generation g0088060, 2026-09-20 |
/usr/local/bin/dwarves |
new | was v0.1.7 (ca28e986c8ba) (ten days stale) · now v0.1.7 (0088060f3640), sha256 6d096650b45d… |
/usr/local/bin/dwarves-server |
new | v0.1.7 (0088060f3640), sha256 ee612b2fbe95… — installed so any user can run dwarves-server mcp; the service copy under /home/dwarves is not world-readable |
| embedded nornir | done | 127.0.0.1:7878, same process |
MEASURED, in order:
cargo clean # 85 409 files, 71.4 GiB reclaimed — the "cleana allt"
cargo build --release -p dwarves --bin dwarves-server --no-default-features \
--features embed-all,read-fat,nornir/embed-ort,build-image,appliance-iso,container-oci
# 4m23s, exit 0
cargo build --release -p dwarves --bin dwarves --no-default-features \
--features cli,cli-install,vault,release,read-fat,rehearse,build-image,appliance-iso,container-oci
# 44s, exit 0
sudo dwarves install --prefix /usr/local/bin
sudo dwarves install --from …/dwarves-server --as dwarves-server --prefix /usr/local/bin
sudo env CARGO_TARGET_DIR=… CARGO_HOME=… HOME=… dwarves-server --refresh --system
--refresh is dwarves' own rollback-safe door: it builds the declared dwarves-server variant from HEAD (never a feature string typed at the prompt), packs it, lands it as a new generation, flips current, restarts and health-checks. MEASURED after: current -> g0088060 is still a symlink, still dwarves:dwarves, service active, dwarves doctor --server http://127.0.0.1:7900 → dwarves-server ok · v0.1.7 (0088060f3640) · 0 pipeline(s) · 7 run(s).
rehearse IS compiled in (MEASURED): dwarves workspace-release <workspace_nordisk> --rehearse started the Gate-B /sparring rehearsal instead of answering Gate B … is COMPILED OUT of this build. The eight generations under software/ are the rollback set; nothing was deleted.
2. THE MCP DOOR — how another agent asks
MCP is a capability of the SERVICE, served on stdio by dwarves-server mcp. There is no MCP port: the agent spawns the process and speaks JSON-RPC to it.
From an agent working on oden — /home/rickard/git/.mcp.json (written 2026-09-20):
{ "mcpServers": { "dwarves": {
"command": "/usr/local/bin/dwarves-server",
"args": ["mcp"],
"env": { "DWARVES_SERVER": "http://127.0.0.1:7900" } } } }
From an agent on another box (t14s, loki) — same tools, over ssh:
{ "mcpServers": { "dwarves": {
"command": "ssh",
"args": ["-o","BatchMode=yes","oden","/usr/local/bin/dwarves-server","mcp"] } } }
⚠ The lenses resolve the constellation from the process's cwd (NORDISK_ROOT, else the side-by-side repo dir). Start the server with cwd /home/rickard/git on oden, or the roster comes back empty.
13 tools, MEASURED from a real tools/list:
| tool | what it answers |
buildthing_inventory |
every declared build thing in the constellation + the ROAD (kod/toml) |
buildthing_search |
term → which repo declares a variant matching it |
buildthing_info |
one product's full ladder: variant → features → settings → typ |
buildthing_resolve |
(product, suite, variant) → the exact cargo line |
release_plan · release_publishable · release_drift · release_gates · release_verification |
the release lenses (offline + live crates.io), publish nothing |
release_publish |
the one ACT; dry-run unless the server carries DWARVES_MCP_ALLOW_PUBLISH=1 and confirm = PUBLISH-TO-CRATES-IO, and Gate B still runs |
funnel_submit_idea · funnel_decompose · autonom_kick |
the funnel/intake legs |
Worked example — "what does mvp-server resolve to?"
tools/call buildthing_resolve {"product":"holger","suite":"MVP","variant":"mvp-server"} MEASURED on oden:
buildthing holger: /home/rickard/git/holger — resolved by child of cwd
delivery 'MVP' — 1 variant(s):
mvp-server server service holger-mvp
cargo build -p holger --bin holger-mvp \
--features mvp-server,appliance,auth-passkey --no-default-features --profile release
That is the whole point of a describe verb: not "an atom called mvp-server exists" but what will actually be compiled.
Worked example — the roster
tools/call buildthing_inventory {} MEASURED on oden:
SUMMA ok 19 repo · 140 varianter · kod 3 · toml 16 · drift 0 · vägrad 0
kod = the repo's Rust <repo>-build-thing recipe was read (the source of truth); toml = only the emitted .nornir/build-variants.toml was readable and the code road was refused, always by name. drift 0 = where both roads were readable they agreed.
3. WHAT IT CANNOT ANSWER — read this before trusting it
- It is not a deployment oracle. It reads declarations and manifests. What is actually installed, what is running, which generation a box carries, whether the twin is up — none of that. Ask the box.
- The roster is box-dependent, and the number moved. Same commit, same day: t14s answered
21 repo · 142 varianter · kod 5, oden19 repo · 140 · kod 3. Nothing drifted — akodroad needs the repo's build-thing crate to compile to wasm32 inside the pinned toolchain image, and the image set differs per box. Treatkod/tomlas a fact about this box today, not about the repo. dwarves deps <dir>counts worktrees as repos. MEASURED on t14s:85 repo(s)and dozens of "produced by multiple repos — ambiguous, excluded" lines, becausedwarves-laneB,gunnar-wt-open,holger-wt-arch… sit beside their parents.buildthing inventorydedups;depsdoes not. Do not quote a cross-repo dependency answer from a box full of worktrees. NOT on MCP anyway.- Not on MCP at all:
deps,dupes,coverage,mimir,docs,pipelines,history. Those are CLI verbs, and the last three read nornir's warehouse. buildthing_info <product>refuses without--suite/suitewhen the product declares more than one delivery. holger declaresORIGINALandMVP, so{"product":"holger"}alone answers "this repo ships 2 deliveries — say which one". That is correct behaviour, not a failure.buildthing_inventoryis slow (tens of seconds — it evaluates code recipes, some in a container). Fine from a real MCP client; a probe that closes stdin early loses the answer totimed out draining in-flight responses.- It does not build in anger from the lens. Every buildthing tool here is read-only by construction. A real build is
dwarves buildthing <product> --variant <v>, in a container or a KVM, never on the host.
4. THE CONSTELLATION ON ODEN — fresh, stale, unreachable
workspace_nordisk/nornir-workspace.toml [repos] lists 25 repos (READ — the brief said 23). MEASURED on oden after the sweep:
| state | repos |
| present + level with origin | 21 of 25 — nornir · modgunn · funnel-core · holger · facett · edda · skade · znippy · znippy-zoomies · knut · korp · korp-collectors · ordning · Skidbladnir · tunnr · dwarves · workspace_nordisk · holger-ui · monetize-impl · private-gunnar-ops · draupnir |
| cloned by me today | holger-ui (was MISSING; git@github.com:vetra-automation/holger-ui.git, now at fb293f0) |
| pulled forward | Skidbladnir 3 behind → fdbfc77; private-gunnar-ops 31 behind → 8924e55 |
| on a lane branch, not main | draupnir on wip/oden-2026-09-12 (1 ahead / 5 behind origin/main); monetize-impl on lane/oden-webhook-secrets-2026-09-12 — dwarves resolves against whatever is checked out, so these answer for the LANE, not for main |
| unreachable | gunnar, gunnar-ui, monetize, private-holger-ops — all four have ssh://git@git.gunnar.rs:2222/… as origin. The forge is the appliance and a new gunnar estate is being stood up, so these were deliberately not touched. Their working copies are from 2026-09-19 and cannot be verified. private-holger-ops is MISSING on oden entirely. |
Auth is NOT the problem anywhere else: oden authenticates to codeberg (mikronova, key odin) and to github (Ignalina).
⚠ A trap found on loki, worth knowing wherever it repeats: ~/git/funnel-core there is not a clone at all — it is an unpacked crates.io .crate (it carries .cargo_vcs_info.json pinning 8d0ed523, Cargo.toml.orig, and 2006 mtimes). git fetch cannot tell you it is stale, because there is no git. oden's funnel-core IS a real clone (d55cde4).
The pinned toolchain image
MEASURED: oden did not carry nordisk-cargo-toolchain@sha256:3d5115ba…, so every code road that needs it was refused with 403 denied: requested access to the resource is denied and the inventory fell back to the emitted toml. Moved it t14s → oden with podman push dir: + rsync + podman pull dir: (never podman save|load, which silently re-digests and breaks every pin), tagged localhost/nordisk-cargo-toolchain:live-uefi. kod went 2 → 3 and holger's roster now comes off holger's own Rust recipe.
The rest are honest, named refusals, and two classes are DECLARATION bugs, not box problems:
holger-ui,gunnar-ui,ordning,modgunn— "[build.toolchain]nordisk-cargo-toolchain: noimage_sha256— a named tag is a convenience, NOT a pin". Fix belongs in those repos' declarations.skade,Skidbladnir,draupnir,gunnar,tunnr,znippy,znippy-zoomies,korp-collectors— the<repo>-build-thingcrate does not compile to wasm32 in the toolchain.dwarves,nornir,knut,edda— no<repo>-build-thingcrate at all. These are the tillskott the law already names.
5. WHAT I FIXED IN DWARVES (the standing rule is FIX DWARVES)
| fix | repo · commit | why |
The build-thing lenses on MCP — buildthing_inventory · _search · _info · _resolve |
dwarves 419de78 |
The CLI has answered "which repo declares which variant" since August; the MCP surface carried only funnel + release tools, so the door an AGENT takes could not ask. All four enter the SAME crate::buildthing functions the CLI enters — no second resolver, no second renderer. |
suite_cli's --list rendering becomes VALUES (list_lines + list_resolved) |
edda 58c8a84 |
run's --list leg printed the variant rows with println!. MEASURED: over a stdio MCP session the tool returned its one header line and wrote the other three into the middle of the JSON-RPC stream. One renderer, two sinks; the CLI output is byte-identical. 682 lib tests green. |
buildthing_resolve goes through the non-printing door |
dwarves f7a17aa |
Follow-up to the above; the lens is now read-only by construction — that path has no build leg at all. |
The fat dwarves client gets its own install door back (cli-install on the declared row) |
dwarves 0088060 |
cli-install is default-ON in Cargo.toml with the reason written next to it — "a door behind an off-by-default flag is the same non-door" — but the dwarves variant sets no_default_features = true and never named it. MEASURED on oden: the freshly built declared dwarves answered error: unrecognized subcommand 'install' while /usr/local/bin/dwarves sat ten days stale, which is the exact drift the verb was added to end. |
What I would fix next, and did not
dwarves depsshould dedup worktrees the waybuildthing inventoryalready does. On a lane box its answer is unusable (85 "repos", every shared crate "ambiguous").- No cheap way to ask a binary whether Gate B is compiled in.
release_doctor::gate_b::available()is aconst fnand nothing surfaces it —dwarves doctorand--versionboth stay silent, so the only way to find out is to start a heavy rehearsal or to hit the refusal at publish time. One line indoctorwould close it. build_and_pack_headshells out tocargo(std::process::Command). It is existing code and it is the one place the law's "no shelling" is broken in the refresh path.- The four
git.gunnar.rsrepos have no second route. While the appliance is down, a dwarves on oden simply cannot see them, and it does not currently SAY so in the inventory — a repo it cannot reach is just absent. It should be listed as unreachable, by name.
6. WHAT WAS SHUT DOWN ON LOKI
Nothing was ever serving there — the build had not finished and no dwarves-server was ever started. Removed: the in-flight cargo build, the tmux session dwarves (the sessions arch, bench, estate, gunnarbench, holger, pullsweep belong to other work and were left alone), the _wt/stinky-dwarves worktree and its branch, /tmp/build-dwarves.sh, /tmp/dwarves-build.log, and ~/git/dwarves/target (cargo clean: 40 744 files, 31.3 GiB reclaimed). loki's dwarves and edda clones were left level with origin.