# Option declarations for `services.hyperhive.c0re.*` — the c0re # daemon's knobs plus the package/source options the flake's # `nixosModules.default` wires to its own outputs (they carry no # in-module defaults; see ../../../flake.nix). The read-only # `servedFrontend` option lives in ./theme.nix with the stylix wiring # that computes it. The hive's own values the meta-flake render hands # its agents are declared here too, under # `services.hyperhive.deploy.hive-controller`. { pkgs, lib, ... }: { imports = [ (lib.mkRemovedOptionModule [ "services" "hyperhive" "c0re" "managerToplevel" ] '' Every agent, ruth included, builds from `agentBaseToplevel`. Remove this definition. '') (lib.mkRemovedOptionModule [ "services" "hyperhive" "c0re" "hyperhiveDocs" ] '' Agents take their reference docs from the pinned `hyperhive` input, which carries `docs/`. Remove this definition. '') ]; options.services.hyperhive.c0re = { package = lib.mkOption { type = lib.types.package; defaultText = lib.literalExpression "hyperhive.packages.\${system}.default"; description = '' hyperhive workspace package. Provides `/bin/hive-c0re` (coordinator daemon) and `/bin/hivectl` (operator-facing host CLI for ad-hoc administration + the host admin socket). Wired to this flake's `packages..default` by `nixosModules.default` (via `lib.mkDefault`, so setting it here wins). ''; }; frontend = lib.mkOption { type = lib.types.package; defaultText = lib.literalExpression "hyperhive.packages.\${system}.frontend"; description = '' Bundled frontend dist (see `nix/packages/frontend.nix`). Output has `dashboard/` and `agent/` subdirectories — hive-c0re serves `dashboard/` via `tower_http::ServeDir` from the path passed in `HIVE_STATIC_DIR`. Override to ship a custom dashboard SPA; the JSON contract (`/api/state`, the SSE streams, the action endpoints) is the source of truth for any replacement. ''; }; assets = lib.mkOption { type = lib.types.package; defaultText = lib.literalExpression "hyperhive.packages.\${system}.assets"; description = '' Bundled static runtime assets (see `nix/packages/assets.nix`): the project's branding family + the claude system-prompt template + claude-settings JSON. Output has `share/hyperhive/{branding,prompts}/`; passed to hive-c0re's systemd unit via `HIVE_ASSETS_DIR` (`hive_sh4re::assets::*` resolve paths underneath). Override to ship customised branding or prompts without rebuilding the rust derivation. ''; }; xdgIcons = lib.mkOption { type = lib.types.package; defaultText = lib.literalExpression "hyperhive.packages.\${system}.xdg-icons"; description = '' XDG icon set + .desktop entries for hyperhive processes (see `nix/packages/hive-xdg-icons.nix`), installed into the host system packages so desktop environments can match hyperhive processes to their icon. ''; }; hyperhiveFlake = lib.mkOption { type = lib.types.str; defaultText = lib.literalMD "the hyperhive flake's own filtered source store path"; description = '' Store path of the hyperhive flake source (no fragment). hive-c0re reads it to tell which revision is running, and the swarm builder builds on it. The meta flake does not use it: that pins `hyperhive` by forge URL and `hyperhiveRev`. Wired by `nixosModules.default` to this flake's own filtered source. ''; }; hyperhiveRev = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; defaultText = lib.literalMD "the hyperhive flake's own `self.rev`"; description = '' Git rev of the hyperhive checkout this host runs. hive-c0re pins the generated meta flake's `hyperhive` input to `git+/hyperhive/hyperhive.git?rev=`, and `nixpkgs` follows that input. Wired by `nixosModules.default` from `self.rev`, which a dirty tree does not have; with `null`, hive-c0re refuses to render the meta flake. ''; }; agentBaseToplevel = lib.mkOption { type = lib.types.package; defaultText = lib.literalExpression "hyperhive.packages.x86_64-linux.agent-base-toplevel"; description = '' Pre-built agent-base container system closure, pulled into the host system closure when `preBuildAgentTemplates` is on. Wired by `nixosModules.default`; only evaluated when that option is enabled. ''; }; nixpkgsFlake = lib.mkOption { type = lib.types.str; default = "path:${pkgs.path}"; defaultText = lib.literalMD "`\"path:\${pkgs.path}\"`"; description = '' Store-path URL of the nixpkgs the swarm builder's check flake builds on. The generated meta flake does not use it: its `nixpkgs` follows `hyperhive/nixpkgs`. Defaults to `"path:''${pkgs.path}"`, the nixpkgs the host NixOS module was evaluated with. ''; }; preBuildAgentTemplates = lib.mkOption { type = lib.types.bool; default = false; example = true; description = '' Pre-fetch the agent-base container system closure, which every agent builds from, into the host's /nix/store as part of this host's NixOS build, instead of letting the first agent spawn do all the work. Enabling this adds roughly the full nixpkgs runtime closure + claude-code + the harness binary to your system closure size (low single-digit GB), but the first `nixos-container start` for any agent then completes in seconds instead of minutes because nothing's left to fetch. Off by default because the toplevel is pinned to `x86_64-linux` (nixos-containers run native arch). Enabling on an aarch64 host would force nix to build the x86 closure via cross or a remote builder, which is rarely what you want. Flip to `true` on an x86_64 host when you care more about first-spawn latency than host store size — or just `nix build .#agent-base-toplevel` once manually to warm the store. ''; }; modelPrices = lib.mkOption { type = lib.types.attrsOf ( lib.types.submodule { options = { input = lib.mkOption { type = lib.types.numbers.nonnegative; description = "USD per million input tokens."; }; output = lib.mkOption { type = lib.types.numbers.nonnegative; description = "USD per million output tokens."; }; cache_read = lib.mkOption { type = lib.types.numbers.nonnegative; description = "USD per million cache-read tokens."; }; cache_write = lib.mkOption { type = lib.types.numbers.nonnegative; description = "USD per million cache-creation (write) tokens."; }; }; } ); # Current Anthropic list prices for the Claude 4.x family (Opus # 4.x, Sonnet 4.x, Haiku 4.5); cache_write is the 1-hour cache-TTL # price (the default through the Claude subscription the agents run # on). Keep in sync with `builtin_prices` in # hive-c0re/src/stats/hive_stats.rs. default = { opus = { input = 5.0; output = 25.0; cache_read = 0.5; cache_write = 10.0; }; sonnet = { input = 3.0; output = 15.0; cache_read = 0.3; cache_write = 6.0; }; haiku = { input = 1.0; output = 5.0; cache_read = 0.1; cache_write = 2.0; }; }; example = { sonnet = { input = 3.0; output = 15.0; cache_read = 0.3; cache_write = 6.0; }; }; description = '' Per-model USD prices (per **million** tokens) used for the hive-wide cost *estimate* on the dashboard's ST4TS tab. Each key is a model-family short name matched case-insensitively as a substring of the active model id at runtime (e.g. `"sonnet"` matches `"claude-sonnet-4-5"`); the longest matching key wins, so a specific entry beats a generic family name. Any model not covered by this table falls back to hive-c0re's built-in estimate. The defaults track Anthropic list pricing at the time of writing — override them here to keep the estimate current without a code change. Passed to `hive-c0re serve` as JSON via `--model-prices`; read only by hive-c0re itself (not injected into containers). Changes apply on the next host rebuild. ''; }; agentCpuQuota = lib.mkOption { type = lib.types.str; default = "200%"; example = "400%"; description = '' systemd `CPUQuota=` applied to every agent container via a `container@h-.service.d/` drop-in written on each spawn/rebuild. Expressed as a percentage of one CPU core — `"200%"` allows each agent to use up to 2 cores. Bump this if agents are hitting CPU limits during builds or heavy tool use. For a hive-wide cap across all containers, set `systemd.slices.machine.serviceConfig.CPUQuota` in your NixOS config (all nspawn containers live in `machine.slice`). ''; }; agentMemoryMax = lib.mkOption { type = lib.types.str; default = "4G"; example = "8G"; description = '' systemd `MemoryMax=` applied to every agent container via the same drop-in as `agentCpuQuota`. ''; }; agentCpuWeight = lib.mkOption { type = lib.types.nullOr (lib.types.ints.between 1 10000); default = 80; example = null; description = '' systemd `CPUWeight=` applied to every agent container via the same drop-in as `agentCpuQuota`. This is the cgroup v2 `cpu.weight` relative share, **not** a cap: a low-weight container still gets the whole machine when nothing else wants it, and the weight only decides who yields under contention. That makes it the complement of `agentCpuQuota`, which throttles even on an idle host. The kernel default is `100`. The hyperhive default of `80` means agent containers yield slightly to everything that is *not* on this drop-in path — host services and the infrastructure containers (`hive-forge`, `hive-gateway`, `hive-matrix`), which stay at `100`. Note this is a hive-wide value, so it does not rank agents against *each other*: they all share one weight. Set to `null` to leave `CPUWeight=` out of the drop-in entirely — the container then inherits the kernel default and the generated unit file is identical to one from before this option existed. ''; }; agentIoWeight = lib.mkOption { type = lib.types.nullOr (lib.types.ints.between 1 10000); default = 80; example = null; description = '' systemd `IOWeight=` applied to every agent container via the same drop-in as `agentCpuQuota` — the block-IO counterpart of `agentCpuWeight`, with the same relative-share, contention-only semantics. Caveat: `IOWeight=` maps to the cgroup v2 `io.weight` knob, which is only honoured when the `io.cost` (blk-iocost) controller is enabled for the backing device, or when the device uses the BFQ scheduler. On a host running `none`/`mq-deadline`/`kyber` without iocost QoS configured, systemd writes the value and the kernel ignores it — harmless, but it will measure as a no-op. Check with `cat /sys/fs/cgroup/io.cost.qos` on the host, and set this to `null` to omit the setting rather than write one nothing reads. ''; }; buildSlots = lib.mkOption { type = lib.types.ints.positive; default = 1; example = 2; description = '' Number of nix-heavy job-queue nodes (container prebuilds, profile swaps, first-spawn creates, meta lock bumps) hive-c0re runs concurrently. The default of 1 serializes all heavy nix work; raise it on hosts with the cores/RAM to build several agent toplevels at once. Per-agent correctness is independent of this count — each agent's container-affecting operations are serialized by its lifecycle lease regardless. ''; }; adminUsers = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; example = [ "alice" ]; description = '' Login users granted **sudoless `hivectl`**. Each is added to the `hive-admin` group, which group-owns the host admin socket (`/run/hyperhive/host.sock`, mode `0660`) — so listed users drive `hivectl` (and thus the whole hive) without `sudo`. This is a real privilege grant: the admin socket is *full* hive control — spawn / kill / destroy / deploy — see `docs/trust-boundary/boundary.md`. Keep the list to trusted operators. Empty (the default) keeps the socket root-only, as before. ''; }; }; # What this hive hands its own agents through the meta-flake render. Per # host, so two hives may set them differently. options.services.hyperhive.deploy.hive-controller = { claudeCodePackage = lib.mkOption { type = lib.types.nullOr lib.types.package; default = null; example = lib.literalExpression "inputs.nixpkgs-unstable.legacyPackages.x86_64-linux.claude-code"; description = '' The `claude-code` build every agent on this hive runs, or `null` (the default) to leave each agent on the `claude-code` from its own nixpkgs — i.e. the nixpkgs pinned by hyperhive's own `flake.lock`. This is the one binary the whole hive is built around, and it moves fast enough that a release channel routinely trails unstable by weeks on it. An agent cannot fix that for itself: agents evaluate against the single nixpkgs hive-c0re injects, so an `agent.nix` has no other tree to reach for. Set this from a second nixpkgs in the host flake and every agent follows, without moving the nixpkgs the rest of the container is built from. What travels into the container is the **store path**, not the derivation: agents share the host's `/nix/store`, so the binary and its full closure are already reachable there — nothing needs rebuilding or copying. hive-c0re writes the path into each agent's generated flake as a plain string literal (a bare path fed to `lib.types.package` would run `builtins.storePath`, which is illegal under pure evaluation) and the agent module puts its `bin/` on the harness's PATH. The flip side of a plain string is that nothing in the agent's own closure refers to it, so the container cannot keep it alive. The **host** does that instead: this package is interpolated into `/etc/hyperhive/serve.json`, which puts it in the host's system closure — so it is gc-rooted by the running generation for exactly as long as that generation is the one the agents were rendered from. The cost is that `nix-collect-garbage` cannot reclaim an old `claude-code` until every agent has been rebuilt past it and the old generations are gone. The swarm's `swarm-claude-login` and `swarm-review-classifier` containers, where this host runs them, follow the same pin and keep their own nixpkgs' `claude-code` when it is `null`. They are not agents, so pinning an older build for an agent-side regression pins them too. ''; }; dashboardPort = lib.mkOption { type = lib.types.port; default = 7000; description = "TCP port this hive's hive-c0re dashboard listens on."; }; }; }