Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/nix/host-modules/hive-c0re/options.nix
atlas 9b1fe85706 claude: say on stderr when the hive's pinned claude-code is missing
A set HYPERHIVE_CLAUDE_CODE_PATH whose build is gone (collected after a
bump while the container still holds the old facts) made the wrapper
run the container's own claude-code silently, which can be a different
version. It now prints one line naming the missing path.

Refs #4857
2026-10-22 07:11:46 +02:00

389 lines
16 KiB
Nix
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.<system>.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+<forge>/hyperhive/hyperhive.git?rev=<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-<name>.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 at container start, through
# `/etc/hyperhive-hive-facts`. 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 hands the path to each
container at start as `HYPERHIVE_CLAUDE_CODE_PATH`, and the agent's
`claude` on PATH runs it, or the container's own `claude-code` when
the variable is absent or names nothing. A change reaches an agent
at its next container start, not through a rebuild.
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. ⚠️ An agent started under an older generation keeps
that generation's path until its next start, and only the old
generation roots it: collect old generations after restarting
the agents, or an agent still running the old path falls back to
its own `claude-code` at its next `claude` spawn, saying so on
stderr.
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. Agents get it
at container start as `HIVE_DASHBOARD_PORT`, for their web UI's
links back to the dashboard.
'';
};
};
}