Draft — publishes after the current v1-trust roadmap lands. The module system, trust artifacts, and fleet measurements are real and green today; den's next-generation internals are the foundation-laid-for, not done-yet, and are phrased that way below. Numbers grounded in
gen/BENCHMARKS.md,gen/VALIDATION.md, andgen/ci/README.mdas of 2026-07-05.
We rebuilt the engine that turns your configuration into a finished system — from scratch, in pure Nix — and it is faster, lighter, smaller, and provably identical to what it replaces. Typical configuration shapes now evaluate 1.6× to nearly 3× faster, deeply-nested ones up to ~6×, while allocating a fraction of the memory. And every one of those numbers is backed by a command you can run yourself and a CI gate that fails the moment it stops being true.
If you use den, this is the foundation everything else now stands on. If you build your own Nix framework, these are libraries you can pick up piece by piece.
Every Nix configuration framework — NixOS, home-manager, den — sits on top of one shared
engine buried in nixpkgs: the module system (lib.evalModules + lib.types). It's
the machinery that takes hundreds of little config fragments, decides which setting wins when
two of them disagree, checks the types, and merges everything into one final answer.
That engine is excellent and general — but it's also a ~4,200-line monolith, and using any
of it drags all of nixpkgs into your evaluation. For a framework like den — which is
mostly about composing typed catalogs of hosts, users, and services and layering "aspects"
across them — you pay for a lot of machinery you don't use, and you can't measure or reason
about the merge on its own.
So we wrote our own, as small decoupled libraries:
- gen-types — checks whether a value matches a type. Nothing else.
- gen-merge — the merge engine: collects the config fragments, resolves conflicts by priority, and produces the final merged result.
Together they're a drop-in replacement for lib.evalModules + lib.types — in 936 lines
instead of ~4,225 — and the rest of gen's stack (typed registries, aspects) was ported onto
them. The whole library layer is now free of nixpkgs's lib entirely. Full nixpkgs
enters at exactly one well-marked door, right at the end, where the finished configuration
values are handed off to build a real NixOS system.
The key discipline that makes all of this safe: types stay inside our fast engine; only
finished values cross into nixpkgs. That clean seam is what lets us test and measure the
engine in isolation — and it's the same one-way trade the
adios project makes.
All numbers below are gated on byte-parity first — the new engine must produce output byte-for-byte identical to the old one (down to a cryptographic hash of each entity's identity) before any speed number is allowed to count. So these are pure engine speedups on provably-identical results, never "fast because it's cutting corners."
Typical configuration shapes (new engine vs. the nixpkgs engine):
| Configuration shape | Speedup | Memory allocated | Thunks built |
|---|---|---|---|
| Aspect trees (cross-cutting layers) | 2.8× | 3.6× less | 3.0× less |
| Instance registries (catalogs of things) | 2.3× | 2.6× less | 2.2× less |
| Typed host/entity registries | 1.8× | 2.0× less | 1.8× less |
| Wide flat option sets | 1.6× | 1.5× less | 1.3× less |
| Deeply-nested configuration | up to ~6× | 4.2× less | 3.5× less |
The speedup number varies a little with your machine; the memory and thunk reductions are exact and reproduce bit-for-bit on a given Nix version. Growth is also provably linear — double the config, roughly double the cost — checked on every run.
Two more wins worth calling out:
- Rebuilding a shared machine is ~83% cheaper. When many machines share a common core, we can hand the engine that pre-computed core instead of re-merging it per machine — skipping about 83% of the merge work, byte-identically.
- Editing one machine in a fleet only recomputes that machine. A localized change to a single host skips roughly two-thirds of the whole fleet's composition work and still produces a result byte-identical to rebuilding everything from scratch.
An honest boundary, stated up front: this speeds up the composition work — resolving
registries, aspects, and merges. It does not make your whole nixos-rebuild several times
faster, because on a single machine the large majority of build cost is nixpkgs constructing
the actual package closure, which we deliberately don't touch. The multiplier shows up where
composition dominates: large registries, aspect resolution, fleet-wide evaluation, and
incremental rebuilds. On a whole real-world flake, framework overhead is a smaller slice, so
expect a more modest end-to-end gain — which is exactly what neighboring projects measure too.
This is the part we're most proud of, and it's why you can trust the numbers above.
- Byte-parity oracles. Two permanent CI checks prove the new engine's output is identical to the old one's, over realistic configuration shapes and a sample of a real project's actual config. They include mutation teeth: the test deliberately perturbs an input and confirms the output changes — so "identical" can never pass vacuously.
- Deterministic, machine-independent metrics. We measure exact evaluator counters (thunks, allocations, function calls) that reproduce bit-for-bit, and report speed as a same-machine ratio so CI-runner speed cancels out. Nobody has to trust "it felt faster on my laptop."
- Three standing performance gates. Every change is checked for (1) still byte-identical, (2) still faster and lighter, and (3) still linear. These aren't one-time measurements — they're permanent regressions. The linearity gate has already earned its keep: it caught a real accidental quadratic bug (a merge that got quadratically slower as configs grew) and now guards against that class of mistake forever.
- Everything is regenerable, with a documented failure mode. Every claim in our benchmark and validation docs comes with the command a stranger can run and a description of exactly what a failure looks like. Nothing is quoted from memory.
- Gates with teeth. The fleet-scale checks include a self-test that deliberately corrupts a saved measurement to confirm the gate actually fires — so we know our safety nets aren't silently broken.
- 1,745 tests across 15 libraries, plus automated scanners that prove the "pure" libraries
genuinely never reach for
nixpkgs.
The theme: no claim without a reproduction. Performance and correctness are treated as regressions we defend, not adjectives we assert.
Because the shared engine every Nix framework depends on is a general-purpose monolith, and building a fast, verifiable, graph-structured framework on top of a black box you can't measure or reason about in isolation is a losing game. We wanted a composition engine we own end to end — one we can profile, gate, shrink, and speed up — without giving up compatibility. The whole design turns on one seam (types stay in, only values come out), and that seam is precisely what makes the engine independently testable and independently fast.
- It's faster where it matters — 1.6× to ~6× on composition-heavy work, with a fraction of the memory.
- It's provably correct — byte-identical to the engine you already trust, enforced in CI, so a "fast but wrong" change literally cannot ship.
- It won't lock you in — standard NixOS systems come out the other end, and there's an opt-in
compatibility mode: during a migration you can keep using your existing
nixpkgsoption types unchanged. Simple leaf types cost nothing; it's a genuine escape hatch, not a rewrite mandate. - It's à la carte — the libraries are decoupled. Want typed registries but not aspects? Graph queries but nothing else? Take one piece; ignore the rest.
den is the primary consumer, and its structure — typed catalogs of hosts/users/services, aspects layered across them, and whole fleets of machines — is exactly the shape that wins the most here. Concretely, den gets:
- Faster evaluation of the registry and aspect resolution that dominates its config work.
- Cheap incremental rebuilds — change one host, recompute one host.
- A verifiable foundation for den's next-generation internals, including the ability to carry deferred, config-dependent values through the engine untouched until the very end — proven byte-identical to the old behavior.
- Confidence to evolve. Because every change to den's config engine is now gated byte-identical and performance-regression-tested, den's internals can be refactored aggressively without fear of silent breakage or silent slowdown.
Will this break my existing config? No. The output is byte-identical to the current engine
over the surface real configs use, and that's enforced. One deliberate simplification: the
rarely-used explicit ordering controls (mkBefore/mkAfter) aren't implemented, because they
had zero uses on the surface we replaced. If you rely on those, that's what compatibility mode
is for.
Do I have to rewrite everything? No. Adopt it per-library, and use compatibility mode to bring your existing types along during a transition.
How do I know these numbers aren't cherry-picked? Every one regenerates from a single command, gated on byte-parity, using deterministic counters and machine-independent ratios. The benchmark and validation docs list the commands and the exact failure modes.
Isn't a from-scratch reimplementation a maintenance risk? It's 936 lines versus ~4,225 precisely because it implements one clean priority rule and drops unused machinery — and it's permanently pinned byte-identical against the original in CI, so any drift is caught the moment it happens.