{ description = "hyperhive — multi-Claude-Code-agent orchestration on nixos-containers"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; # Crane (replaces naersk #538). Stateless — no nixpkgs input to # follow; `crane.mkLib pkgs` returns the lib at whatever pkgs we # pass it (we use the project's pinned nixpkgs). crane.url = "github:ipetkov/crane"; treefmt-nix = { url = "github:numtide/treefmt-nix"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = inputs@{ self, nixpkgs, nixpkgs-unstable, crane, treefmt-nix, }: let inherit (nixpkgs) lib; systems = [ "aarch64-linux" "x86_64-linux" ]; treefmt-config = { projectRootFile = "flake.nix"; programs = { keep-sorted.enable = true; nixfmt.enable = true; rustfmt.enable = true; taplo.enable = true; }; }; forAllSystems = f: lib.genAttrs systems ( system: f rec { inherit system; pkgs = nixpkgs.legacyPackages.${system}; treefmt-eval = treefmt-nix.lib.evalModule pkgs treefmt-config; craneLib = crane.mkLib pkgs; # Narrowed source tree the rust derivations consume. # `cleanCargoSource` is crane's standard "everything cargo # cares about" filter (Cargo.toml/Cargo.lock + *.rs). All # non-rust runtime assets — branding + the claude system # prompt template + claude-settings.json — live in the # separate `hyperhive-assets` derivation (#555) and are # loaded by the binaries at runtime from `$HIVE_ASSETS_DIR`. # The unit tests in `hive-ag3nt::prompt` read the same # `prompts/system.md` directly from the workspace tree at # *test* runtime (via `env!("CARGO_MANIFEST_DIR")` — a # compile-time string, no file open at compile), so the # prompt template doesn't have to be in this fileset to # keep `cargo test` honest. Net effect: tweaks to any # non-`*.rs` / non-`Cargo.*` file (README, branding, # nix modules, frontend tree, OR `hive-ag3nt/prompts/*`) # do NOT bust this src hash, so the rust derivations # stay fully cached. cleanSrc = craneLib.cleanCargoSource ./.; # Build the workspace's dependency tree once, cached as # its own derivation. `buildPackage` and `cargoClippy` # both reuse this via `inherit cargoArtifacts;` so a # workspace-only edit doesn't rebuild deps. All three # derivations consume the same `cleanSrc` so the input # hash stays consistent across the chain. cargoArtifacts = craneLib.buildDepsOnly { src = cleanSrc; # Workspace Cargo.toml is virtual (no `[package].name`), # so crane can't auto-derive a name. Spell it out # explicitly here and below — keeps the derivation name # stable across crane bumps + silences the placeholder # warning. Same `pname` everywhere so dep + workspace + # clippy share a clean naming family. pname = "hyperhive-workspace"; version = "0.1.0"; inherit nativeBuildInputs; }; # Shared between buildDepsOnly + buildPackage + cargoClippy # so the three derivations see the same toolchain shape. # git: naersk used to auto-include it; crane is more # minimal, so we add it explicitly so hive-c0re's # `lifecycle::tests::setup_proposed_*` (which shell out to # `git init` + commit) pass under `cargo test` in the # sandbox. # `librsvg` used to live here for `hive-c0re/build.rs`'s # rsvg-convert call — that whole codepath moved into the # `hyperhive-assets` derivation in #555, so the rust # derivation no longer needs the dependency. # `sqlite` required by matrix-sdk's `sqlite` feature # (`hive-matrix-mcp` workspace member, #548 phase 3) — the # matrix-sdk-sqlite + rusqlite stack links against system # libsqlite3 by default. nativeBuildInputs = [ pkgs.git pkgs.sqlite pkgs.pkg-config ]; } ); in { packages = forAllSystems ( { pkgs, craneLib, cleanSrc, cargoArtifacts, nativeBuildInputs, ... }: let docsAttrs = import ./nix/docs { inherit pkgs self; inherit (nixpkgs) lib; inherit (nixpkgs.lib) nixosSystem; }; in { # Build the workspace binaries without running tests. Tests # are run as a separate check (`checks.cargo-test`) that # carries the `hyperhive-assets` build input — `hive-ag3nt:: # prompt::tests` reads the production prompt template at test # runtime through `$HIVE_ASSETS_DIR`, so wiring the env var # into the build phase here would make the prompt's hash a # build input of `default` (defeats #555's cache goal: a # prompt edit would still bust the binary derivation, even # though no .rs file changed). Keeping tests in a separate # check derivation localises the asset-rebuild blast radius # to that one check — `nix flake check` still exercises them. default = craneLib.buildPackage { src = cleanSrc; inherit cargoArtifacts nativeBuildInputs; pname = "hyperhive-workspace"; version = "0.1.0"; meta.description = "hyperhive workspace (hive-c0re, hive-ag3nt, hive-root)"; doCheck = false; }; # Bundled browser assets — see ./nix/frontend.nix. Output is # $out/{dashboard,agent}/ which the Rust binaries serve via # tower_http::ServeDir (wired up in Phase 4 of #273). frontend = pkgs.callPackage ./nix/frontend.nix { branding-svg = ./branding/hyperhive.svg; }; # Static runtime assets the rust binaries read via # `hive_sh4re::assets::*` (#555): branding/* + prompts/*, # plus the rendered agent-configs.png. Split out of the # rust derivation so a tweak to e.g. system.md doesn't bust # the cargo cache. Build input of the `cargo-test` check but # NOT of `packages.default`, so the binary derivation stays # cached when a prompt edit ripples through. assets = pkgs.callPackage ./nix/assets.nix { }; # Pre-built per-container system closures. Exposed as packages # so operators can `nix build .#agent-base-toplevel` (or wire # them into their host system closure via the # `preBuildAgentTemplates` option on the hive-c0re module — # see nix/modules/hive-c0re.nix). Speeds up the first agent # spawn dramatically because the heavy lifting (nixpkgs + # claude-code + hive-ag3nt binary) is already in the store # when the meta evaluator goes to build the container. # Closes #97. # # nixosConfigurations are pinned to x86_64-linux (nixos- # containers only run native arch), so these toplevels are # only useful on an x86_64-linux host — flake check across # systems still tolerates evaluating them on aarch64 because # they're plain derivations, but `nix build` from a non-x86 # host would only succeed via a remote x86 builder. agent-base-toplevel = self.nixosConfigurations.agent-base.config.system.build.toplevel; ruth-toplevel = self.nixosConfigurations.ruth.config.system.build.toplevel; # Auto-generated nix options reference for hyperhive (#616). # `docs` bundles host + agent pages into one tree; the split # outputs are useful when consumers only want one surface. # All three are pure markdown — no rust or frontend deps in # the closure, so `nix build .#docs` is cheap. docs = docsAttrs.bundle; docs-host = docsAttrs.host; docs-agent = docsAttrs.agent; } ); overlays = { default = final: prev: { hyperhive = self.packages.${prev.stdenv.hostPlatform.system}.default; # Bundled frontend dist (see ./nix/frontend.nix). Output is # $out/{dashboard,agent}/; consumers pick the surface they # need. Exposed via the overlay so containers' nix evaluations # can reach it as `pkgs.hyperhive-frontend` once the overlay # is applied (manager + agent containers both apply it via # `mkContainer` further down). hyperhive-frontend = self.packages.${prev.stdenv.hostPlatform.system}.frontend; # Static runtime assets (#555). Exposed alongside the binary # so the harness module can wire $HIVE_ASSETS_DIR straight # to `${pkgs.hyperhive-assets}/share/hyperhive`. hyperhive-assets = self.packages.${prev.stdenv.hostPlatform.system}.assets; }; claude-unstable = final: prev: let # The overlay imports its own nixpkgs-unstable instance to # pin claude-code there. That instance has its own config # (independent from the user's prev.config), so we have to # set allowUnfreePredicate inline to whitelist claude-code # specifically — otherwise the unstable import itself # refuses to evaluate. This is scoped: only claude-code # bypasses unfree, nothing else. unstable = import nixpkgs-unstable { inherit (prev.stdenv.hostPlatform) system; config.allowUnfreePredicate = pkg: builtins.elem (prev.lib.getName pkg) [ "claude-code" ]; }; in { inherit (unstable) claude-code; }; }; nixosModules = { agent-base = ./nix/templates/agent-base.nix; ruth = ./nix/templates/manager.nix; # The hive-c0re module wants `pkgs.hyperhive` for its default # `services.hyperhive.c0re.package`. To avoid making operators apply an # overlay (which would also pollute their host pkgs with our # build), we thread the package straight from this flake's # `packages..default` via a `hyperhivePackage` argument. # The `claude-unstable` overlay only matters inside our container # builds (already applied internally in `nixosConfigurations`). hive-c0re = import ./nix/modules/hive-c0re.nix { hyperhivePackage = system: self.packages.${system}.default; hyperhiveFrontend = system: self.packages.${system}.frontend; hyperhiveAssets = system: self.packages.${system}.assets; hyperhiveFlake = "${self}"; # Store path of the nixpkgs-unstable input this flake was evaluated # with — the channel that carries claude-code. Passed as the default # for `services.hyperhive.c0re.nixpkgsUnstableFlake` so operators can # override it without touching this file. hyperhiveNixpkgsUnstable = "path:${nixpkgs-unstable}"; # Per-container toplevels — wired into `system.extraDependencies` # when `services.hyperhive.c0re.preBuildAgentTemplates` is on so the # host system closure pre-fetches the heavy build inputs (#97). # Defined only for x86_64-linux because nixosConfigurations are # hardcoded to that system; the option's default keeps the # extra deps gated so aarch64 hosts don't accidentally pull # them in via cross-build. agentBaseToplevel = self.packages.x86_64-linux.agent-base-toplevel; managerToplevel = self.packages.x86_64-linux.ruth-toplevel; }; hive-ci = ./nix/modules/hive-ci.nix; hive-forge = ./nix/modules/hive-forge.nix; # Convenience alias: one import covers the full hyperhive host # stack (hive-c0re + hive-forge, since hive-c0re already pulls # in hive-forge). Intended usage: # # imports = [ hyperhive.nixosModules.default ]; # services.hyperhive.enable = true; # default = self.nixosModules.hive-c0re; }; nixosConfigurations = let mkContainer = module: nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ module { nixpkgs.overlays = [ self.overlays.default self.overlays.claude-unstable ]; } ]; }; in { agent-base = mkContainer self.nixosModules.agent-base; ruth = mkContainer self.nixosModules.ruth; }; devShells = forAllSystems ( { pkgs, ... }: { default = pkgs.mkShell { packages = with pkgs; [ cargo clippy librsvg # rsvg-convert — hive-c0re/build.rs invokes it (#424) pkg-config rust-analyzer rustc rustfmt sqlite ]; }; } ); formatter = forAllSystems ({ treefmt-eval, ... }: treefmt-eval.config.build.wrapper); checks = forAllSystems ( { pkgs, system, treefmt-eval, craneLib, cleanSrc, cargoArtifacts, nativeBuildInputs, ... }: { formatting = treefmt-eval.config.build.check self; # Clippy as a check via crane's first-class `cargoClippy` # builder. Reuses the shared `cargoArtifacts` (deps already # built) and runs `cargo clippy --workspace --all-targets # -- -D warnings` directly — no `overrideAttrs` hack needed, # because crane parses `cargoClippyExtraArgs` correctly # (naersk's `mode = "clippy"` used to mangle the `--` # separator, which is why the old wiring went through # overrideAttrs). clippy = craneLib.cargoClippy { src = cleanSrc; inherit cargoArtifacts nativeBuildInputs; pname = "hyperhive-workspace"; version = "0.1.0"; cargoClippyExtraArgs = "--workspace --all-targets -- -D warnings"; }; # `cargo test --workspace` lifted out of `buildPackage` so the # `hyperhive-assets` dep (which `hive-ag3nt::prompt::tests` # needs via `HIVE_ASSETS_DIR` to assert against the actual # production prompt template) is scoped to this one check # instead of bleeding into the binary derivation's input # hash. Net: editing `hive-ag3nt/prompts/system.md` still # rebuilds this test check (correct — the tests assert # against its wording), but `packages.default` and the # per-container toplevels stay fully cached. cargo-test = craneLib.cargoTest { src = cleanSrc; inherit cargoArtifacts nativeBuildInputs; pname = "hyperhive-workspace"; version = "0.1.0"; cargoTestExtraArgs = "--workspace"; HIVE_ASSETS_DIR = "${self.packages.${system}.assets}/share/hyperhive"; }; # Nix options docs evaluation (#616). Cheap: pulls in # `nixosOptionsDoc` + the host module's stub eval, no rust or # frontend deps. CI fails fast if a module change breaks # option declarations or the doc rendering. Reuses the # `packages..docs` derivation so the per-system eval # of `nix/docs/default.nix` happens once. inherit (self.packages.${system}) docs; } ); }; }