hyperhive/nix/host-modules/hive-c0re/options.nix
iris 9efe6f64b6 move swaggerUiTheme nix option out of c0re's namespace
services.hyperhive.c0re.swaggerUiTheme -> services.hyperhive.gateway.swaggerUiTheme.
hive-c0re has no relationship to this option (it's consumed entirely
by the gateway's nginx config) since the previous two commits moved
the theme files out of hive-c0re/ and gateway to hosting the whole
dist itself — the option shouldn't live in c0re's namespace either.

Pure option move: default wiring in flake.nix now sets
services.hyperhive.gateway.swaggerUiTheme instead of the c0re one;
hive-gateway/default.nix reads it via the module's own already-bound
cfg (services.hyperhive.gateway) instead of reaching into c0re's.

Verified: nix build .#swagger-ui-theme unaffected (same store path,
pure option-plumbing change, no content change); a full nixosSystem
eval of nixosModules.default resolves the option at its new path and
confirms the gateway's /api/docs/ location still aliases the correct
store path.
2026-08-02 21:24:57 +02:00

423 lines
18 KiB
Nix

# 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.
{
pkgs,
lib,
config,
...
}:
{
options.services.hyperhive.c0re = {
enable = lib.mkOption {
type = lib.types.bool;
default = config.services.hyperhive.enable;
defaultText = lib.literalExpression "config.services.hyperhive.enable";
description = "Enable hive-c0re coordinator daemon (auto-enabled by services.hyperhive.enable).";
};
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 = ''
URL of the hyperhive flake (no fragment). Inlined into each
per-agent `flake.nix` at `inputs.hyperhive.url`. The per-agent
flake then pulls `hyperhive.nixosConfigurations.agent-base` to
build the container. Wired by `nixosModules.default` to this
flake's own filtered source only override if you want agents
tracking a different ref.
'';
};
hyperhiveDocs = lib.mkOption {
type = lib.types.str;
defaultText = lib.literalMD "the docs/ tree's own store path";
description = ''
URL of the narrow `docs/` source (no fragment). Inlined into the
generated meta `flake.nix` at `inputs.hyperhive-docs.url` and
threaded to each agent as `hyperhive.docs.source`, from which the
harness resolves `$HIVE_DOCS_DIR`. Its own store path separate
from `hyperhiveFlake` so a doc edit only re-locks this input
instead of rebuilding every agent container.
'';
};
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.
'';
};
managerToplevel = lib.mkOption {
type = lib.types.package;
defaultText = lib.literalExpression "hyperhive.packages.x86_64-linux.ruth-toplevel";
description = ''
Pre-built manager (ruth) container system closure see
`agentBaseToplevel`.
'';
};
nixpkgsFlake = lib.mkOption {
type = lib.types.str;
default = "path:${pkgs.path}";
defaultText = lib.literalMD "`\"path:\${pkgs.path}\"`";
description = ''
Store-path URL for the `nixpkgs` input in the generated meta
flake. The meta flake declares this as a top-level input and
wires `inputs.hyperhive.inputs.nixpkgs.follows = "nixpkgs"` so
every agent container evaluates with this exact nixpkgs.
Defaults to `"path:''${pkgs.path}"` the store path of the
nixpkgs the host NixOS module was evaluated with. When the
operator sets `inputs.hyperhive.inputs.nixpkgs.follows =
"nixpkgs"` in their host flake, `pkgs.path` resolves to the
host's own nixpkgs, so agents transparently track the same
channel as the host.
Override to pin agents to a specific nixpkgs version regardless
of the host's channel.
'';
};
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 runs, or `null` (the
default) to leave each agent on the `claude-code` from its own
nixpkgs i.e. whatever `nixpkgsFlake` resolves to.
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.
'';
};
dashboardPort = lib.mkOption {
type = lib.types.port;
default = 7000;
description = "TCP port the hive-c0re dashboard listens on.";
};
operatorPronouns = lib.mkOption {
type = lib.types.str;
default = "she/her";
example = "they/them";
description = ''
Operator pronouns, free text. Threaded into every agent
container as the `HIVE_OPERATOR_PRONOUNS` env var; the
harness substitutes it into the agent / manager system
prompt at boot so claude refers to the operator naturally
in third person ("ask her", "tell them", etc.). Changes
propagate to running agents on the next ` R3BU1LD`
forwards as a meta flake env-var bump, no per-agent
approval needed.
'';
};
preBuildAgentTemplates = lib.mkOption {
type = lib.types.bool;
default = false;
example = true;
description = ''
Pre-fetch the per-container system closures (agent-base +
manager toplevels) 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 toplevels are 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.
'';
};
contextWindowTokens = lib.mkOption {
type = lib.types.attrsOf lib.types.int;
default = {
haiku = 200000;
sonnet = 1000000;
opus = 1000000;
};
example = {
haiku = 150000;
sonnet = 900000;
};
description = ''
Per-model context-window sizes in tokens. Each key is a
model-family short name matched case-insensitively as a
substring of the active model name at runtime (e.g. `"sonnet"`
matches `"claude-sonnet-4-5"`). The defaults cover the known
Anthropic families; add entries for new models or override
existing ones here to change the window for all agents at once.
Passed to `hive-c0re serve` as JSON and injected into every
container's harness service environment as
`HIVE_CONTEXT_WINDOW_TOKENS_<KEY_UPPER>`. Changes propagate
on the next ` R3BU1LD` no per-agent approval needed.
'';
};
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/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-ci`, `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/boundary.md`.
Keep the list to trusted operators. Empty (the default) keeps the
socket root-only, as before.
'';
};
};
}