diff --git a/README.md b/README.md index 5cac0715..c5f36cf8 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,8 @@ Minimal `flake.nix` for a host that runs hive-c0re: modules = [ hyperhive.nixosModules.default # hive-c0re + hive-forge in one import ({ ... }: { - hyperhive.enable = true; - # hyperhive.c0re.operatorPronouns = "they/them"; # default: "she/her" + services.hyperhive.enable = true; + # services.hyperhive.c0re.operatorPronouns = "they/them"; # default: "she/her" # ... rest of your host config system.stateVersion = "25.11"; @@ -78,7 +78,7 @@ manager container, and auto-rebuilds any container whose hyperhive rev goes stale. `claude-code` is unfree — hyperhive scopes the whitelist to itself, nothing for the operator to set. -Optional: set `hyperhive.c0re.preBuildAgentTemplates = true;` +Optional: set `services.hyperhive.c0re.preBuildAgentTemplates = true;` to pre-fetch the per-container system closures into your host's /nix/store as part of `nixos-rebuild`. First-agent-spawn then completes in seconds instead of minutes (no nixpkgs/claude-code @@ -88,19 +88,20 @@ system closure. Off by default (the toplevels are pinned to Alternatively warm the store manually: `nix build git+https://forge.darkest.space/hyperhive/hyperhive#agent-base-toplevel`. -Optional: set `hyperhive.domain = "example.com";` to define the +Optional: set `services.hyperhive.domain = "example.com";` to define the canonical hostname for hyperhive subsystems that need a stable public name. No default — subsystems that require it (currently: -`hyperhive.matrix`) assert non-null at eval time with a clear error -message if it is missing. +`services.hyperhive.matrix`) assert non-null at eval time with a clear +error message if it is missing. -Optional: set `hyperhive.matrix.enable = true;` to spin up a private -[matrix-tuwunel](https://github.com/matrix-construct/tuwunel) homeserver -in a nixos-container. Requires either `hyperhive.domain` or -`hyperhive.matrix.serverName` to be set (eval fails with a clear error -if both are absent). The `server_name` (embedded irrevocably in every -user and room ID) defaults to `matrix.`; override with -`hyperhive.matrix.serverName = "chat.example.com";` if needed. State +Optional: set `services.hyperhive.matrix.enable = true;` to spin up a +private [matrix-tuwunel](https://github.com/matrix-construct/tuwunel) +homeserver in a nixos-container. Requires either +`services.hyperhive.domain` or `services.hyperhive.matrix.serverName` +to be set (eval fails with a clear error if both are absent). The +`server_name` (embedded irrevocably in every user and room ID) +defaults to `matrix.`; override with +`services.hyperhive.matrix.serverName = "chat.example.com";` if needed. State lives at `/var/lib/nixos-containers/hive-matrix/`. Federation is enabled with an empty `trusted_servers` list; e2ee is deferred to a follow-up (#551). diff --git a/docs/turn-loop.md b/docs/turn-loop.md index 324dff12..1817a613 100644 --- a/docs/turn-loop.md +++ b/docs/turn-loop.md @@ -76,7 +76,7 @@ match wins): 1. `HIVE_CONTEXT_WINDOW_TOKENS_` env var, where `KEY` (lowercased) is a substring of the active model name. Injected - by the meta flake from `hyperhive.c0re.contextWindowTokens` + by the meta flake from `services.hyperhive.c0re.contextWindowTokens` (host-level NixOS option, defaults: haiku=200k, sonnet=1M, opus=1M). Override these for all agents at once without a per-agent config change. @@ -178,7 +178,7 @@ socket at `/run/hive/` once at startup: #519); everything else is shared. Then `{label}` and `{operator_pronouns}` get substituted in the assembled output. Pronouns come from `HIVE_OPERATOR_PRONOUNS` env (set by the meta - flake from `hyperhive.c0re.operatorPronouns`, default + flake from `services.hyperhive.c0re.operatorPronouns`, default `she/her`). Passed via `--system-prompt-file`. The shared per-turn plumbing lives in `hive_ag3nt::turn::{write_mcp_config, diff --git a/flake.nix b/flake.nix index 964a15f3..28656c70 100644 --- a/flake.nix +++ b/flake.nix @@ -225,7 +225,7 @@ agent-base = ./nix/templates/agent-base.nix; manager = ./nix/templates/manager.nix; # The hive-c0re module wants `pkgs.hyperhive` for its default - # `hyperhive.c0re.package`. To avoid making operators apply an + # `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. @@ -237,7 +237,7 @@ hyperhiveAssets = system: self.packages.${system}.assets; hyperhiveFlake = "${self}"; # Per-container toplevels — wired into `system.extraDependencies` - # when `hyperhive.c0re.preBuildAgentTemplates` is on so the + # 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 @@ -252,7 +252,7 @@ # in hive-forge). Intended usage: # # imports = [ hyperhive.nixosModules.default ]; - # hyperhive.enable = true; + # services.hyperhive.enable = true; # default = self.nixosModules.hive-c0re; }; diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index f6c7753d..8d1630e6 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -13,22 +13,22 @@ ... }: let - cfg = config.hyperhive.c0re; + cfg = config.services.hyperhive.c0re; in { # The forge is part of the standard install — hive-c0re mirrors # every agent's applied config repo into it. On by default; opt out - # with `hyperhive.forge.enable = false`. hive-matrix is opt-in (off - # by default) and asserts that `hyperhive.domain` is set before it - # can be enabled. + # with `services.hyperhive.forge.enable = false`. hive-matrix is + # opt-in (off by default) and asserts that `services.hyperhive.domain` + # is set before it can be enabled. imports = [ ./hive-forge.nix ./hive-matrix.nix ]; # Top-level hyperhive enable flag. When true, automatically enables - # hive-c0re and hyperhive subsystems. - options.hyperhive.enable = lib.mkEnableOption "hyperhive — the agent swarm coordinator"; + # hive-c0re and the on-by-default hyperhive subsystems. + options.services.hyperhive.enable = lib.mkEnableOption "hyperhive — the agent swarm coordinator"; # Top-level option shared by any hyperhive subsystem that needs a # stable hostname (matrix server_name today, forge ROOT_URL likely @@ -36,36 +36,27 @@ in # configs that don't set it still evaluate; subsystems that # actually need it (matrix) assert non-null in their own config # block with a helpful message. - options.hyperhive.domain = lib.mkOption { + options.services.hyperhive.domain = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "darkest.space"; description = '' Canonical host domain for hyperhive subsystems that need a - stable name (currently: `hyperhive.matrix.serverName` derives - from this, defaulting to `matrix.''${hyperhive.domain}` when - `serverName` is null). No default — subsystems that opt to - require it assert non-null in their own config and fail eval - with a helpful message if it's missing. + stable name (currently: `services.hyperhive.matrix.serverName` + derives from this, defaulting to + `matrix.''${services.hyperhive.domain}` when `serverName` is + null). No default — subsystems that opt to require it assert + non-null in their own config and fail eval with a helpful + message if it's missing. ''; }; - # Deprecated alias for backward compatibility. Remove in v0.2. - options.services.hive-c0re.enable = lib.mkOption { - type = lib.types.bool; - default = false; - description = '' - **DEPRECATED** (as of #612). Use `hyperhive.enable = true` or - `hyperhive.c0re.enable = true` instead. This option is maintained - for backward compatibility and will be removed in a future release. - ''; - }; - - options.hyperhive.c0re = { + options.services.hyperhive.c0re = { enable = lib.mkOption { type = lib.types.bool; - default = config.hyperhive.enable; - description = "Enable hive-c0re coordinator daemon (auto-enabled by hyperhive.enable)."; + 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; @@ -185,87 +176,77 @@ in }; }; - config = lib.mkMerge [ - # Backward-compatibility redirect for deprecated services.hive-c0re.enable - (lib.mkIf config.services.hive-c0re.enable { - hyperhive.c0re.enable = true; - warnings = [ - "services.hive-c0re.enable is deprecated (as of #612). Use 'hyperhive.enable = true' or 'hyperhive.c0re.enable = true' instead." - ]; - }) - # Main config block - (lib.mkIf cfg.enable { - environment.systemPackages = [ - cfg.package + config = lib.mkIf cfg.enable { + environment.systemPackages = [ + cfg.package + pkgs.git + ]; + + # Pull the per-container toplevels into the host system closure + # (#97). `system.extraDependencies` adds paths to the system build + # without referencing them at runtime — nixos-rebuild fetches / + # builds them, they end up in /nix/store, and the first + # nixos-container update + start for an agent has nothing left to + # do. Gated because the closure is sizeable and pinned to x86_64. + system.extraDependencies = lib.optionals cfg.preBuildAgentTemplates [ + agentBaseToplevel + managerToplevel + ]; + + # Dashboard + per-container web UIs share the host's network namespace and + # need their ports reachable. Dashboard: `cfg.dashboardPort` (default 7000). + # Manager: 8000. Sub-agents: 8100..8999 (deterministic hash; see + # `lifecycle::agent_web_port`). + networking.firewall.allowedTCPPorts = [ + cfg.dashboardPort + 8000 + ]; + networking.firewall.allowedTCPPortRanges = [ + { + from = 8100; + to = 8999; + } + ]; + + systemd.services.hive-c0re = { + description = "hyperhive coordinator daemon"; + wantedBy = [ "multi-user.target" ]; + path = [ pkgs.git + "/run/current-system/sw" ]; - - # Pull the per-container toplevels into the host system closure - # (#97). `system.extraDependencies` adds paths to the system build - # without referencing them at runtime — nixos-rebuild fetches / - # builds them, they end up in /nix/store, and the first - # nixos-container update + start for an agent has nothing left to - # do. Gated because the closure is sizeable and pinned to x86_64. - system.extraDependencies = lib.optionals cfg.preBuildAgentTemplates [ - agentBaseToplevel - managerToplevel - ]; - - # Dashboard + per-container web UIs share the host's network namespace and - # need their ports reachable. Dashboard: `cfg.dashboardPort` (default 7000). - # Manager: 8000. Sub-agents: 8100..8999 (deterministic hash; see - # `lifecycle::agent_web_port`). - networking.firewall.allowedTCPPorts = [ - cfg.dashboardPort - 8000 - ]; - networking.firewall.allowedTCPPortRanges = [ - { - from = 8100; - to = 8999; - } - ]; - - systemd.services.hive-c0re = { - description = "hyperhive coordinator daemon"; - wantedBy = [ "multi-user.target" ]; - path = [ - pkgs.git - "/run/current-system/sw" - ]; - environment = { - HYPERHIVE_GIT = "${pkgs.git}/bin/git"; - # Path to the dashboard static dist. The hive-c0re axum router - # serves this via `tower_http::ServeDir` for any path it doesn't - # match against an API/action route. - HIVE_STATIC_DIR = "${cfg.frontend}/dashboard"; - # Path to the static runtime asset tree (branding + claude - # prompts). `hive_sh4re::assets::*` reads paths underneath. - # `forge.rs` reads the avatar PNGs from here on startup. - HIVE_ASSETS_DIR = "${cfg.assets}/share/hyperhive"; - } - // lib.optionalAttrs config.hyperhive.forge.enable { - # Agents poll this URL for Forgejo notifications. Derived from - # hyperhive.forge.{domain,httpPort} so it tracks forge config changes. - HIVE_FORGE_URL = "http://${config.hyperhive.forge.domain}:${toString config.hyperhive.forge.httpPort}"; - } - // lib.optionalAttrs config.hyperhive.matrix.gui.enable { - # Optional matrix-GUI static dist mounted at /matrix/ by the - # dashboard router (#607 v0). Pre-#15 / pre-nginx-front: this is - # the simplest same-origin shape — fluffychat-web ships as a - # static dist, no runtime daemon needed. - HIVE_MATRIX_GUI_DIR = "${config.hyperhive.matrix.gui.package}"; - }; - serviceConfig = { - ExecStart = "${cfg.package}/bin/hive-c0re --socket /run/hyperhive/host.sock serve --hyperhive-flake ${cfg.hyperhiveFlake} --dashboard-port ${toString cfg.dashboardPort} --operator-pronouns ${lib.escapeShellArg cfg.operatorPronouns} --context-window-tokens ${lib.escapeShellArg (builtins.toJSON cfg.contextWindowTokens)}"; - Restart = "on-failure"; - RestartSec = 2; - RuntimeDirectory = "hyperhive"; - RuntimeDirectoryMode = "0750"; - RuntimeDirectoryPreserve = "yes"; - StateDirectory = "hyperhive"; - }; + environment = { + HYPERHIVE_GIT = "${pkgs.git}/bin/git"; + # Path to the dashboard static dist. The hive-c0re axum router + # serves this via `tower_http::ServeDir` for any path it doesn't + # match against an API/action route. + HIVE_STATIC_DIR = "${cfg.frontend}/dashboard"; + # Path to the static runtime asset tree (branding + claude + # prompts). `hive_sh4re::assets::*` reads paths underneath. + # `forge.rs` reads the avatar PNGs from here on startup. + HIVE_ASSETS_DIR = "${cfg.assets}/share/hyperhive"; + } + // lib.optionalAttrs config.services.hyperhive.forge.enable { + # Agents poll this URL for Forgejo notifications. Derived from + # services.hyperhive.forge.{domain,httpPort} so it tracks forge config changes. + HIVE_FORGE_URL = "http://${config.services.hyperhive.forge.domain}:${toString config.services.hyperhive.forge.httpPort}"; + } + // lib.optionalAttrs config.services.hyperhive.matrix.gui.enable { + # Optional matrix-GUI static dist mounted at /matrix/ by the + # dashboard router (#607 v0). Pre-#15 / pre-nginx-front: this is + # the simplest same-origin shape — fluffychat-web ships as a + # static dist, no runtime daemon needed. + HIVE_MATRIX_GUI_DIR = "${config.services.hyperhive.matrix.gui.package}"; }; - }) - ]; + serviceConfig = { + ExecStart = "${cfg.package}/bin/hive-c0re --socket /run/hyperhive/host.sock serve --hyperhive-flake ${cfg.hyperhiveFlake} --dashboard-port ${toString cfg.dashboardPort} --operator-pronouns ${lib.escapeShellArg cfg.operatorPronouns} --context-window-tokens ${lib.escapeShellArg (builtins.toJSON cfg.contextWindowTokens)}"; + Restart = "on-failure"; + RestartSec = 2; + RuntimeDirectory = "hyperhive"; + RuntimeDirectoryMode = "0750"; + RuntimeDirectoryPreserve = "yes"; + StateDirectory = "hyperhive"; + }; + }; + }; } diff --git a/nix/modules/hive-forge.nix b/nix/modules/hive-forge.nix index fb83ff94..73639373 100644 --- a/nix/modules/hive-forge.nix +++ b/nix/modules/hive-forge.nix @@ -5,7 +5,7 @@ ... }: let - cfg = config.hyperhive.forge; + cfg = config.services.hyperhive.forge; in { # Private Forgejo for hyperhive agents, wrapped in a nixos-container @@ -24,7 +24,7 @@ in # and survives container restart / host reboot. To wipe, destroy the # container. - options.hyperhive.forge = { + options.services.hyperhive.forge = { enable = lib.mkOption { type = lib.types.bool; default = true; @@ -33,7 +33,7 @@ in hyperhive agents. On by default: hive-c0re mirrors every agent's applied config repo into the forge's `agent-configs` org, so the forge is part of the standard install. Set - `hyperhive.forge.enable = false` to opt out. + `services.hyperhive.forge.enable = false` to opt out. ''; }; @@ -220,19 +220,22 @@ in Group = "forgejo"; }; environment.GNUPGHOME = "/var/lib/forgejo/.gnupg"; - path = [ pkgs.gnupg pkgs.coreutils ]; + path = [ + pkgs.gnupg + pkgs.coreutils + ]; script = '' - mkdir -p "$GNUPGHOME" - chmod 700 "$GNUPGHOME" - gpg --batch --gen-key <<'EOF' -%no-protection -Key-Type: RSA -Key-Length: 4096 -Name-Real: HyperHive Forge -Name-Email: forgejo@hive -Expire-Date: 0 -EOF - touch "$GNUPGHOME/hive-key-init.stamp" + mkdir -p "$GNUPGHOME" + chmod 700 "$GNUPGHOME" + gpg --batch --gen-key <<'EOF' + %no-protection + Key-Type: RSA + Key-Length: 4096 + Name-Real: HyperHive Forge + Name-Email: forgejo@hive + Expire-Date: 0 + EOF + touch "$GNUPGHOME/hive-key-init.stamp" ''; }; }; diff --git a/nix/modules/hive-matrix.nix b/nix/modules/hive-matrix.nix index fb5244e2..a2ef7abe 100644 --- a/nix/modules/hive-matrix.nix +++ b/nix/modules/hive-matrix.nix @@ -5,8 +5,8 @@ ... }: let - cfg = config.hyperhive.matrix; - hyperhiveDomain = config.hyperhive.domain; + cfg = config.services.hyperhive.matrix; + hyperhiveDomain = config.services.hyperhive.domain; effectiveServerName = if cfg.serverName != null then cfg.serverName else "matrix.${hyperhiveDomain}"; in @@ -45,7 +45,7 @@ in # so the agent's matrix MCP client can authenticate without ever # seeing the shared registration token. - options.hyperhive.matrix = { + options.services.hyperhive.matrix = { enable = lib.mkOption { type = lib.types.bool; default = false; @@ -53,7 +53,7 @@ in Run hive-matrix — a private matrix-tuwunel homeserver (in a nixos-container) for hyperhive agents. Off by default while the integration phases in; flip to `true` once the operator - has set `hyperhive.domain` and is ready to onboard agents. + has set `services.hyperhive.domain` and is ready to onboard agents. ''; }; @@ -77,7 +77,7 @@ in (`@argus:`) and room ID minted on this homeserver. CRITICAL: must be stable from day one because it's embedded irrevocably in the identifiers. Defaults to - `matrix.''${hyperhive.domain}` (always a subdomain — keeps + `matrix.''${services.hyperhive.domain}` (always a subdomain — keeps the root domain free for the dashboard or forge). Override here only if you need a name that doesn't follow the `matrix.` shape. @@ -169,7 +169,7 @@ in without standing up a separate gateway. Same-origin via hive-c0re is the simplest single-host - shape; the post-#15 nginx-front re-root (`https://matrix.''${hyperhive.domain}`) + shape; the post-#15 nginx-front re-root (`https://matrix.''${services.hyperhive.domain}`) is tracked separately in #609. fluffychat-web supports per-login server pick — point it at the in-host tuwunel URL (`http://localhost:8008` by default) the first time. @@ -196,17 +196,17 @@ in # mara on #548: "there is no default, but it is required. add # assertion." — fail eval with a helpful message rather than # spawning a homeserver with a bogus server_name we can never - # change later. `hyperhive.domain` is host-wide; matrix derives + # change later. `services.hyperhive.domain` is host-wide; matrix derives # the server_name from it (or from `cfg.serverName` if the # operator wants to override). assertions = [ { assertion = hyperhiveDomain != null || cfg.serverName != null; message = '' - hyperhive.matrix.enable = true requires either: - - hyperhive.domain set to your host's canonical domain + services.hyperhive.matrix.enable = true requires either: + - services.hyperhive.domain set to your host's canonical domain (recommended; shared with forge / dashboard), or - - hyperhive.matrix.serverName set explicitly. + - services.hyperhive.matrix.serverName set explicitly. The matrix server_name is embedded into every user ID and room ID on this homeserver — it cannot be changed later diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index 8cf9a68f..f86e600c 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -36,7 +36,7 @@ `"haiku"`, `"sonnet"`, `"opus"` (or any future identifier). Context window sizes are looked up at runtime from the `HIVE_CONTEXT_WINDOW_TOKENS_` env vars injected by the - meta flake; override sizes via `hyperhive.c0re.contextWindowTokens` + meta flake; override sizes via `services.hyperhive.c0re.contextWindowTokens` on the host. ''; }; @@ -595,7 +595,7 @@ # both the harness binary and any user-shell `cargo run` inside the # container resolve them from the same path. # HIVE_CONTEXT_WINDOW_TOKENS_* are injected by the meta flake from the - # host-level `hyperhive.c0re.contextWindowTokens` option — not set here. + # host-level `services.hyperhive.c0re.contextWindowTokens` option — not set here. environment.variables = { HIVE_DEFAULT_MODEL = config.hyperhive.model; HIVE_ASSETS_DIR = "${pkgs.hyperhive-assets}/share/hyperhive";