From 281667d5a89ee87d0a288f9bdc804ceb5c815487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?m=C3=BCde?= Date: Mon, 13 Jul 2026 21:43:26 +0200 Subject: [PATCH] refactor: thread agent packages via option, drop overlay and forge-tools shim --- flake.nix | 153 ++++++++++------------ nix/docs/default.nix | 21 +-- nix/packages/default.nix | 4 +- nix/packages/hive-forge-tools.nix | 35 ----- nix/templates/harness/agent-service.nix | 4 +- nix/templates/harness/claude-settings.nix | 2 +- nix/templates/harness/default.nix | 39 +++--- nix/templates/harness/docs.nix | 8 +- nix/templates/harness/forge.nix | 5 +- nix/templates/harness/frontend.nix | 4 +- nix/templates/harness/matrix.nix | 4 +- nix/templates/harness/mcp.nix | 6 +- nix/templates/harness/packages.nix | 22 ++++ 13 files changed, 139 insertions(+), 168 deletions(-) delete mode 100644 nix/packages/hive-forge-tools.nix create mode 100644 nix/templates/harness/packages.nix diff --git a/flake.nix b/flake.nix index 3ed23ea6..e86c5b6b 100644 --- a/flake.nix +++ b/flake.nix @@ -67,80 +67,78 @@ } ); - overlays = { - default = final: prev: { - hyperhive = self.packages.${prev.stdenv.hostPlatform.system}.default; - # Per-binary daemon/harness/MCP-server packages, exposed via the - # overlay so container nix evaluations can depend on exactly the - # bin(s) they need instead of the full `hyperhive` bundle — see - # `packages..hive-*` and their use in `harness-base.nix`. - hive-c0re = self.packages.${prev.stdenv.hostPlatform.system}.hive-c0re; - hive-priv = self.packages.${prev.stdenv.hostPlatform.system}.hive-priv; - hive-agent = self.packages.${prev.stdenv.hostPlatform.system}.hive-agent; - hive-agent-mcp = self.packages.${prev.stdenv.hostPlatform.system}.hive-agent-mcp; - hive-agent-wake = self.packages.${prev.stdenv.hostPlatform.system}.hive-agent-wake; - hive-bash-daemon = self.packages.${prev.stdenv.hostPlatform.system}.hive-bash-daemon; - hive-bash-mcp = self.packages.${prev.stdenv.hostPlatform.system}.hive-bash-mcp; - hive-matrix-daemon = self.packages.${prev.stdenv.hostPlatform.system}.hive-matrix-daemon; - hive-matrix-mcp = self.packages.${prev.stdenv.hostPlatform.system}.hive-matrix-mcp; - hive-metric = self.packages.${prev.stdenv.hostPlatform.system}.hive-metric; - # Bundled frontend dist (see nix/packages/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. 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; - # Standalone docs/ tree (see nix/packages/reference-docs.nix). - # Exposed via the overlay so the harness module can build the - # in-container agent docs dir from it. - hyperhive-docs = self.packages.${prev.stdenv.hostPlatform.system}.reference-docs; - }; - }; - - nixosModules = { - agent-base = ./nix/templates/agent-base.nix; - ruth = ./nix/templates/manager.nix; - # The full host stack (nix/modules/default.nix aggregator) plus - # the package/source wiring from this flake. The wiring is a - # plain config module setting the `services.hyperhive.c0re.*` - # package options via `lib.mkDefault` — no overlay involved, and - # an operator override still wins. Intended usage: - # - # imports = [ hyperhive.nixosModules.default ]; - # services.hyperhive.enable = true; - # - default = - { lib, pkgs, ... }: - { - imports = [ ./nix/modules ]; - services.hyperhive.c0re = { - package = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.default; - frontend = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.frontend; - assets = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.assets; - xdgIcons = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.xdg-icons; - hyperhiveFlake = lib.mkDefault "${sources.hyperhiveFlakeSource}"; - # Narrow docs/ source, threaded as its own meta-flake input - # so doc edits don't re-hash the whole flake source. - hyperhiveDocs = lib.mkDefault "${sources.hyperhiveDocsSource}"; - # 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. x86_64-linux only (nixosConfigurations are - # hardcoded to that system); the gate keeps aarch64 hosts - # from pulling them in via cross-build. - agentBaseToplevel = lib.mkDefault self.packages.x86_64-linux.agent-base-toplevel; - managerToplevel = lib.mkDefault self.packages.x86_64-linux.ruth-toplevel; + nixosModules = + let + # Package wiring for agent containers — the harness modules + # consume hyperhive's own packages via the `hyperhive.packages` + # option (see nix/templates/harness/packages.nix); no overlay. + # `mkDefault` so a per-agent override of an individual key wins. + agentPackages = + { lib, pkgs, ... }: + { + hyperhive.packages = lib.mkDefault { + inherit (self.packages.${pkgs.stdenv.hostPlatform.system}) + hive-agent + hive-agent-mcp + hive-agent-wake + hive-bash-daemon + hive-bash-mcp + hive-forge + hive-matrix-daemon + hive-matrix-mcp + hive-metric + assets + frontend + reference-docs + ; + }; }; - }; - hive-ci = ./nix/modules/hive-ci.nix; - hive-forge = ./nix/modules/hive-forge.nix; - }; + in + { + agent-base.imports = [ + ./nix/templates/agent-base.nix + agentPackages + ]; + ruth.imports = [ + ./nix/templates/manager.nix + agentPackages + ]; + # The full host stack (nix/modules/default.nix aggregator) plus + # the package/source wiring from this flake. The wiring is a + # plain config module setting the `services.hyperhive.c0re.*` + # package options via `lib.mkDefault` — no overlay involved, and + # an operator override still wins. Intended usage: + # + # imports = [ hyperhive.nixosModules.default ]; + # services.hyperhive.enable = true; + # + default = + { lib, pkgs, ... }: + { + imports = [ ./nix/modules ]; + services.hyperhive.c0re = { + package = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.default; + frontend = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.frontend; + assets = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.assets; + xdgIcons = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.xdg-icons; + hyperhiveFlake = lib.mkDefault "${sources.hyperhiveFlakeSource}"; + # Narrow docs/ source, threaded as its own meta-flake input + # so doc edits don't re-hash the whole flake source. + hyperhiveDocs = lib.mkDefault "${sources.hyperhiveDocsSource}"; + # 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. x86_64-linux only (nixosConfigurations are + # hardcoded to that system); the gate keeps aarch64 hosts + # from pulling them in via cross-build. + agentBaseToplevel = lib.mkDefault self.packages.x86_64-linux.agent-base-toplevel; + managerToplevel = lib.mkDefault self.packages.x86_64-linux.ruth-toplevel; + }; + }; + hive-ci = ./nix/modules/hive-ci.nix; + hive-forge = ./nix/modules/hive-forge.nix; + }; nixosConfigurations = let @@ -148,14 +146,7 @@ module: nixpkgs.lib.nixosSystem { system = "x86_64-linux"; - modules = [ - module - { - nixpkgs.overlays = [ - self.overlays.default - ]; - } - ]; + modules = [ module ]; }; in { diff --git a/nix/docs/default.nix b/nix/docs/default.nix index dd003274..b40e3853 100644 --- a/nix/docs/default.nix +++ b/nix/docs/default.nix @@ -23,17 +23,6 @@ let name = "hyperhive-nix-src"; }; - # Stub overlay that satisfies pkgs.hyperhive-* references in module - # option defaults without depending on self's Rust / frontend builds. - # nixosOptionsDoc renders `defaultText` for these options anyway; the - # stubs just prevent attribute-missing eval errors. - docsStubOverlay = _final: _prev: { - hyperhive = pkgs.emptyFile; - hyperhive-frontend = pkgs.emptyDirectory; - hyperhive-assets = pkgs.emptyDirectory; - hyperhive-docs = pkgs.emptyDirectory; - }; - # Stub host system: every hyperhive subsystem `mkForce false` so # heavy build inputs (matrix container, forge, etc.) stay out of # the eval — only option *declarations* matter for the doc walk. @@ -48,7 +37,6 @@ let ( { lib, ... }: { - nixpkgs.overlays = [ docsStubOverlay ]; fileSystems."/" = { device = "/dev/null"; fsType = "tmpfs"; @@ -62,14 +50,15 @@ let ]; }; - # Agent module eval from the content-addressed nixSrc. Relative - # imports inside agent-base.nix (e.g. ./harness-base.nix) resolve - # correctly against the nixSrc directory tree. + # Agent module eval from the content-addressed nixSrc. Relative + # imports inside agent-base.nix (the ./harness module dir) resolve + # correctly against the nixSrc directory tree. `hyperhive.packages` + # stays unset — every option default that references it carries a + # `defaultText`, so the doc walk never forces the packages. agentEval = nixosSystem { system = pkgs.stdenv.hostPlatform.system; modules = [ "${nixSrc}/templates/agent-base.nix" - { nixpkgs.overlays = [ docsStubOverlay ]; } ]; }; diff --git a/nix/packages/default.nix b/nix/packages/default.nix index 9e3b6e84..1828b0f1 100644 --- a/nix/packages/default.nix +++ b/nix/packages/default.nix @@ -117,8 +117,8 @@ in { # All workspace binaries in one derivation — a symlinkJoin over the # per-bin packages, pure assembly with no extra compilation. The - # NixOS module's `pkgs.hyperhive` (= this) and `nix build .#` both - # land here. + # host module's `services.hyperhive.c0re.package` and `nix build .#` + # both land here. default = pkgs.symlinkJoin { name = "hyperhive"; paths = lib.attrValues binPkgs; diff --git a/nix/packages/hive-forge-tools.nix b/nix/packages/hive-forge-tools.nix deleted file mode 100644 index 6ebfff4f..00000000 --- a/nix/packages/hive-forge-tools.nix +++ /dev/null @@ -1,35 +0,0 @@ -{ pkgs, lib }: -# hive-forge — Forgejo CLI wrapper for hyperhive. -# -# Previously a ~600-line bash script. Rewritten as a proper Rust -# binary in `/hive-forge` so we get: -# - typed clap subcommands (`hive-forge --help` instead of -# reading the case statement), -# - one reqwest client with consistent error surfaces (no more -# `curl --fail-with-body` repeated per verb), -# - sane shell quoting (no more HEREDOC-eaten-by-positional traps), -# - and a single test surface. -# -# This Nix file is now a thin extractor: it pulls just the -# `hive-forge` binary out of the hyperhive workspace package so -# agents that already imported this file via -# `pkgs.callPackage ../packages/hive-forge-tools.nix { }` keep -# working, getting only the verb they need on PATH (not the full -# `hive-c0re` / `hive` surface). -# -# Requires the hyperhive overlay (see `flake.nix`'s `overlays.default`) -# so `pkgs.hyperhive` resolves. -let - _ = lib; # placeholder; kept so callers don't break when reading the args. -in -pkgs.runCommand "hive-forge" - { - meta = { - description = "Forgejo CLI wrapper for hyperhive (Rust)"; - mainProgram = "hive-forge"; - }; - } - '' - mkdir -p $out/bin - ln -s ${pkgs.hyperhive}/bin/hive-forge $out/bin/hive-forge - '' diff --git a/nix/templates/harness/agent-service.nix b/nix/templates/harness/agent-service.nix index d7f60026..ade20d7f 100644 --- a/nix/templates/harness/agent-service.nix +++ b/nix/templates/harness/agent-service.nix @@ -195,7 +195,7 @@ in SHELL = "${pkgs.bashInteractive}/bin/bash"; HOME = homeDir; HIVE_STATIC_DIR = "${config.hyperhive.frontend.mergedDist}"; - HIVE_ASSETS_DIR = "${pkgs.hyperhive-assets}/share/hyperhive"; + HIVE_ASSETS_DIR = "${config.hyperhive.packages.assets}/share/hyperhive"; # Unix-socket path for the harness web UI. All agents always bind # here; there is no TCP fallback. Path matches # `hive_c0re::agent_sockets::socket_path_for(name)` so lifecycle @@ -226,7 +226,7 @@ in HIVE_EXTRA_WEB_PROXIES = builtins.toJSON config.hyperhive.extraWebProxies; }; serviceConfig = { - ExecStart = "${pkgs.hive-agent}/bin/${binary}"; + ExecStart = "${config.hyperhive.packages.hive-agent}/bin/${binary}"; # Pin the journal identity to the binary name (otherwise systemd # derives SyslogIdentifier from the ExecStart basename). SyslogIdentifier = binary; diff --git a/nix/templates/harness/claude-settings.nix b/nix/templates/harness/claude-settings.nix index ca8a2fa1..260c5e71 100644 --- a/nix/templates/harness/claude-settings.nix +++ b/nix/templates/harness/claude-settings.nix @@ -265,7 +265,7 @@ in # `hivectl choom`) so no launch wrapper is needed. environment.etc."claude-code/managed-settings.json".source = let - baseSettings = "${pkgs.hyperhive-assets}/share/hyperhive/prompts/claude-settings.json"; + baseSettings = "${config.hyperhive.packages.assets}/share/hyperhive/prompts/claude-settings.json"; # Merge base env (always) with OTEL env (when enabled). jq is always # run — `baseClaudeEnv` contains per-agent values (e.g. # CLAUDE_REMOTE_CONTROL_SESSION_NAME_PREFIX) that can't live in the diff --git a/nix/templates/harness/default.nix b/nix/templates/harness/default.nix index 8e249b32..115f3e1f 100644 --- a/nix/templates/harness/default.nix +++ b/nix/templates/harness/default.nix @@ -31,6 +31,7 @@ ./matrix.nix ./mcp.nix ./network.nix + ./packages.nix ./user.nix ./weston-vnc.nix ]; @@ -153,22 +154,24 @@ nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (pkgs.lib.getName pkg) [ "claude-code" ]; # Core tooling every agent gets. Per-bin split packages (see - # nix/packages/default.nix) rather than the full `pkgs.hyperhive` - # bundle — that bundle also carries `hivectl` (a host-admin CLI - # that dials the *host* admin socket — useless and unreachable - # from inside a container — wrapped with `wireguard-tools` for - # `hivectl wg`). The daemon/harness/MCP bins the harness execs - # (hive-agent{,-mcp}, hive-bash-daemon, hive-matrix-daemon, - # hive-bash-mcp, hive-matrix-mcp) are wired via their own - # ExecStart/command lines in the sibling modules with the matching - # `pkgs.hive-*` package — they don't need to be on PATH too. Only - # these two are actually looked up on PATH by claude/shell code - # inside the container: `hive-agent-wake` (external wake CLI, - # docs/turn-loop/mcp.md) and `hive-metric` (agent-emitted custom - # metrics CLI, docs/observability.md). - environment.systemPackages = with pkgs; [ - hive-agent-wake - hive-metric + # nix/packages/default.nix + ./packages.nix) rather than the full + # `hyperhive` bundle — that bundle also carries `hivectl` (a + # host-admin CLI that dials the *host* admin socket — useless and + # unreachable from inside a container — wrapped with + # `wireguard-tools` for `hivectl wg`). The daemon/harness/MCP bins + # the harness execs (hive-agent{,-mcp}, hive-bash-daemon, + # hive-matrix-daemon, hive-bash-mcp, hive-matrix-mcp) are wired via + # their own ExecStart/command lines in the sibling modules — they + # don't need to be on PATH too. Only these two are actually looked + # up on PATH by claude/shell code inside the container: + # `hive-agent-wake` (external wake CLI, docs/turn-loop/mcp.md) and + # `hive-metric` (agent-emitted custom metrics CLI, + # docs/observability.md). + environment.systemPackages = [ + config.hyperhive.packages.hive-agent-wake + config.hyperhive.packages.hive-metric + ] + ++ (with pkgs; [ claude-code bashInteractive coreutils-full @@ -180,7 +183,7 @@ jq # curl: HTTP client for forge REST API and other web requests. curl - ]; + ]); # HIVE_ASSETS_DIR points at the project's static runtime assets # (branding + claude prompts; see `nix/packages/assets.nix`). Set @@ -191,7 +194,7 @@ # host-level `services.hyperhive.c0re.contextWindowTokens` option — not # set here. environment.variables = { - HIVE_ASSETS_DIR = "${pkgs.hyperhive-assets}/share/hyperhive"; + HIVE_ASSETS_DIR = "${config.hyperhive.packages.assets}/share/hyperhive"; SHELL = "${pkgs.bashInteractive}/bin/bash"; # Route interactive-shell nix invocations through the host daemon. # Redundant with /etc/profile.d/nix-daemon.sh but ensures it's set diff --git a/nix/templates/harness/docs.nix b/nix/templates/harness/docs.nix index c50dbd64..565abc97 100644 --- a/nix/templates/harness/docs.nix +++ b/nix/templates/harness/docs.nix @@ -21,12 +21,12 @@ options.hyperhive.docs.source = lib.mkOption { type = lib.types.path; - default = pkgs.hyperhive-docs; - defaultText = lib.literalMD "`pkgs.hyperhive-docs` (built from the repo `docs/` tree)"; + default = config.hyperhive.packages.reference-docs; + defaultText = lib.literalMD "`hyperhive.packages.reference-docs` (built from the repo `docs/` tree)"; description = '' Store path of the reference-docs tree exposed at `$HIVE_DOCS_DIR` - when `hyperhive.docs.enable` is set. Defaults to - `pkgs.hyperhive-docs` (the `nix/packages/reference-docs.nix` + when `hyperhive.docs.enable` is set. Defaults to the flake's + `reference-docs` package (the `nix/packages/reference-docs.nix` build) so a standalone container build from a full checkout works unchanged. The generated meta flake overrides this with the narrow `hyperhive-docs` flake input so a doc edit only diff --git a/nix/templates/harness/forge.nix b/nix/templates/harness/forge.nix index af37a183..5619fb34 100644 --- a/nix/templates/harness/forge.nix +++ b/nix/templates/harness/forge.nix @@ -45,8 +45,9 @@ in # claude can `tea repos create`, `tea pulls create`, etc. pkgs.tea # hive-forge : CLI wrapping common Forgejo REST API operations - # (view, pr, issue, comment, assign, close, labels, branches, etc.) - (pkgs.callPackage ../../packages/hive-forge-tools.nix { }) + # (view, pr, issue, comment, assign, close, labels, branches, etc.). + # The per-bin split package — narrow closure, no hivectl/wireguard. + config.hyperhive.packages.hive-forge ]; # One-shot: tea config.yml from the seeded forge token. Shape diff --git a/nix/templates/harness/frontend.nix b/nix/templates/harness/frontend.nix index 759ca03f..90a631cf 100644 --- a/nix/templates/harness/frontend.nix +++ b/nix/templates/harness/frontend.nix @@ -10,8 +10,8 @@ { options.hyperhive.frontend.dist = lib.mkOption { type = lib.types.package; - default = pkgs.hyperhive-frontend; - defaultText = lib.literalExpression "pkgs.hyperhive-frontend"; + default = config.hyperhive.packages.frontend; + defaultText = lib.literalMD "`hyperhive.packages.frontend` (the flake's frontend dist)"; description = '' The shipped frontend dist (built by `nix/packages/frontend.nix`). Output layout: `dashboard/` (used by hive-c0re on the host) and diff --git a/nix/templates/harness/matrix.nix b/nix/templates/harness/matrix.nix index 10517a78..7321ba1f 100644 --- a/nix/templates/harness/matrix.nix +++ b/nix/templates/harness/matrix.nix @@ -195,7 +195,7 @@ in # can override the entry. hyperhive.extraMcpServers = lib.mkIf config.hyperhive.matrix.enable { matrix = lib.mkDefault { - command = "${pkgs.hive-matrix-mcp}/bin/hive-matrix-mcp"; + command = "${config.hyperhive.packages.hive-matrix-mcp}/bin/hive-matrix-mcp"; args = [ ]; # Same socket path the hive-matrix-daemon service binds # via its `RuntimeDirectory = "hive-matrix"`. Keeps the @@ -259,7 +259,7 @@ in HIVE_ICON_PNG = "${iconPng}"; }; serviceConfig = { - ExecStart = "${pkgs.hive-matrix-daemon}/bin/hive-matrix-daemon"; + ExecStart = "${config.hyperhive.packages.hive-matrix-daemon}/bin/hive-matrix-daemon"; SyslogIdentifier = "hive-matrix-daemon"; Restart = "on-failure"; RestartSec = 5; diff --git a/nix/templates/harness/mcp.nix b/nix/templates/harness/mcp.nix index dd664dc3..cab1695d 100644 --- a/nix/templates/harness/mcp.nix +++ b/nix/templates/harness/mcp.nix @@ -159,7 +159,7 @@ in # agent.nix can override the entry. (The matrix sibling lives in # ./matrix.nix, gated on hyperhive.matrix.enable.) hyperhive.extraMcpServers.bash = lib.mkDefault { - command = "${pkgs.hive-bash-mcp}/bin/hive-bash-mcp"; + command = "${config.hyperhive.packages.hive-bash-mcp}/bin/hive-bash-mcp"; args = [ ]; env.HIVE_BASH_SOCKET = "/run/hive-bash/socket"; allowedTools = [ "*" ]; @@ -203,7 +203,7 @@ in # value but is less robust if the two vars ever diverge. }; serviceConfig = { - ExecStart = "${pkgs.hive-bash-daemon}/bin/hive-bash-daemon"; + ExecStart = "${config.hyperhive.packages.hive-bash-daemon}/bin/hive-bash-daemon"; SyslogIdentifier = "hive-bash-daemon"; Restart = "on-failure"; RestartSec = 3; @@ -239,7 +239,7 @@ in before = [ "hive-ag3nt.service" ]; environment.RUST_LOG = "info"; serviceConfig = { - ExecStart = "${pkgs.hive-agent-mcp}/bin/hive-agent-mcp --http 127.0.0.1:${toString config.hyperhive.mcp.httpPort}"; + ExecStart = "${config.hyperhive.packages.hive-agent-mcp}/bin/hive-agent-mcp --http 127.0.0.1:${toString config.hyperhive.mcp.httpPort}"; SyslogIdentifier = "hive-mcp-http"; # `always` (not `on-failure`): this endpoint is load-bearing — the # sole hyperhive-MCP transport, so a down window is total diff --git a/nix/templates/harness/packages.nix b/nix/templates/harness/packages.nix new file mode 100644 index 00000000..f5a29df1 --- /dev/null +++ b/nix/templates/harness/packages.nix @@ -0,0 +1,22 @@ +# The hyperhive-built packages the harness modules consume, threaded +# in explicitly as an option — no overlay. The flake's +# `nixosModules.{agent-base,ruth}` set this to the flake's own package +# outputs via `lib.mkDefault`, so a per-agent override of an +# individual key still wins. +{ lib, ... }: +{ + options.hyperhive.packages = lib.mkOption { + type = lib.types.attrsOf lib.types.package; + internal = true; + description = '' + hyperhive package outputs consumed by the harness modules: the + per-binary daemon/CLI packages (`hive-agent`, `hive-agent-mcp`, + `hive-agent-wake`, `hive-bash-daemon`, `hive-bash-mcp`, + `hive-forge`, `hive-matrix-daemon`, `hive-matrix-mcp`, + `hive-metric`) plus the `assets`, `frontend` and + `reference-docs` trees. Wired by the flake's agent-base/ruth + nixosModules to `hyperhive.packages..*`; override an + individual key per-agent to swap in a patched binary. + ''; + }; +}