From 3662eda440e7cd049bf60bfbdd0283f79195abfb Mon Sep 17 00:00:00 2001 From: atlas Date: Thu, 17 Sep 2026 19:29:17 +0200 Subject: [PATCH 1/2] nix: move the agent option namespace under services.hyperhive.agent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every per-agent harness option lived at the top-level `hyperhive.*` while the host tier has always been `services.hyperhive.*`. Move all 52 agent-tier option leaves (33 top-level names across 16 modules) to `services.hyperhive.agent.*`, repoint every read, and keep existing agent configs evaluating through one `mkRenamedOptionModule` per old leaf path in the new nix/agent-modules/renamed-options.nix. The shims are per leaf rather than per namespace: `user`, `mcp`, `otel`, `queue`, `docs`, `forge`, `frontend`, `github`, `gui`, `logs`, `matrix` and `cargo` are plain attrsets of declarations, not submodule-typed options, so a parent-path rename would not reach their children. Three read-only options (`frontend.mergedDist`, `queue.clientIdFile`, `queue.clientSecretFile`) deliberately get no shim — a rename contributes a definition, which a read-only option refuses; the exclusions are commented in place. Refs #4473 --- flake.nix | 2 +- nix/agent-modules/agent-service.nix | 78 +++++----- nix/agent-modules/bash-env.nix | 32 ++-- nix/agent-modules/claude-settings.nix | 41 ++--- nix/agent-modules/dashboard-links.nix | 48 +++--- nix/agent-modules/default.nix | 31 ++-- nix/agent-modules/docs.nix | 16 +- nix/agent-modules/forge.nix | 39 ++--- nix/agent-modules/frontend.nix | 50 +++--- nix/agent-modules/github.nix | 14 +- nix/agent-modules/logs.nix | 8 +- nix/agent-modules/matrix.nix | 72 ++++----- nix/agent-modules/mcp.nix | 67 ++++---- nix/agent-modules/otel.nix | 18 ++- nix/agent-modules/packages.nix | 2 +- nix/agent-modules/queue.nix | 10 +- nix/agent-modules/renamed-options.nix | 213 ++++++++++++++++++++++++++ nix/agent-modules/screen.nix | 8 +- nix/agent-modules/user.nix | 50 +++--- nix/agent-modules/weston-vnc.nix | 20 +-- nix/templates/ruth.nix | 2 +- 21 files changed, 531 insertions(+), 290 deletions(-) create mode 100644 nix/agent-modules/renamed-options.nix diff --git a/flake.nix b/flake.nix index 8f831359..870a4b2e 100644 --- a/flake.nix +++ b/flake.nix @@ -82,7 +82,7 @@ agentPackages = { lib, pkgs, ... }: { - hyperhive.packages = lib.mapAttrs (_: lib.mkDefault) { + services.hyperhive.agent.packages = lib.mapAttrs (_: lib.mkDefault) { inherit (self.packages.${pkgs.stdenv.hostPlatform.system}) hive-agent hive-agent-mcp diff --git a/nix/agent-modules/agent-service.nix b/nix/agent-modules/agent-service.nix index db5af618..b806ac33 100644 --- a/nix/agent-modules/agent-service.nix +++ b/nix/agent-modules/agent-service.nix @@ -9,11 +9,11 @@ ... }: let - userName = config.hyperhive.user.name; + userName = config.services.hyperhive.agent.user.name; homeDir = "/home/${userName}"; in { - options.hyperhive.model = lib.mkOption { + options.services.hyperhive.agent.model = lib.mkOption { type = lib.types.str; default = "haiku"; example = "sonnet"; @@ -34,7 +34,7 @@ in ''; }; - options.hyperhive.availableModels = lib.mkOption { + options.services.hyperhive.agent.availableModels = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ "haiku" @@ -54,7 +54,7 @@ in Configure hive-wide by setting a shared default (e.g. in your `agent.nix` shared defaults) or per-agent to narrow the menu — for example a haiku-only agent can hide `opus` and `sonnet`. The *current* model is - still set by `hyperhive.model` and remains switchable at runtime via the + still set by `services.hyperhive.agent.model` and remains switchable at runtime via the UI; this option only controls which choices the picker presents. Values are the short model names that `claude --model` accepts: @@ -62,7 +62,7 @@ in ''; }; - options.hyperhive.effortLevel = lib.mkOption { + options.services.hyperhive.agent.effortLevel = lib.mkOption { type = lib.types.enum [ "low" "medium" @@ -87,7 +87,7 @@ in ''; }; - options.hyperhive.autoCompact = lib.mkOption { + options.services.hyperhive.agent.autoCompact = lib.mkOption { type = lib.types.bool; default = true; description = '' @@ -106,7 +106,7 @@ in ''; }; - options.hyperhive.useApiKey = lib.mkOption { + options.services.hyperhive.agent.useApiKey = lib.mkOption { type = lib.types.bool; default = false; description = '' @@ -121,14 +121,14 @@ in `~/.claude/` as "needs login" for this agent (see `hive_c0re::container_view`'s `needs_login` computation). - Set this AND `hyperhive.backendEnvironmentFile` together — this + Set this AND `services.hyperhive.agent.backendEnvironmentFile` together — this option changes what the harness believes about its own login state, the other actually supplies the credentials `claude` reads. Neither is useful alone. ''; }; - options.hyperhive.backendEnvironmentFile = lib.mkOption { + options.services.hyperhive.agent.backendEnvironmentFile = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "/agents/myagent/harness/openrouter.env"; @@ -160,12 +160,12 @@ in so a path that doesn't exist yet — an operator setting this option before creating the file, or a fresh host rebuild before state is restored — makes systemd skip it rather than refuse to start the - harness. See `hyperhive.useApiKey`'s doc for the option this one is + harness. See `services.hyperhive.agent.useApiKey`'s doc for the option this one is paired with. ''; }; - options.hyperhive.extraWebProxies = lib.mkOption { + options.services.hyperhive.agent.extraWebProxies = lib.mkOption { type = lib.types.attrsOf lib.types.str; default = { }; example = lib.literalExpression ''{ "stats" = "http://127.0.0.1:3737"; }''; @@ -192,21 +192,21 @@ in config = { assertions = [ - # hyperhive.model must be a non-empty string — an empty value causes + # services.hyperhive.agent.model must be a non-empty string — an empty value causes # the harness to pass an invalid model flag to claude. { - assertion = config.hyperhive.model != ""; - message = "hyperhive.model must not be empty (set it to e.g. \"haiku\" or \"sonnet\")"; + assertion = config.services.hyperhive.agent.model != ""; + message = "services.hyperhive.agent.model must not be empty (set it to e.g. \"haiku\" or \"sonnet\")"; } # The current model must appear in the quick-picker menu, otherwise the # UI would offer no way back to the model the agent is actually running. { assertion = - config.hyperhive.availableModels == [ ] - || builtins.elem config.hyperhive.model config.hyperhive.availableModels; + config.services.hyperhive.agent.availableModels == [ ] + || builtins.elem config.services.hyperhive.agent.model config.services.hyperhive.agent.availableModels; message = - "hyperhive.model (\"${config.hyperhive.model}\") must be one of " - + "hyperhive.availableModels ([ ${lib.concatStringsSep " " config.hyperhive.availableModels} ]) " + "services.hyperhive.agent.model (\"${config.services.hyperhive.agent.model}\") must be one of " + + "services.hyperhive.agent.availableModels ([ ${lib.concatStringsSep " " config.services.hyperhive.agent.availableModels} ]) " + "— add it to the list or change the model."; } ]; @@ -214,17 +214,17 @@ in # HIVE_DEFAULT_MODEL seeds the initial model selection when no # persisted model choice exists in the state dir. environment.variables = { - HIVE_DEFAULT_MODEL = config.hyperhive.model; + HIVE_DEFAULT_MODEL = config.services.hyperhive.agent.model; # Comma-separated menu for the per-agent UI model quick-picker - # (see hyperhive.availableModels). The harness surfaces it to the + # (see services.hyperhive.agent.availableModels). The harness surfaces it to the # frontend; an empty value falls back to the built-in default list. - HIVE_AVAILABLE_MODELS = lib.concatStringsSep "," config.hyperhive.availableModels; - # Per-agent baseline effort (see hyperhive.effortLevel). The + HIVE_AVAILABLE_MODELS = lib.concatStringsSep "," config.services.hyperhive.agent.availableModels; + # Per-agent baseline effort (see services.hyperhive.agent.effortLevel). The # harness resolves operator-override-file → this env → "medium" # and passes it to claude --effort at turn launch. - HIVE_DEFAULT_EFFORT = config.hyperhive.effortLevel; + HIVE_DEFAULT_EFFORT = config.services.hyperhive.agent.effortLevel; } - // lib.optionalAttrs (!config.hyperhive.autoCompact) { + // lib.optionalAttrs (!config.services.hyperhive.agent.autoCompact) { # Zero watermark disables proactive compaction; the reactive path # (compact-on-overflow) still fires when the session is truly full. HIVE_COMPACT_WATERMARK_TOKENS = "0"; @@ -253,8 +253,8 @@ in environment = { SHELL = "${pkgs.bashInteractive}/bin/bash"; HOME = homeDir; - HIVE_STATIC_DIR = "${config.hyperhive.frontend.mergedDist}"; - HIVE_ASSETS_DIR = "${config.hyperhive.packages.assets}/share/hyperhive"; + HIVE_STATIC_DIR = "${config.services.hyperhive.agent.frontend.mergedDist}"; + HIVE_ASSETS_DIR = "${config.services.hyperhive.agent.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 @@ -274,30 +274,30 @@ in # (see ./mcp.nix) via the same option. Always set — network # isolation is unconditional, so a fixed per-container port is # collision-free. - HYPERHIVE_MCP_HTTP_PORT = toString config.hyperhive.mcp.httpPort; + HYPERHIVE_MCP_HTTP_PORT = toString config.services.hyperhive.agent.mcp.httpPort; } - // lib.optionalAttrs config.hyperhive.gui.enable { + // lib.optionalAttrs config.services.hyperhive.agent.gui.enable { # Tells the harness which fixed VNC port weston bound, and (by # its presence) that gui is enabled — the harness `/screen/ws` # relay reads this instead of a runtime marker file. The port is # container-local + fixed (network isolation is unconditional), # so the same value for every gui agent is fine. See - # ./weston-vnc.nix::hyperhive.gui.vncPort. - HIVE_GUI_VNC_PORT = toString config.hyperhive.gui.vncPort; + # ./weston-vnc.nix::services.hyperhive.agent.gui.vncPort. + HIVE_GUI_VNC_PORT = toString config.services.hyperhive.agent.gui.vncPort; } - // lib.optionalAttrs (config.hyperhive.extraWebProxies != { }) { + // lib.optionalAttrs (config.services.hyperhive.agent.extraWebProxies != { }) { # JSON object {"": ""} for the transparent - # reverse-proxies. See `hyperhive.extraWebProxies` option + # reverse-proxies. See `services.hyperhive.agent.extraWebProxies` option # and `web_ui/proxy.rs::extra_proxy_service`. - HIVE_EXTRA_WEB_PROXIES = builtins.toJSON config.hyperhive.extraWebProxies; + HIVE_EXTRA_WEB_PROXIES = builtins.toJSON config.services.hyperhive.agent.extraWebProxies; } - // lib.optionalAttrs config.hyperhive.useApiKey { + // lib.optionalAttrs config.services.hyperhive.agent.useApiKey { # Tells the harness not to wait for a Claude OAuth session — see - # `hyperhive.useApiKey`'s own description for the full mechanism. + # `services.hyperhive.agent.useApiKey`'s own description for the full mechanism. HIVE_USE_API_KEY = "1"; }; serviceConfig = { - ExecStart = "${config.hyperhive.packages.hive-agent}/bin/${binary}"; + ExecStart = "${config.services.hyperhive.agent.packages.hive-agent}/bin/${binary}"; # Pin the journal identity to the binary name (otherwise systemd # derives SyslogIdentifier from the ExecStart basename). SyslogIdentifier = binary; @@ -321,10 +321,10 @@ in User = userName; Group = userName; } - // lib.optionalAttrs (config.hyperhive.backendEnvironmentFile != null) { - # See `hyperhive.backendEnvironmentFile`'s own description for + // lib.optionalAttrs (config.services.hyperhive.agent.backendEnvironmentFile != null) { + # See `services.hyperhive.agent.backendEnvironmentFile`'s own description for # the file shape and the leading-`-` rationale. - EnvironmentFile = "-${config.hyperhive.backendEnvironmentFile}"; + EnvironmentFile = "-${config.services.hyperhive.agent.backendEnvironmentFile}"; }; }; }; diff --git a/nix/agent-modules/bash-env.nix b/nix/agent-modules/bash-env.nix index 302ece25..ad649427 100644 --- a/nix/agent-modules/bash-env.nix +++ b/nix/agent-modules/bash-env.nix @@ -16,7 +16,7 @@ # all contributions across modules into one file. Generic by # design so future hooks don't need to rename this file or # invent a parallel dispatcher. - options.hyperhive._bashEnvFragments = lib.mkOption { + options.services.hyperhive.agent._bashEnvFragments = lib.mkOption { type = lib.types.lines; default = ""; internal = true; @@ -27,11 +27,11 @@ unset, and the interactive bashrc hook is omitted — zero cost when no feature is on. Internal — set indirectly via the per-feature options that own the gate (e.g. - `hyperhive.cargo.shortMessages`). + `services.hyperhive.agent.cargo.shortMessages`). ''; }; - options.hyperhive.cargo.shortMessages = lib.mkOption { + options.services.hyperhive.agent.cargo.shortMessages = lib.mkOption { type = lib.types.bool; default = true; example = false; @@ -45,7 +45,7 @@ carry no signal beyond the warning/error summary. Implementation: contributes a `cargo` shell function to - `/etc/hyperhive/bash-env.sh` (see `hyperhive._bashEnvFragments`). + `/etc/hyperhive/bash-env.sh` (see `services.hyperhive.agent._bashEnvFragments`). Loaded via `BASH_ENV` for non-interactive shells (`bash -c` — what the claude `Bash` tool runs) and sourced from `programs.bash.interactiveShellInit` for interactive shells. @@ -70,7 +70,7 @@ # back to the un-wrapped binary in PATH (the rust toolchain's cargo # — either from `environment.systemPackages` or from whatever # `nix develop` shell the agent's working in). - hyperhive._bashEnvFragments = lib.mkIf config.hyperhive.cargo.shortMessages '' + services.hyperhive.agent._bashEnvFragments = lib.mkIf config.services.hyperhive.agent.cargo.shortMessages '' # Auto-injects --message-format short on cargo compile # subcommands so per-crate progress lines don't flood # claude's context. Bypassed when the caller already passes @@ -108,15 +108,17 @@ # Wiring is gated on at least one fragment being active so a # fully feature-disabled agent has neither the file nor the # `BASH_ENV` / interactive sourcing — zero cost in that case. - environment.etc."hyperhive/bash-env.sh" = lib.mkIf (config.hyperhive._bashEnvFragments != "") { - text = config.hyperhive._bashEnvFragments; - }; + environment.etc."hyperhive/bash-env.sh" = + lib.mkIf (config.services.hyperhive.agent._bashEnvFragments != "") + { + text = config.services.hyperhive.agent._bashEnvFragments; + }; # Non-interactive bash invocations (claude's `Bash` tool runs # `bash -c`) source $BASH_ENV at startup — drops every active # feature hook's snippet into scope without touching # `/etc/profile` (login-only). - environment.variables = lib.mkIf (config.hyperhive._bashEnvFragments != "") { + environment.variables = lib.mkIf (config.services.hyperhive.agent._bashEnvFragments != "") { BASH_ENV = "/etc/hyperhive/bash-env.sh"; }; @@ -125,10 +127,12 @@ # hook surface as claude's non-interactive calls. Gated on at # least one fragment being active so we don't write a no-op # source line into `/etc/bashrc` on fully-feature-disabled agents. - programs.bash.interactiveShellInit = lib.mkIf (config.hyperhive._bashEnvFragments != "") '' - if [ -r /etc/hyperhive/bash-env.sh ]; then - . /etc/hyperhive/bash-env.sh - fi - ''; + programs.bash.interactiveShellInit = + lib.mkIf (config.services.hyperhive.agent._bashEnvFragments != "") + '' + if [ -r /etc/hyperhive/bash-env.sh ]; then + . /etc/hyperhive/bash-env.sh + fi + ''; }; } diff --git a/nix/agent-modules/claude-settings.nix b/nix/agent-modules/claude-settings.nix index 371595db..67257ce5 100644 --- a/nix/agent-modules/claude-settings.nix +++ b/nix/agent-modules/claude-settings.nix @@ -13,12 +13,12 @@ ... }: let - userName = config.hyperhive.user.name; + userName = config.services.hyperhive.agent.user.name; homeDir = "/home/${userName}"; # Hive-wide OpenTelemetry config (host-driven; baked in per-agent by # meta.rs `otel_config`). Options declared in `otel.nix`, which also # exports the generic OTLP environment this container's producers read. - otelCfg = config.hyperhive.otel; + otelCfg = config.services.hyperhive.agent.otel; # Hive display name, read from the per-agent option meta.rs renders # (NOT from `environment.variables` — that carries the same name at # *runtime* only, so reading it here silently yielded "unknown" on @@ -26,12 +26,15 @@ let # means the hive did not name itself; "unknown" is then an honest label # rather than a guess. hiveDisplayName = - if config.hyperhive.hiveName == null then "unknown" else config.hyperhive.hiveName; + if config.services.hyperhive.agent.hiveName == null then + "unknown" + else + config.services.hyperhive.agent.hiveName; # Effective per-agent MemoryMax=, in bytes, injected by meta.rs's - # per-agent flake render (`hyperhive.claudeMemoryMaxBytes`). `null` + # per-agent flake render (`services.hyperhive.agent.claudeMemoryMaxBytes`). `null` # when the effective cap is unbounded ("infinity") or a RAM # percentage — see `resource_limits::effective_memory_bytes`. - memoryMaxBytes = config.hyperhive.claudeMemoryMaxBytes; + memoryMaxBytes = config.services.hyperhive.agent.claudeMemoryMaxBytes; # Base claude-code environment applied to every agent regardless of OTEL. # Shipped via the managed settings `env` block so claude and `hivectl # choom` both inherit them without a launch wrapper. @@ -99,13 +102,13 @@ in # `MemoryMax=` (per-agent `resource-limits.json` override, else the # hive-wide `services.hyperhive.agentMemoryMax`) — see # `resource_limits::effective_memory_bytes_from`. Not meant to be set - # directly in an agent.nix, same convention as `hyperhive.otel.*` + # directly in an agent.nix, same convention as `services.hyperhive.agent.otel.*` # above; the host option (or `hivectl agent set-limits`) is # the real operator knob, and this only reflects the value baked in at # the agent's *last rebuild* — `set-limits` still applies the # cgroup cap live via a drop-in reload, but this derived heap ceiling # needs a rebuild to pick up a new value. - options.hyperhive.hiveName = lib.mkOption { + options.services.hyperhive.agent.hiveName = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; internal = true; @@ -122,7 +125,7 @@ in ''; }; - options.hyperhive.swarmName = lib.mkOption { + options.services.hyperhive.agent.swarmName = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; internal = true; @@ -130,13 +133,13 @@ in Human-readable swarm name, rendered per-agent by `meta.rs::render_flake` from the host's `services.hyperhive.swarm.name`. Same build-time/runtime split as - `hyperhive.hiveName`. + `services.hyperhive.agent.hiveName`. `null` means the hive is not part of a named swarm. ''; }; - options.hyperhive.claudeMemoryMaxBytes = lib.mkOption { + options.services.hyperhive.agent.claudeMemoryMaxBytes = lib.mkOption { type = lib.types.nullOr lib.types.ints.positive; default = null; internal = true; @@ -150,11 +153,11 @@ in ''; }; - options.hyperhive.claudeMarketplaces = lib.mkOption { + options.services.hyperhive.agent.claudeMarketplaces = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ "anthropics/claude-plugins-official" - "${config.hyperhive.packages.claude-plugins}" + "${config.services.hyperhive.agent.packages.claude-plugins}" ]; defaultText = lib.literalMD '' `[ "anthropics/claude-plugins-official" "''${hyperhive.packages.claude-plugins}" ]` @@ -169,7 +172,7 @@ in entry is passed to `claude plugin marketplace add ` (`owner/repo`, full git URL, or local path). Idempotent — re-adding an existing marketplace is treated as success. - Required before `hyperhive.claudePlugins` entries that + Required before `services.hyperhive.agent.claudePlugins` entries that reference a marketplace (e.g. `foo@claude-plugins-official`). Rendered to `/etc/hyperhive/claude-marketplaces.json`. @@ -185,7 +188,7 @@ in ''; }; - options.hyperhive.claudePlugins = lib.mkOption { + options.services.hyperhive.agent.claudePlugins = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ "skill-creator@claude-plugins-official" @@ -224,7 +227,7 @@ in ''; }; - options.hyperhive.claudePluginsAutoUpdate = lib.mkOption { + options.services.hyperhive.agent.claudePluginsAutoUpdate = lib.mkOption { type = lib.types.bool; default = false; description = '' @@ -239,13 +242,13 @@ in config = { environment.etc."hyperhive/claude-plugins.json".text = - builtins.toJSON config.hyperhive.claudePlugins; + builtins.toJSON config.services.hyperhive.agent.claudePlugins; environment.etc."hyperhive/claude-marketplaces.json".text = - builtins.toJSON config.hyperhive.claudeMarketplaces; + builtins.toJSON config.services.hyperhive.agent.claudeMarketplaces; environment.etc."hyperhive/claude-plugins-auto-update.json".text = - builtins.toJSON config.hyperhive.claudePluginsAutoUpdate; + builtins.toJSON config.services.hyperhive.agent.claudePluginsAutoUpdate; # Hive-enforced claude settings. claude-code auto-discovers managed # settings at this canonical Linux path (precedence #1, read-only, @@ -263,7 +266,7 @@ in # `hivectl choom`) so no launch wrapper is needed. environment.etc."claude-code/managed-settings.json".source = let - baseSettings = "${config.hyperhive.packages.assets}/share/hyperhive/prompts/claude-settings.json"; + baseSettings = "${config.services.hyperhive.agent.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/agent-modules/dashboard-links.nix b/nix/agent-modules/dashboard-links.nix index 9ba7a5b6..fa98ef94 100644 --- a/nix/agent-modules/dashboard-links.nix +++ b/nix/agent-modules/dashboard-links.nix @@ -7,7 +7,7 @@ ... }: { - options.hyperhive.dashboardLinks = lib.mkOption { + options.services.hyperhive.agent.dashboardLinks = lib.mkOption { type = lib.types.listOf ( lib.types.submodule { options = { @@ -47,27 +47,29 @@ # read them without accessing the container's /etc/ from the host. # Best-effort oneshot (always exit 0): # docs/process/conventions.md::Best-effort oneshot services. - systemd.services.hive-dashboard-links = lib.mkIf (config.hyperhive.dashboardLinks != [ ]) { - description = "write declarative dashboardLinks to agent state dir"; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - # Pin the journal identity (else it's the `script` store-path wrapper). - SyslogIdentifier = "hive-dashboard-links"; - }; - environment.LINKS_JSON = builtins.toJSON config.hyperhive.dashboardLinks; - script = '' - # Sub-agents have their state dir bind-mounted at /agents//state. - # Use a glob — exactly one match per container at runtime. - STATE_DIR=$(echo /agents/*/state) - if [ ! -d "$STATE_DIR" ]; then - echo "hive-dashboard-links: no state dir found at /agents/*/state; skipping" - exit 0 - fi - printf '%s' "$LINKS_JSON" > "$STATE_DIR/hyperhive-dashboard-links.json" - echo "hive-dashboard-links: wrote $(printf '%s' "$LINKS_JSON" | wc -c) bytes to $STATE_DIR/hyperhive-dashboard-links.json" - ''; - }; + systemd.services.hive-dashboard-links = + lib.mkIf (config.services.hyperhive.agent.dashboardLinks != [ ]) + { + description = "write declarative dashboardLinks to agent state dir"; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + # Pin the journal identity (else it's the `script` store-path wrapper). + SyslogIdentifier = "hive-dashboard-links"; + }; + environment.LINKS_JSON = builtins.toJSON config.services.hyperhive.agent.dashboardLinks; + script = '' + # Sub-agents have their state dir bind-mounted at /agents//state. + # Use a glob — exactly one match per container at runtime. + STATE_DIR=$(echo /agents/*/state) + if [ ! -d "$STATE_DIR" ]; then + echo "hive-dashboard-links: no state dir found at /agents/*/state; skipping" + exit 0 + fi + printf '%s' "$LINKS_JSON" > "$STATE_DIR/hyperhive-dashboard-links.json" + echo "hive-dashboard-links: wrote $(printf '%s' "$LINKS_JSON" | wc -c) bytes to $STATE_DIR/hyperhive-dashboard-links.json" + ''; + }; }; } diff --git a/nix/agent-modules/default.nix b/nix/agent-modules/default.nix index 281ebb49..fea45d18 100644 --- a/nix/agent-modules/default.nix +++ b/nix/agent-modules/default.nix @@ -4,9 +4,9 @@ # they came from. # # This is the core module: container plumbing (boot/nix/nixpkgs), -# base tooling, and the cross-cutting `hyperhive.icon` option. Each +# base tooling, and the cross-cutting `services.hyperhive.agent.icon` option. Each # feature lives in its own sibling module (imported below) that -# declares its own `hyperhive.*` options + config. +# declares its own `services.hyperhive.agent.*` options + config. { pkgs, lib, @@ -35,6 +35,7 @@ ./otel.nix ./packages.nix ./queue.nix + ./renamed-options.nix ./user.nix ./screen.nix ./weston-vnc.nix @@ -48,7 +49,7 @@ '') ]; - options.hyperhive.icon = lib.mkOption { + options.services.hyperhive.agent.icon = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; example = lib.literalExpression "./icon.svg"; @@ -65,7 +66,7 @@ ''; }; - options.hyperhive.claudeCodePath = lib.mkOption { + options.services.hyperhive.agent.claudeCodePath = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "/nix/store/…-claude-code-2.1.220"; @@ -108,20 +109,22 @@ _module.args.flakeInputs = builtins.removeAttrs inputs [ "self" ]; ''; } - # hyperhive.icon must reference an SVG file when set. + # services.hyperhive.agent.icon must reference an SVG file when set. { - assertion = config.hyperhive.icon == null || lib.hasSuffix ".svg" (toString config.hyperhive.icon); - message = "hyperhive.icon must point to an .svg file"; + assertion = + config.services.hyperhive.agent.icon == null + || lib.hasSuffix ".svg" (toString config.services.hyperhive.agent.icon); + message = "services.hyperhive.agent.icon must point to an .svg file"; } ]; - # Operator-set per-agent icon (hyperhive.icon). When configured, the + # Operator-set per-agent icon (services.hyperhive.agent.icon). When configured, the # SVG lands at /etc/hyperhive/icon.svg; the harness serves it at # GET /icon, 404ing when absent (client-side fallback, no # server-side default). Consumed by forge-avatar-sync (./forge.nix) # and the matrix avatar sync (./matrix.nix) too. - environment.etc."hyperhive/icon.svg" = lib.mkIf (config.hyperhive.icon != null) { - source = config.hyperhive.icon; + environment.etc."hyperhive/icon.svg" = lib.mkIf (config.services.hyperhive.agent.icon != null) { + source = config.services.hyperhive.agent.icon; }; boot.isNspawnContainer = true; @@ -213,11 +216,11 @@ # `hive-metric` (agent-emitted custom metrics CLI, # docs/scheduler/observability.md). environment.systemPackages = [ - config.hyperhive.packages.hive-metric + config.services.hyperhive.agent.packages.hive-metric ] ++ [ ( - if config.hyperhive.claudeCodePath == null then + if config.services.hyperhive.agent.claudeCodePath == null then pkgs.claude-code else # Host-pinned claude: a symlink farm around a path the @@ -235,7 +238,7 @@ # see `services.hyperhive.c0re.claudeCodePackage`. pkgs.runCommandLocal "claude-code-pinned" { } '' mkdir -p "$out/bin" - ln -s ${config.hyperhive.claudeCodePath}/bin/claude "$out/bin/claude" + ln -s ${config.services.hyperhive.agent.claudeCodePath}/bin/claude "$out/bin/claude" '' ) ] @@ -262,7 +265,7 @@ # host-level `services.hyperhive.c0re.contextWindowTokens` option — not # set here. environment.variables = { - HIVE_ASSETS_DIR = "${config.hyperhive.packages.assets}/share/hyperhive"; + HIVE_ASSETS_DIR = "${config.services.hyperhive.agent.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/agent-modules/docs.nix b/nix/agent-modules/docs.nix index e717f0bb..0020b582 100644 --- a/nix/agent-modules/docs.nix +++ b/nix/agent-modules/docs.nix @@ -1,4 +1,4 @@ -# In-container hyperhive reference docs: the `hyperhive.docs.*` +# In-container hyperhive reference docs: the `services.hyperhive.agent.docs.*` # options and the `$HIVE_DOCS_DIR` wiring the harness reads. { pkgs, @@ -7,7 +7,7 @@ ... }: { - options.hyperhive.docs.enable = lib.mkEnableOption '' + options.services.hyperhive.agent.docs.enable = lib.mkEnableOption '' make the hyperhive reference docs (the repo `docs/` tree, shipped read-only as the standalone `hyperhive-docs` derivation) available in-container. When enabled the harness exposes the docs dir to claude @@ -19,13 +19,13 @@ `agent.nix`. ''; - options.hyperhive.docs.source = lib.mkOption { + options.services.hyperhive.agent.docs.source = lib.mkOption { type = lib.types.path; - default = config.hyperhive.packages.reference-docs; + default = config.services.hyperhive.agent.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 the flake's + when `services.hyperhive.agent.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 @@ -36,15 +36,15 @@ }; config = { - environment.variables = lib.mkIf config.hyperhive.docs.enable { + environment.variables = lib.mkIf config.services.hyperhive.agent.docs.enable { # The harness reads HIVE_DOCS_DIR and passes it to claude as # `--add-dir` so the docs are readable, and appends a single # pointer sentence to the system prompt # (hive-agent::prompt::render) telling the agent the docs exist. - # Source is `hyperhive.docs.source` (the narrow `hyperhive-docs` + # Source is `services.hyperhive.agent.docs.source` (the narrow `hyperhive-docs` # meta-flake input, or `pkgs.hyperhive-docs` for standalone # builds). See hive-agent::turn. - HIVE_DOCS_DIR = "${config.hyperhive.docs.source}"; + HIVE_DOCS_DIR = "${config.services.hyperhive.agent.docs.source}"; }; }; } diff --git a/nix/agent-modules/forge.nix b/nix/agent-modules/forge.nix index 98e18dd0..8e0a9da2 100644 --- a/nix/agent-modules/forge.nix +++ b/nix/agent-modules/forge.nix @@ -8,14 +8,14 @@ ... }: let - userName = config.hyperhive.user.name; + userName = config.services.hyperhive.agent.user.name; homeDir = "/home/${userName}"; # Same 512×512 rasterization of the agent icon the matrix avatar # sync uses (./matrix.nix — identical derivation, same store path). # Only forced when an icon is configured (the avatar-sync unit below - # is gated on `hyperhive.icon != null`). + # is gated on `services.hyperhive.agent.icon != null`). iconPng = pkgs.runCommand "hive-agent-icon.png" { nativeBuildInputs = [ pkgs.librsvg ]; } '' - rsvg-convert -f png -w 512 -h 512 ${config.hyperhive.icon} -o $out + rsvg-convert -f png -w 512 -h 512 ${config.services.hyperhive.agent.icon} -o $out ''; # git credential helper for the hive forge --- the exact shape @@ -39,7 +39,7 @@ let ''; in { - options.hyperhive.forge.url = lib.mkOption { + options.services.hyperhive.agent.forge.url = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "http://forge.internal:3000"; @@ -80,10 +80,10 @@ in # is for, so reject it and name the option. { assertion = - config.hyperhive.forge.url == null - || lib.hasPrefix "http://" config.hyperhive.forge.url - || lib.hasPrefix "https://" config.hyperhive.forge.url; - message = "hyperhive.forge.url must be an http:// or https:// URL, or null for no forge (got: \"${toString config.hyperhive.forge.url}\")"; + config.services.hyperhive.agent.forge.url == null + || lib.hasPrefix "http://" config.services.hyperhive.agent.forge.url + || lib.hasPrefix "https://" config.services.hyperhive.agent.forge.url; + message = "services.hyperhive.agent.forge.url must be an http:// or https:// URL, or null for no forge (got: \"${toString config.services.hyperhive.agent.forge.url}\")"; } ]; @@ -95,9 +95,9 @@ in # hive-forge : CLI wrapping common Forgejo REST API operations # (view, pr, issue, comment, assign, close, labels, branches, etc.). # The per-bin split package — narrow closure, no hivectl/wireguard. - config.hyperhive.packages.hive-forge + config.services.hyperhive.agent.packages.hive-forge ] - ++ lib.optional (config.hyperhive.forge.url != null) gitCredHelper; + ++ lib.optional (config.services.hyperhive.agent.forge.url != null) gitCredHelper; # Wire the forge credential helper for `git push`, scoped to the forge # this agent is configured for. @@ -135,9 +135,9 @@ in # works for any agent whose personal `~/.gitconfig` names the helper by # ABSOLUTE path — so this is masked exactly where it would be noticed, # and bites a fresh agent that has no such file. - environment.etc."gitconfig" = lib.mkIf (config.hyperhive.forge.url != null) { + environment.etc."gitconfig" = lib.mkIf (config.services.hyperhive.agent.forge.url != null) { text = '' - [credential "${lib.removeSuffix "/" config.hyperhive.forge.url}"] + [credential "${lib.removeSuffix "/" config.services.hyperhive.agent.forge.url}"] helper = hive-forge username = ${userName} ''; @@ -165,7 +165,7 @@ in # and the agent's `forge-token` from under the second. }; serviceConfig = { - ExecStart = "${config.hyperhive.packages.hive-forge-notify}/bin/hive-forge-notify"; + ExecStart = "${config.services.hyperhive.agent.packages.hive-forge-notify}/bin/hive-forge-notify"; SyslogIdentifier = "hive-forge-notify"; # `on-failure`, NOT `always`: an agent with no forge account is a # supported configuration, and the poller reports that by logging @@ -183,7 +183,7 @@ in # docs/process/conventions.md::Best-effort oneshot services. # Not generated at all when no forge is configured: an absent # integration rather than one pointed at a guessed address. - systemd.services.tea-login = lib.mkIf (config.hyperhive.forge.url != null) { + systemd.services.tea-login = lib.mkIf (config.services.hyperhive.agent.forge.url != null) { description = "configure tea CLI from hive-forge token (best-effort)"; wantedBy = [ "multi-user.target" ]; after = [ "local-fs.target" ]; @@ -202,7 +202,7 @@ in environment.AGENT_USER = userName; script = '' # No `set -e`: best-effort posture (see docs pointer above). - FORGE_URL=${lib.escapeShellArg config.hyperhive.forge.url} + FORGE_URL=${lib.escapeShellArg config.services.hyperhive.agent.forge.url} # $HYPERHIVE_STATE_DIR is system-wide via the meta flake. TOKEN_FILE="$HYPERHIVE_STATE_DIR/forge-token" if [ ! -f "$TOKEN_FILE" ]; then @@ -266,13 +266,13 @@ in # watches paths this unit has no business reacting to. # The service reads `$HYPERHIVE_STATE_DIR/forge-token`; this is the same # file, spelled the way `tea-login` above already spells it. - systemd.paths.forge-avatar-sync = lib.mkIf (config.hyperhive.icon != null) { + systemd.paths.forge-avatar-sync = lib.mkIf (config.services.hyperhive.agent.icon != null) { description = "trigger forge-avatar-sync when forge-token appears"; wantedBy = [ "multi-user.target" ]; pathConfig.PathChanged = "/agents/${userName}/state/forge-token"; }; - # One-shot: hyperhive.icon → Forgejo profile avatar. Shape contract: + # One-shot: services.hyperhive.agent.icon → Forgejo profile avatar. Shape contract: # docs/process/conventions.md::Best-effort oneshot services. # RemainAfterExit = false so the .path trigger above can re-fire # this unit when the forge-token arrives after boot. The PNG is @@ -281,7 +281,8 @@ in # and needs no librsvg at runtime — Forgejo's Go image library # can't decode SVG, hence PNG. systemd.services.forge-avatar-sync = - lib.mkIf (config.hyperhive.icon != null && config.hyperhive.forge.url != null) + lib.mkIf + (config.services.hyperhive.agent.icon != null && config.services.hyperhive.agent.forge.url != null) { description = "sync agent icon to Forgejo user avatar (best-effort)"; wantedBy = [ "multi-user.target" ]; @@ -298,7 +299,7 @@ in pkgs.jq ]; script = '' - FORGE_URL=${lib.escapeShellArg config.hyperhive.forge.url} + FORGE_URL=${lib.escapeShellArg config.services.hyperhive.agent.forge.url} # $HYPERHIVE_STATE_DIR is set system-wide by the meta flake # (systemd.globalEnvironment) to `/agents//state`. TOKEN_FILE="$HYPERHIVE_STATE_DIR/forge-token" diff --git a/nix/agent-modules/frontend.nix b/nix/agent-modules/frontend.nix index 90a631cf..38bdc937 100644 --- a/nix/agent-modules/frontend.nix +++ b/nix/agent-modules/frontend.nix @@ -8,9 +8,9 @@ ... }: { - options.hyperhive.frontend.dist = lib.mkOption { + options.services.hyperhive.agent.frontend.dist = lib.mkOption { type = lib.types.package; - default = config.hyperhive.packages.frontend; + default = config.services.hyperhive.agent.packages.frontend; defaultText = lib.literalMD "`hyperhive.packages.frontend` (the flake's frontend dist)"; description = '' The shipped frontend dist (built by `nix/packages/frontend.nix`). @@ -22,18 +22,18 @@ ''; }; - options.hyperhive.frontend.mergedDist = lib.mkOption { + options.services.hyperhive.agent.frontend.mergedDist = lib.mkOption { type = lib.types.package; readOnly = true; description = '' Computed: the merged static tree consumed by the harness via `HIVE_STATIC_DIR`. Composed at evaluation time by copying - `hyperhive.frontend.dist`'s `agent/` subdir as the base, then + `services.hyperhive.agent.frontend.dist`'s `agent/` subdir as the base, then layering each `extraFiles` entry on top. Read-only — do not set directly. ''; }; - options.hyperhive.frontend.extraFiles = lib.mkOption { + options.services.hyperhive.agent.frontend.extraFiles = lib.mkOption { type = lib.types.attrsOf ( lib.types.submodule ( { name, ... }: @@ -100,7 +100,7 @@ prior entry's target), the `mergedDist` build aborts with `refusing to overwrite existing path '' in the default dist`. To override a default file, fork the dist via - `hyperhive.frontend.dist` instead — `extraFiles` is for + `services.hyperhive.agent.frontend.dist` instead — `extraFiles` is for pure additions. `target` must be a relative path inside the static dir. An @@ -112,7 +112,7 @@ config = { assertions = [ - # hyperhive.frontend.extraFiles[*].target is concatenated into + # services.hyperhive.agent.frontend.extraFiles[*].target is concatenated into # $out during the mergedDist build. The option's strMatching # type already rejects leading `/`, leading `.`, and the # weirder characters; this assertion catches mid-path `..` @@ -122,10 +122,10 @@ # kind of mistake that's easy to make and hard to spot. { assertion = lib.all (entry: !(builtins.any (seg: seg == "..") (lib.splitString "/" entry.target))) ( - lib.attrValues config.hyperhive.frontend.extraFiles + lib.attrValues config.services.hyperhive.agent.frontend.extraFiles ); message = '' - hyperhive.frontend.extraFiles: `target` must not contain + services.hyperhive.agent.frontend.extraFiles: `target` must not contain `..` path segments. ''; } @@ -137,20 +137,22 @@ # filename collision with the default dist surfaces as a build # failure rather than a silent override (operator gets a clear # nix error rather than a confusing 404 / silent dist swap). - hyperhive.frontend.mergedDist = pkgs.runCommand "hyperhive-agent-frontend-merged" { } ( - '' - mkdir -p $out - cp -r ${config.hyperhive.frontend.dist}/agent/. $out/ - chmod -R u+w $out - '' - + lib.concatMapStrings (entry: '' - mkdir -p $(dirname $out/${entry.target}) - if [ -e $out/${entry.target} ]; then - echo "hyperhive.frontend.extraFiles: refusing to overwrite existing path '${entry.target}' in the default dist" >&2 - exit 1 - fi - cp -r ${entry.source} $out/${entry.target} - '') (lib.attrValues config.hyperhive.frontend.extraFiles) - ); + services.hyperhive.agent.frontend.mergedDist = + pkgs.runCommand "hyperhive-agent-frontend-merged" { } + ( + '' + mkdir -p $out + cp -r ${config.services.hyperhive.agent.frontend.dist}/agent/. $out/ + chmod -R u+w $out + '' + + lib.concatMapStrings (entry: '' + mkdir -p $(dirname $out/${entry.target}) + if [ -e $out/${entry.target} ]; then + echo "services.hyperhive.agent.frontend.extraFiles: refusing to overwrite existing path '${entry.target}' in the default dist" >&2 + exit 1 + fi + cp -r ${entry.source} $out/${entry.target} + '') (lib.attrValues config.services.hyperhive.agent.frontend.extraFiles) + ); }; } diff --git a/nix/agent-modules/github.nix b/nix/agent-modules/github.nix index 1fe7a9bd..e68cfba1 100644 --- a/nix/agent-modules/github.nix +++ b/nix/agent-modules/github.nix @@ -1,4 +1,4 @@ -# GitHub integration (hyperhive.github.enable): a `gh` wrapper + a git +# GitHub integration (services.hyperhive.agent.github.enable): a `gh` wrapper + a git # credential helper, both reading the PAT from the agent's # `github-token` state file at invocation, so a dashboard-pasted token # takes effect with no rebuild. The token PATH is baked in at build @@ -15,7 +15,7 @@ ... }: let - userName = config.hyperhive.user.name; + userName = config.services.hyperhive.agent.user.name; ghWrapper = pkgs.writeShellScriptBin "gh" '' if [ -r "/agents/${userName}/state/github-token" ]; then GH_TOKEN="$(cat "/agents/${userName}/state/github-token")" @@ -35,7 +35,7 @@ let ''; in { - options.hyperhive.github.enable = lib.mkOption { + options.services.hyperhive.agent.github.enable = lib.mkOption { type = lib.types.bool; default = true; description = '' @@ -63,7 +63,7 @@ in config = { # No bare pkgs.gh here — the wrapper *is* `gh` and hardcodes the # real binary path, so it can't be shadowed. - environment.systemPackages = lib.optionals config.hyperhive.github.enable [ + environment.systemPackages = lib.optionals config.services.hyperhive.agent.github.enable [ ghWrapper gitCredHelper ]; @@ -78,7 +78,7 @@ in # entries in the harness modules) — a whole-set `environment.etc = {…}` # here would collide with them at the nix level ("attribute already # defined"). - environment.etc."gitconfig" = lib.mkIf config.hyperhive.github.enable { + environment.etc."gitconfig" = lib.mkIf config.services.hyperhive.agent.github.enable { text = '' [credential "https://github.com"] helper = hive-github @@ -100,7 +100,7 @@ in # extractor copies exactly one binary, so an agent that installs # only the Forgejo poller has no github.com poller anywhere in its # closure — not merely an unstarted unit. - systemd.services.hive-github-notify = lib.mkIf config.hyperhive.github.enable { + systemd.services.hive-github-notify = lib.mkIf config.services.hyperhive.agent.github.enable { description = "github.com notification poller for this agent"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; @@ -113,7 +113,7 @@ in # poller reads the agent's `github-token` from under it. }; serviceConfig = { - ExecStart = "${config.hyperhive.packages.hive-github-notify}/bin/hive-github-notify"; + ExecStart = "${config.services.hyperhive.agent.packages.hive-github-notify}/bin/hive-github-notify"; SyslogIdentifier = "hive-github-notify"; # `on-failure`, NOT `always`, for the same reason as the forge # poller: this unit ships on every agent, but most agents have no diff --git a/nix/agent-modules/logs.nix b/nix/agent-modules/logs.nix index 22d9e1d3..31849f21 100644 --- a/nix/agent-modules/logs.nix +++ b/nix/agent-modules/logs.nix @@ -21,8 +21,8 @@ ... }: let - cfg = config.hyperhive.logs; - queueCfg = config.hyperhive.queue; + cfg = config.services.hyperhive.agent.logs; + queueCfg = config.services.hyperhive.agent.queue; configured = cfg.queryUrl != null && queueCfg.tokenEndpoint != null; # The four coordinates `swarm-logs` reads, all-or-none on its side. Bound @@ -46,7 +46,7 @@ let # The whole point of the CLI is that its stdout pipes into `grep`. swarmLogsConfigured = pkgs.symlinkJoin { name = "swarm-logs-configured"; - paths = [ config.hyperhive.packages.swarm-logs ]; + paths = [ config.services.hyperhive.agent.packages.swarm-logs ]; nativeBuildInputs = [ pkgs.makeWrapper ]; postBuild = '' wrapProgram $out/bin/swarm-logs ${ @@ -58,7 +58,7 @@ let }; in { - options.hyperhive.logs = { + options.services.hyperhive.agent.logs = { queryUrl = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; diff --git a/nix/agent-modules/matrix.nix b/nix/agent-modules/matrix.nix index 37dcaeb8..760ff7c8 100644 --- a/nix/agent-modules/matrix.nix +++ b/nix/agent-modules/matrix.nix @@ -1,5 +1,5 @@ -# Per-agent matrix integration: the `hyperhive.matrix.*` + -# `hyperhive.matrixAccounts` options, the long-running +# Per-agent matrix integration: the `services.hyperhive.agent.matrix.*` + +# `services.hyperhive.agent.matrixAccounts` options, the long-running # hive-matrix-daemon (serves its MCP tools directly over # streamable-http), its token-arrival path trigger, and the # auto-injected extraMcpServers entry. @@ -10,19 +10,19 @@ ... }: let - userName = config.hyperhive.user.name; - # Rasterize the operator-set agent icon (`hyperhive.icon`, an SVG) to a + userName = config.services.hyperhive.agent.user.name; + # Rasterize the operator-set agent icon (`services.hyperhive.agent.icon`, an SVG) to a # 512x512 PNG so the matrix daemon can upload it as each account's avatar # over the live authenticated Client (see hive-matrix-mcp::client::sync_avatar). # Only forced when an icon is configured — the `HIVE_ICON_PNG` daemon-env - # entry is gated on `hyperhive.icon != null`, so this binding stays lazy + # entry is gated on `services.hyperhive.agent.icon != null`, so this binding stays lazy # when no icon is set. iconPng = pkgs.runCommand "hive-agent-icon.png" { nativeBuildInputs = [ pkgs.librsvg ]; } '' - rsvg-convert -f png -w 512 -h 512 ${config.hyperhive.icon} -o $out + rsvg-convert -f png -w 512 -h 512 ${config.services.hyperhive.agent.icon} -o $out ''; in { - options.hyperhive.matrix.enable = lib.mkOption { + options.services.hyperhive.agent.matrix.enable = lib.mkOption { type = lib.types.bool; default = true; description = '' @@ -31,7 +31,7 @@ in - runs `hive-matrix-daemon` as a systemd unit that holds a matrix-sdk Client + sync against the homeserver named by - `HIVE_MATRIX_URL` (see `hyperhive.matrix.url` — there is no + `HIVE_MATRIX_URL` (see `services.hyperhive.agent.matrix.url` — there is no default, since an agent's own netns makes a loopback guess wrong). The daemon auto-skips when that URL or `/matrix-token` is missing, and a `systemd.paths` @@ -41,7 +41,7 @@ in send_reaction, send_reply, mark_read, list_rooms, list_room_members, read_room) to claude via an auto-injected `extraMcpServers.matrix` entry pointed at the daemon's own - streamable-http listener (`hyperhive.mcp.matrixHttpPort`) — no + streamable-http listener (`services.hyperhive.agent.mcp.matrixHttpPort`) — no stdio bridge, no per-turn respawn, same shape as the built-in hyperhive surface and `hive-bash-daemon`. - wakes the agent on incoming room events via a short teaser @@ -50,13 +50,13 @@ in unread server-side until `read_room` consumes it. Set to `false` for agents that should NOT have matrix tools at - all (e.g. agents on a host without `hyperhive.matrix.enable` on + all (e.g. agents on a host without `services.hyperhive.agent.matrix.enable` on the meta side). When token file is absent the daemon and MCP both no-op cleanly anyway, so `false` is rarely necessary. ''; }; - options.hyperhive.matrix.url = lib.mkOption { + options.services.hyperhive.agent.matrix.url = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "https://matrix.darkest.space"; @@ -82,7 +82,7 @@ in ''; }; - options.hyperhive.matrixAccounts = lib.mkOption { + options.services.hyperhive.agent.matrixAccounts = lib.mkOption { type = lib.types.attrsOf ( lib.types.submodule { options = { @@ -112,7 +112,7 @@ in example = "https://matrix.example.org"; description = '' Homeserver URL for this account. When null (the default), - the account falls back to `hyperhive.matrix.url`. Set it for + the account falls back to `services.hyperhive.agent.matrix.url`. Set it for an account on a different homeserver than the agent's default (e.g. an external public-matrix account). ''; @@ -140,7 +140,7 @@ in The **hive-internal account is always present and is the primary**: it is named `main`, synthesized by the daemon from - `hyperhive.matrix.url` + `/matrix-token` + + `services.hyperhive.agent.matrix.url` + `/matrix-token` + `/matrix-sdk-state`, and is the account a tool call acts as when it omits `account`. You never declare it here --- this option is only for the extras (e.g. an external public-matrix account). @@ -149,23 +149,23 @@ in agent then has only `main`. When non-empty, the extras are serialized to the daemon's `HIVE_MATRIX_ACCOUNTS` environment variable and the daemon appends them after `main`. Requires - `hyperhive.matrix.enable` (there is no `main` to extend otherwise). + `services.hyperhive.agent.matrix.enable` (there is no `main` to extend otherwise). ''; }; - options.hyperhive.mcp.matrixHttpPort = lib.mkOption { + options.services.hyperhive.agent.mcp.matrixHttpPort = lib.mkOption { type = lib.types.port; default = 8792; example = 8793; description = '' Loopback port `hive-matrix-daemon` serves its MCP tools (`send_message`, `list_rooms`, `read_room`, …) on. Same shape as - `hyperhive.mcp.bashHttpPort`: HTTP is the *sole* transport (no + `services.hyperhive.agent.mcp.bashHttpPort`: HTTP is the *sole* transport (no stdio bridge — the daemon that owns the matrix-sdk `Client` registry serves the MCP tools directly in-process), `Restart = "always"` keeps the listener self-healing, and loopback-only binding means no auth token is needed (same - `allowed_hosts` reasoning as `hyperhive.mcp.httpPort`). Safe as a + `allowed_hosts` reasoning as `services.hyperhive.agent.mcp.httpPort`). Safe as a single fixed default across all agents (private per-container network namespace — see docs/networking/network.md). ''; @@ -177,18 +177,20 @@ in # `main` account they extend, which exists only when matrix is # enabled. { - assertion = config.hyperhive.matrixAccounts == { } || config.hyperhive.matrix.enable; + assertion = + config.services.hyperhive.agent.matrixAccounts == { } + || config.services.hyperhive.agent.matrix.enable; message = - "hyperhive.matrixAccounts requires hyperhive.matrix.enable = true " + "services.hyperhive.agent.matrixAccounts requires services.hyperhive.agent.matrix.enable = true " + "(the extras extend the hive-internal `main` account, which only " + "exists when matrix is enabled)."; } # `main` is reserved for the synthesized hive-internal account; a # declared extra by that name would silently collide with it. { - assertion = !builtins.hasAttr "main" config.hyperhive.matrixAccounts; + assertion = !builtins.hasAttr "main" config.services.hyperhive.agent.matrixAccounts; message = - "hyperhive.matrixAccounts cannot contain a key named \"main\" " + "services.hyperhive.agent.matrixAccounts cannot contain a key named \"main\" " + "--- that name is reserved for the hive-internal account."; } # Token files must land at the `matrix-token*` name the daemon @@ -199,17 +201,17 @@ in # `matrix-catgirl-token`) is caught at build time, not silently. { assertion = lib.all (a: lib.hasPrefix "matrix-token" (baseNameOf a.tokenFile)) ( - lib.attrValues config.hyperhive.matrixAccounts + lib.attrValues config.services.hyperhive.agent.matrixAccounts ); message = - "every hyperhive.matrixAccounts..tokenFile basename must start with " + "every services.hyperhive.agent.matrixAccounts..tokenFile basename must start with " + "\"matrix-token\" so the daemon path-watcher glob " + "(matrix-token* in the agent's state dir) picks it up live. Offending: " + lib.concatStringsSep ", " ( lib.mapAttrsToList (n: a: "${n}=${baseNameOf a.tokenFile}") ( lib.filterAttrs ( _n: a: !lib.hasPrefix "matrix-token" (baseNameOf a.tokenFile) - ) config.hyperhive.matrixAccounts + ) config.services.hyperhive.agent.matrixAccounts ) ) + "."; @@ -220,10 +222,10 @@ in # ./mcp.nix. `lib.mkDefault` so the operator's own agent.nix can # override it. Points at the daemon's own persistent # streamable-http listener — no stdio bridge, no per-turn spawn. - hyperhive.extraMcpServers = lib.mkIf config.hyperhive.matrix.enable { + services.hyperhive.agent.extraMcpServers = lib.mkIf config.services.hyperhive.agent.matrix.enable { matrix = lib.mkDefault { type = "http"; - url = "http://127.0.0.1:${toString config.hyperhive.mcp.matrixHttpPort}/mcp"; + url = "http://127.0.0.1:${toString config.services.hyperhive.agent.mcp.matrixHttpPort}/mcp"; allowedTools = [ "*" ]; }; }; @@ -233,7 +235,7 @@ in # signals on incoming room events via `/run/hive/mcp.sock`. See # `docs/agent-lifecycle/persistence.md::Matrix per-agent daemon + token-arrival # trigger` for the first-boot-ordering rationale. - systemd.services.hive-matrix-daemon = lib.mkIf config.hyperhive.matrix.enable { + systemd.services.hive-matrix-daemon = lib.mkIf config.services.hyperhive.agent.matrix.enable { description = "long-running matrix-sdk Client + MCP daemon"; wantedBy = [ "multi-user.target" ]; before = [ "hive-agent.service" ]; @@ -255,8 +257,8 @@ in # token and no-ops. Nothing here falls back to loopback: that would # be a value that evaluates fine and then addresses the agent's own # netns instead of the homeserver. - // lib.optionalAttrs (config.hyperhive.matrix.url != null) { - HIVE_MATRIX_URL = config.hyperhive.matrix.url; + // lib.optionalAttrs (config.services.hyperhive.agent.matrix.url != null) { + HIVE_MATRIX_URL = config.services.hyperhive.agent.matrix.url; } # Multi-account: serialize the *extra* accounts to the JSON the # daemon parses (`accounts::configured`). Only set when extras are @@ -265,7 +267,7 @@ in # prepends it, so we emit extras only. Each entry is in the # daemon's `AccountCfg` serde shape: name (the attr key) / # token_file / state_dir / optional homeserver. - // lib.optionalAttrs (config.hyperhive.matrixAccounts != { }) { + // lib.optionalAttrs (config.services.hyperhive.agent.matrixAccounts != { }) { HIVE_MATRIX_ACCOUNTS = builtins.toJSON ( lib.mapAttrsToList ( name: a: @@ -275,17 +277,17 @@ in state_dir = a.sessionDir; } // lib.optionalAttrs (a.homeserver != null) { inherit (a) homeserver; } - ) config.hyperhive.matrixAccounts + ) config.services.hyperhive.agent.matrixAccounts ); } # Rasterized agent icon path for the daemon's avatar sync. Only set # when an icon is configured; absent → the daemon skips avatar setting # (hive-matrix-mcp::client::sync_avatar returns early on unset env). - // lib.optionalAttrs (config.hyperhive.icon != null) { + // lib.optionalAttrs (config.services.hyperhive.agent.icon != null) { HIVE_ICON_PNG = "${iconPng}"; }; serviceConfig = { - ExecStart = "${config.hyperhive.packages.hive-matrix-daemon}/bin/hive-matrix-daemon --http 127.0.0.1:${toString config.hyperhive.mcp.matrixHttpPort}"; + ExecStart = "${config.services.hyperhive.agent.packages.hive-matrix-daemon}/bin/hive-matrix-daemon --http 127.0.0.1:${toString config.services.hyperhive.agent.mcp.matrixHttpPort}"; SyslogIdentifier = "hive-matrix-daemon"; # `on-failure`, not `always`: the daemon deliberately exits 0 # (a clean, non-failure exit) when no token is provisioned yet @@ -308,7 +310,7 @@ in # the daemon would exit 0 silently on first boot and the MCP # would have no backend until next restart. See # `docs/agent-lifecycle/persistence.md` (same section as above). - systemd.paths.hive-matrix-daemon = lib.mkIf config.hyperhive.matrix.enable { + systemd.paths.hive-matrix-daemon = lib.mkIf config.services.hyperhive.agent.matrix.enable { description = "trigger hive-matrix-daemon when a matrix token appears"; wantedBy = [ "multi-user.target" ]; # `matrix-token*` (not just `matrix-token`) so a secondary diff --git a/nix/agent-modules/mcp.nix b/nix/agent-modules/mcp.nix index bbda68b2..0144ff8f 100644 --- a/nix/agent-modules/mcp.nix +++ b/nix/agent-modules/mcp.nix @@ -10,13 +10,13 @@ ... }: let - userName = config.hyperhive.user.name; + userName = config.services.hyperhive.agent.user.name; # This container's own effective `MemoryMax=` in bytes, baked in per # agent by meta.rs's flake render — see - # `hyperhive.claudeMemoryMaxBytes` in ./claude-settings.nix. `null` + # `services.hyperhive.agent.claudeMemoryMaxBytes` in ./claude-settings.nix. `null` # when the cap is `infinity` or a RAM percentage, i.e. when the module # has no byte count to size anything against. - containerMemoryMaxBytes = config.hyperhive.claudeMemoryMaxBytes; + containerMemoryMaxBytes = config.services.hyperhive.agent.claudeMemoryMaxBytes; # Two thirds of the container's cap, as the soft ceiling on everything # the subagent daemon runs. The daemon spawns nested `claude` sessions # as plain children, so its cgroup already *is* the "all subagents" @@ -32,7 +32,7 @@ let subagentMemoryHigh = containerMemoryMaxBytes * 2 / 3; in { - options.hyperhive.allowedRecipients = lib.mkOption { + options.services.hyperhive.agent.allowedRecipients = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; example = [ @@ -58,7 +58,7 @@ in ''; }; - options.hyperhive.extraMcpServers = lib.mkOption { + options.services.hyperhive.agent.extraMcpServers = lib.mkOption { type = lib.types.attrsOf ( lib.types.submodule { options = { @@ -173,14 +173,14 @@ in whatever brought the new agent.nix into deployed/*). The `bash` entry above is illustrative only — it's auto-injected - below (`hyperhive.extraMcpServers.bash` via `lib.mkDefault`) already - tracking `hyperhive.mcp.bashHttpPort`, so overriding it directly + below (`services.hyperhive.agent.extraMcpServers.bash` via `lib.mkDefault`) already + tracking `services.hyperhive.agent.mcp.bashHttpPort`, so overriding it directly with a hardcoded port (as shown) is unusual and will drift if `bashHttpPort` is changed separately; bump `bashHttpPort` instead. ''; }; - options.hyperhive.mcp.httpPort = lib.mkOption { + options.services.hyperhive.agent.mcp.httpPort = lib.mkOption { type = lib.types.port; default = 8790; example = 8791; @@ -197,7 +197,7 @@ in `initialize`/`tools-list` completed, stranding the agent with `No such tool` — the http endpoint eliminates that). `matrix` stays a stdio bridge; `bash` runs its own persistent http listener (see - `hyperhive.mcp.bashHttpPort`). + `services.hyperhive.agent.mcp.bashHttpPort`). Bound loopback-only; the rmcp streamable-http transport's default `allowed_hosts` (`localhost` / `127.0.0.1` / `::1`) rejects Host @@ -220,24 +220,24 @@ in ''; }; - options.hyperhive.mcp.bashHttpPort = lib.mkOption { + options.services.hyperhive.agent.mcp.bashHttpPort = lib.mkOption { type = lib.types.port; default = 8791; example = 8792; description = '' Loopback port `hive-bash-daemon` serves its MCP tools - (`run`/`status`/`kill`) on. Same shape as `hyperhive.mcp.httpPort` + (`run`/`status`/`kill`) on. Same shape as `services.hyperhive.agent.mcp.httpPort` for the built-in surface: HTTP is the *sole* transport (no stdio bridge — the daemon that owns the subprocess runner serves the MCP tools directly in-process), `Restart = "always"` keeps the listener self-healing, and loopback-only binding means no auth token is - needed (same `allowed_hosts` reasoning as `hyperhive.mcp.httpPort`). + needed (same `allowed_hosts` reasoning as `services.hyperhive.agent.mcp.httpPort`). Safe as a single fixed default across all agents (private per-container network namespace — see docs/networking/network.md). ''; }; - options.hyperhive.mcp.subagentHttpPort = lib.mkOption { + options.services.hyperhive.agent.mcp.subagentHttpPort = lib.mkOption { type = lib.types.port; default = 8793; example = 8794; @@ -247,7 +247,7 @@ in `hive-subagent-mcp`) — a subagent spawns a full nested `claude` process, a much heavier capability than a bash command, worth its own deployable/restartable unit. Same shape/reasoning as - `hyperhive.mcp.bashHttpPort` otherwise: sole transport, self-healing + `services.hyperhive.agent.mcp.bashHttpPort` otherwise: sole transport, self-healing restart, loopback-only so no auth token is needed. Shipped default-on for every agent today, same as `bash` — expected to become a real opt-in capability gate later, not yet. @@ -263,22 +263,22 @@ in assertions = lib.mapAttrsToList (name: spec: { assertion = spec.type != "stdio" || spec.command != null; - message = "hyperhive.extraMcpServers.${name}: type = \"stdio\" requires `command` to be set"; - }) config.hyperhive.extraMcpServers + message = "services.hyperhive.agent.extraMcpServers.${name}: type = \"stdio\" requires `command` to be set"; + }) config.services.hyperhive.agent.extraMcpServers ++ lib.mapAttrsToList (name: spec: { assertion = spec.type != "http" || spec.url != null; - message = "hyperhive.extraMcpServers.${name}: type = \"http\" requires `url` to be set"; - }) config.hyperhive.extraMcpServers; + message = "services.hyperhive.agent.extraMcpServers.${name}: type = \"http\" requires `url` to be set"; + }) config.services.hyperhive.agent.extraMcpServers; # Auto-inject the built-in bash MCP server — always present, every # agent needs bash tools. `lib.mkDefault` so the operator's own # agent.nix can override the entry. (The matrix sibling lives in - # ./matrix.nix, gated on hyperhive.matrix.enable.) `hive-bash-daemon` + # ./matrix.nix, gated on services.hyperhive.agent.matrix.enable.) `hive-bash-daemon` # serves its MCP tools directly over streamable-http (no stdio bridge, # no round-trip socket) — see the `hive-bash-daemon` service below. - hyperhive.extraMcpServers.bash = lib.mkDefault { + services.hyperhive.agent.extraMcpServers.bash = lib.mkDefault { type = "http"; - url = "http://127.0.0.1:${toString config.hyperhive.mcp.bashHttpPort}/mcp"; + url = "http://127.0.0.1:${toString config.services.hyperhive.agent.mcp.bashHttpPort}/mcp"; allowedTools = [ "*" ]; }; @@ -287,21 +287,22 @@ in # later" — not gated behind an enable option yet, unlike `matrix.nix`'s # pattern). `lib.mkDefault` so an agent.nix can still override/disable # the entry in the meantime. - hyperhive.extraMcpServers.subagent = lib.mkDefault { + services.hyperhive.agent.extraMcpServers.subagent = lib.mkDefault { type = "http"; - url = "http://127.0.0.1:${toString config.hyperhive.mcp.subagentHttpPort}/mcp"; + url = "http://127.0.0.1:${toString config.services.hyperhive.agent.mcp.subagentHttpPort}/mcp"; allowedTools = [ "*" ]; }; - environment.etc."hyperhive/extra-mcp.json".text = builtins.toJSON config.hyperhive.extraMcpServers; + environment.etc."hyperhive/extra-mcp.json".text = + builtins.toJSON config.services.hyperhive.agent.extraMcpServers; environment.etc."hyperhive/send-allow.json".text = - builtins.toJSON config.hyperhive.allowedRecipients; + builtins.toJSON config.services.hyperhive.agent.allowedRecipients; # Bash task runner daemon — long-running process that owns subprocess # monitoring + completion wake signals, and serves the MCP tools # (`run`/`status`/`kill`) directly over streamable-http on - # `hyperhive.mcp.bashHttpPort` — no stdio bridge, no per-turn spawn. + # `services.hyperhive.agent.mcp.bashHttpPort` — no stdio bridge, no per-turn spawn. systemd.services.hive-bash-daemon = { description = "bash task runner + MCP daemon for hive-bash"; wantedBy = [ "multi-user.target" ]; @@ -334,7 +335,7 @@ in # deriving a fallback, since every service here always gets it. }; serviceConfig = { - ExecStart = "${config.hyperhive.packages.hive-bash-daemon}/bin/hive-bash-daemon --http 127.0.0.1:${toString config.hyperhive.mcp.bashHttpPort}"; + ExecStart = "${config.services.hyperhive.agent.packages.hive-bash-daemon}/bin/hive-bash-daemon --http 127.0.0.1:${toString config.services.hyperhive.agent.mcp.bashHttpPort}"; SyslogIdentifier = "hive-bash-daemon"; # `always` (not `on-failure`): since the MCP tools are served # in-process now, a down window is total loss of bash tools with @@ -349,7 +350,7 @@ in # Subagent task runner daemon — independent of `hive-bash-daemon` (own # crate, own process): spawns nested claude sessions on request, serves # the `start`/`continue`/`status`/`interrupt` MCP tools directly over - # streamable-http on `hyperhive.mcp.subagentHttpPort`. The same port also + # streamable-http on `services.hyperhive.agent.mcp.subagentHttpPort`. The same port also # serves a second, subagent-facing route (`/signal/mcp/`: # `goal_reached`/`need_help`) — not something an agent's own config points # at: the daemon mints each subagent it spawns its own token and writes @@ -388,7 +389,7 @@ in # `null` when the agent has no groups declared, which systemd drops # — the same "absent" the harness itself would see. HIVE_TOOL_GROUPS = config.systemd.services.hive-agent.environment.HIVE_TOOL_GROUPS or null; - # Same `hyperhive.availableModels` the harness's own assertions gate + # Same `services.hyperhive.agent.availableModels` the harness's own assertions gate # the primary session's model against, so a subagent can't be spawned # on a model the operator didn't make available to this agent. The # option renders into the *global* environment for the web UI's @@ -398,16 +399,16 @@ in # a safety rail, not a security boundary), matching the harness # assertion that an empty list waives too. HIVE_AVAILABLE_MODELS = - if config.hyperhive.availableModels == [ ] then + if config.services.hyperhive.agent.availableModels == [ ] then null else - lib.concatStringsSep "," config.hyperhive.availableModels; + lib.concatStringsSep "," config.services.hyperhive.agent.availableModels; # HYPERHIVE_HARNESS_DIR / HYPERHIVE_STATE_DIR: see # `hive-bash-daemon`'s own comment above — same global injection, # same reasoning. }; serviceConfig = { - ExecStart = "${config.hyperhive.packages.hive-subagent-daemon}/bin/hive-subagent-daemon --http 127.0.0.1:${toString config.hyperhive.mcp.subagentHttpPort}"; + ExecStart = "${config.services.hyperhive.agent.packages.hive-subagent-daemon}/bin/hive-subagent-daemon --http 127.0.0.1:${toString config.services.hyperhive.agent.mcp.subagentHttpPort}"; SyslogIdentifier = "hive-subagent-daemon"; # `always`, same reasoning as `hive-bash-daemon`: the MCP tools are # served in-process, so a down window is total loss of @@ -463,7 +464,7 @@ in # with the static loose-ends from hive-c0re. environment.HIVE_AGENT_SOCKET = "/run/hive-agent/${userName}/agent.sock"; serviceConfig = { - ExecStart = "${config.hyperhive.packages.hive-agent-mcp}/bin/hive-agent-mcp --http 127.0.0.1:${toString config.hyperhive.mcp.httpPort}"; + ExecStart = "${config.services.hyperhive.agent.packages.hive-agent-mcp}/bin/hive-agent-mcp --http 127.0.0.1:${toString config.services.hyperhive.agent.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/agent-modules/otel.nix b/nix/agent-modules/otel.nix index 95a432cf..f06cd8e8 100644 --- a/nix/agent-modules/otel.nix +++ b/nix/agent-modules/otel.nix @@ -23,8 +23,8 @@ ... }: let - cfg = config.hyperhive.otel; - userName = config.hyperhive.user.name; + cfg = config.services.hyperhive.agent.otel; + userName = config.services.hyperhive.agent.user.name; # Hive/swarm display names, read from the per-agent options meta.rs # renders (NOT from `environment.variables` — those carry the same # names at *runtime* only, so reading them here silently yielded @@ -32,9 +32,15 @@ let # answer). `null` means the hive did not name itself; "unknown" is then # an honest label rather than a guess. hiveDisplayName = - if config.hyperhive.hiveName == null then "unknown" else config.hyperhive.hiveName; + if config.services.hyperhive.agent.hiveName == null then + "unknown" + else + config.services.hyperhive.agent.hiveName; swarmDisplayName = - if config.hyperhive.swarmName == null then "unknown" else config.hyperhive.swarmName; + if config.services.hyperhive.agent.swarmName == null then + "unknown" + else + config.services.hyperhive.agent.swarmName; # Resource labels every producer in this container stamps on what it # emits. `service.name` names the container's role, not one binary @@ -81,7 +87,7 @@ in # the build-time implementation surface that injection writes into; # they are not meant to be set directly in an agent.nix. Marked # `internal` so the host option is the only documented operator knob. - options.hyperhive.otel = { + options.services.hyperhive.agent.otel = { enable = lib.mkOption { type = lib.types.bool; default = false; @@ -197,7 +203,7 @@ in "none" ]); message = '' - hyperhive.otel.enable is on for agent ${userName}, but + services.hyperhive.agent.otel.enable is on for agent ${userName}, but services.journald.storage is "${config.services.journald.storage}" in this container. diff --git a/nix/agent-modules/packages.nix b/nix/agent-modules/packages.nix index cc14d826..762cd25b 100644 --- a/nix/agent-modules/packages.nix +++ b/nix/agent-modules/packages.nix @@ -5,7 +5,7 @@ # individual key still wins. { lib, ... }: { - options.hyperhive.packages = lib.mkOption { + options.services.hyperhive.agent.packages = lib.mkOption { type = lib.types.attrsOf lib.types.package; internal = true; description = '' diff --git a/nix/agent-modules/queue.nix b/nix/agent-modules/queue.nix index 4d7c5837..ffcaaf2c 100644 --- a/nix/agent-modules/queue.nix +++ b/nix/agent-modules/queue.nix @@ -16,7 +16,7 @@ ... }: let - cfg = config.hyperhive.queue; + cfg = config.services.hyperhive.agent.queue; configured = cfg.natsUrl != null && cfg.tokenEndpoint != null; # The two ids `hive_c0re::lifecycle::host_config` forwards under. Neither @@ -32,7 +32,7 @@ let credentialsDir = "/run/credentials/hive-agent.service"; in { - options.hyperhive.queue = { + options.services.hyperhive.agent.queue = { natsUrl = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; @@ -61,7 +61,7 @@ in `client_credentials` token there and presents it to the queue, which authenticates it as the client named in the delivered credential. - Set together with {option}`hyperhive.queue.natsUrl` or not at all — + Set together with {option}`services.hyperhive.agent.queue.natsUrl` or not at all — the harness treats a half-set pair as a deployment bug rather than as "no queue coordinates". ''; @@ -79,7 +79,7 @@ in description = '' Path the agent's OIDC client id is delivered at, for a consumer outside the harness unit. Read-only: it is a fact about where the - credential lands, not a knob — see {option}`hyperhive.logs.queryUrl` + credential lands, not a knob — see {option}`services.hyperhive.agent.logs.queryUrl` for the consumer this exists for. ''; }; @@ -90,7 +90,7 @@ in default = "${credentialsDir}/${secretCredential}"; description = '' Path the agent's OIDC client secret is delivered at. Read-only for - the same reason as {option}`hyperhive.queue.clientIdFile`. + the same reason as {option}`services.hyperhive.agent.queue.clientIdFile`. 🩸 A PATH and never a value. The file is `0400` to the agent user and is read at the moment of a token request; nothing in this tree puts diff --git a/nix/agent-modules/renamed-options.nix b/nix/agent-modules/renamed-options.nix new file mode 100644 index 00000000..cbf6d554 --- /dev/null +++ b/nix/agent-modules/renamed-options.nix @@ -0,0 +1,213 @@ +# Deprecation shims for the agent tier's old option namespace. +# +# Every per-agent option used to live at the top level, `hyperhive.`, +# while the host tier has always been under `services.hyperhive.*`. The agent +# tier moved to `services.hyperhive.agent.`; these entries keep an +# existing `agent.nix` evaluating (with a deprecation warning) against the old +# spelling. +# +# ⚠️ One entry per LEAF, not per namespace. A single entry for a parent path +# only covers its children when the parent is a submodule-typed option; every +# nested option here (`user`, `mcp`, `otel`, …) is a plain attrset of +# declarations instead, so each leaf needs its own line — the same reason +# `../host-modules/deploy.nix` renames the CI runner's knobs one by one. +{ lib, ... }: +{ + imports = [ + (lib.mkRenamedOptionModule + [ "hyperhive" "_bashEnvFragments" ] + [ "services" "hyperhive" "agent" "_bashEnvFragments" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "allowedRecipients" ] + [ "services" "hyperhive" "agent" "allowedRecipients" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "autoCompact" ] + [ "services" "hyperhive" "agent" "autoCompact" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "availableModels" ] + [ "services" "hyperhive" "agent" "availableModels" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "backendEnvironmentFile" ] + [ "services" "hyperhive" "agent" "backendEnvironmentFile" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "cargo" "shortMessages" ] + [ "services" "hyperhive" "agent" "cargo" "shortMessages" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "claudeCodePath" ] + [ "services" "hyperhive" "agent" "claudeCodePath" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "claudeMarketplaces" ] + [ "services" "hyperhive" "agent" "claudeMarketplaces" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "claudeMemoryMaxBytes" ] + [ "services" "hyperhive" "agent" "claudeMemoryMaxBytes" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "claudePlugins" ] + [ "services" "hyperhive" "agent" "claudePlugins" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "claudePluginsAutoUpdate" ] + [ "services" "hyperhive" "agent" "claudePluginsAutoUpdate" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "dashboardLinks" ] + [ "services" "hyperhive" "agent" "dashboardLinks" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "docs" "enable" ] + [ "services" "hyperhive" "agent" "docs" "enable" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "docs" "source" ] + [ "services" "hyperhive" "agent" "docs" "source" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "effortLevel" ] + [ "services" "hyperhive" "agent" "effortLevel" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "extraMcpServers" ] + [ "services" "hyperhive" "agent" "extraMcpServers" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "extraWebProxies" ] + [ "services" "hyperhive" "agent" "extraWebProxies" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "forge" "url" ] + [ "services" "hyperhive" "agent" "forge" "url" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "frontend" "dist" ] + [ "services" "hyperhive" "agent" "frontend" "dist" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "frontend" "extraFiles" ] + [ "services" "hyperhive" "agent" "frontend" "extraFiles" ] + ) + # `frontend.mergedDist` moved with the rest and deliberately has NO entry: + # it is `readOnly` and ./frontend.nix already defines it, and a rename + # module contributes a *definition* to its target — so a shim for it makes + # every evaluation fail with "set multiple times". Nothing could have set + # it anyway; a reader of the old path now gets "option does not exist", + # which says the same thing sooner. Same reasoning as the `hostUsersFile` + # exclusion in ../host-modules/deploy.nix. + (lib.mkRenamedOptionModule + [ "hyperhive" "github" "enable" ] + [ "services" "hyperhive" "agent" "github" "enable" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "gui" "enable" ] + [ "services" "hyperhive" "agent" "gui" "enable" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "gui" "vncPort" ] + [ "services" "hyperhive" "agent" "gui" "vncPort" ] + ) + (lib.mkRenamedOptionModule [ "hyperhive" "hiveName" ] [ "services" "hyperhive" "agent" "hiveName" ]) + (lib.mkRenamedOptionModule [ "hyperhive" "icon" ] [ "services" "hyperhive" "agent" "icon" ]) + (lib.mkRenamedOptionModule + [ "hyperhive" "logs" "queryUrl" ] + [ "services" "hyperhive" "agent" "logs" "queryUrl" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "matrix" "enable" ] + [ "services" "hyperhive" "agent" "matrix" "enable" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "matrix" "url" ] + [ "services" "hyperhive" "agent" "matrix" "url" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "matrixAccounts" ] + [ "services" "hyperhive" "agent" "matrixAccounts" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "mcp" "bashHttpPort" ] + [ "services" "hyperhive" "agent" "mcp" "bashHttpPort" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "mcp" "httpPort" ] + [ "services" "hyperhive" "agent" "mcp" "httpPort" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "mcp" "matrixHttpPort" ] + [ "services" "hyperhive" "agent" "mcp" "matrixHttpPort" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "mcp" "subagentHttpPort" ] + [ "services" "hyperhive" "agent" "mcp" "subagentHttpPort" ] + ) + (lib.mkRenamedOptionModule [ "hyperhive" "model" ] [ "services" "hyperhive" "agent" "model" ]) + (lib.mkRenamedOptionModule + [ "hyperhive" "otel" "debug" ] + [ "services" "hyperhive" "agent" "otel" "debug" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "otel" "enable" ] + [ "services" "hyperhive" "agent" "otel" "enable" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "otel" "endpoint" ] + [ "services" "hyperhive" "agent" "otel" "endpoint" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "otel" "extraResourceAttributes" ] + [ "services" "hyperhive" "agent" "otel" "extraResourceAttributes" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "otel" "metricIntervalMs" ] + [ "services" "hyperhive" "agent" "otel" "metricIntervalMs" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "otel" "protocol" ] + [ "services" "hyperhive" "agent" "otel" "protocol" ] + ) + (lib.mkRenamedOptionModule [ "hyperhive" "packages" ] [ "services" "hyperhive" "agent" "packages" ]) + (lib.mkRenamedOptionModule + [ "hyperhive" "queue" "natsUrl" ] + [ "services" "hyperhive" "agent" "queue" "natsUrl" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "queue" "tokenEndpoint" ] + [ "services" "hyperhive" "agent" "queue" "tokenEndpoint" ] + ) + # `queue.clientIdFile` / `queue.clientSecretFile` are excluded for the + # `mergedDist` reason above: both are `readOnly`, so a shim's definition + # collides with the option's own and breaks every evaluation. They are + # derived paths no agent config could have set. + (lib.mkRenamedOptionModule + [ "hyperhive" "swarmName" ] + [ "services" "hyperhive" "agent" "swarmName" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "useApiKey" ] + [ "services" "hyperhive" "agent" "useApiKey" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "user" "gid" ] + [ "services" "hyperhive" "agent" "user" "gid" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "user" "name" ] + [ "services" "hyperhive" "agent" "user" "name" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "user" "passwordlessSudo" ] + [ "services" "hyperhive" "agent" "user" "passwordlessSudo" ] + ) + (lib.mkRenamedOptionModule + [ "hyperhive" "user" "uid" ] + [ "services" "hyperhive" "agent" "user" "uid" ] + ) + ]; +} diff --git a/nix/agent-modules/screen.nix b/nix/agent-modules/screen.nix index 88858e2d..5f581686 100644 --- a/nix/agent-modules/screen.nix +++ b/nix/agent-modules/screen.nix @@ -1,6 +1,6 @@ # Screen MCP — screenshot, keyboard, and mouse for GUI agents. # -# Auto-activated when `hyperhive.gui.enable = true`. Wires the +# Auto-activated when `services.hyperhive.agent.gui.enable = true`. Wires the # `hive-screen-mcp` stdio bridge as `extraMcpServers.screen` so claude # gets five tools: `screenshot`, `type_text`, `key_press`, # `mouse_move`, and `mouse_click`. @@ -17,10 +17,10 @@ ... }: { - config = lib.mkIf config.hyperhive.gui.enable { + config = lib.mkIf config.services.hyperhive.agent.gui.enable { # Register the screen MCP bridge so claude gets the screen tools. - hyperhive.extraMcpServers.screen = { - command = "${config.hyperhive.packages.hive-screen-mcp}/bin/hive-screen-mcp"; + services.hyperhive.agent.extraMcpServers.screen = { + command = "${config.services.hyperhive.agent.packages.hive-screen-mcp}/bin/hive-screen-mcp"; args = [ ]; }; diff --git a/nix/agent-modules/user.nix b/nix/agent-modules/user.nix index ec42b296..19f66a11 100644 --- a/nix/agent-modules/user.nix +++ b/nix/agent-modules/user.nix @@ -1,4 +1,4 @@ -# Per-agent unix user: the `hyperhive.user.*` options, the user/group +# Per-agent unix user: the `services.hyperhive.agent.user.*` options, the user/group # declarations, passwordless sudo, and the first-boot migration that # chowns the bind-mounted state dirs to the agent user. { @@ -8,7 +8,7 @@ ... }: let - userName = config.hyperhive.user.name; + userName = config.services.hyperhive.agent.user.name; homeDir = "/home/${userName}"; in { @@ -20,7 +20,7 @@ in # uniquely-named user matching its agent label. UID auto-assigned # by NixOS (the auto-allocation range for normal users); no hard- # coded UID. - options.hyperhive.user.name = lib.mkOption { + options.services.hyperhive.agent.user.name = lib.mkOption { type = lib.types.strMatching "^[a-z_][a-z0-9_-]{0,30}$"; default = "agent"; example = "iris"; @@ -33,11 +33,11 @@ in Constraints match `useradd`'s NAME_REGEX: lowercase / `_` start, total length ≤ 31, no special characters. UID is auto-assigned - by NixOS unless `hyperhive.user.uid` is explicitly set. + by NixOS unless `services.hyperhive.agent.user.uid` is explicitly set. ''; }; - options.hyperhive.user.uid = lib.mkOption { + options.services.hyperhive.agent.user.uid = lib.mkOption { type = lib.types.nullOr lib.types.int; default = null; example = 1100; @@ -59,24 +59,24 @@ in ''; }; - options.hyperhive.user.gid = lib.mkOption { + options.services.hyperhive.agent.user.gid = lib.mkOption { type = lib.types.nullOr lib.types.int; default = null; example = 1100; description = '' Optional fixed GID for the per-agent unix group. `null` (default) - lets NixOS auto-assign. Usually set alongside `hyperhive.user.uid` + lets NixOS auto-assign. Usually set alongside `services.hyperhive.agent.user.uid` to the same value (the conventional Unix pattern for per-user groups where uid == gid), but can be set independently. ''; }; - options.hyperhive.user.passwordlessSudo = lib.mkOption { + options.services.hyperhive.agent.user.passwordlessSudo = lib.mkOption { type = lib.types.bool; default = true; example = false; description = '' - Grant `${config.hyperhive.user.name}` passwordless sudo + Grant `${config.services.hyperhive.agent.user.name}` passwordless sudo (`NOPASSWD: ALL`). True by default so claude's `Bash` tool keeps working for tools that expect root inside the container (`systemctl`, package managers in dev shells, etc.) — the @@ -94,10 +94,12 @@ in assertions = [ { assertion = - config.hyperhive.user.uid == null - || (config.hyperhive.user.uid >= 1000 && config.hyperhive.user.uid < 60000); + config.services.hyperhive.agent.user.uid == null + || ( + config.services.hyperhive.agent.user.uid >= 1000 && config.services.hyperhive.agent.user.uid < 60000 + ); message = '' - hyperhive.user.uid must be in [1000, 60000) — values below + services.hyperhive.agent.user.uid must be in [1000, 60000) — values below 1000 clash with system accounts; values ≥ 60000 are reserved by NixOS for dynamic allocation. Leave unset (null) to let NixOS auto-assign. @@ -105,11 +107,13 @@ in } { assertion = - config.hyperhive.user.gid == null - || (config.hyperhive.user.gid >= 1000 && config.hyperhive.user.gid < 60000); + config.services.hyperhive.agent.user.gid == null + || ( + config.services.hyperhive.agent.user.gid >= 1000 && config.services.hyperhive.agent.user.gid < 60000 + ); message = '' - hyperhive.user.gid must be in [1000, 60000) — same range - constraint as hyperhive.user.uid. + services.hyperhive.agent.user.gid must be in [1000, 60000) — same range + constraint as services.hyperhive.agent.user.uid. ''; } ]; @@ -123,29 +127,29 @@ in home = homeDir; createHome = true; group = userName; - extraGroups = lib.optional config.hyperhive.user.passwordlessSudo "wheel"; + extraGroups = lib.optional config.services.hyperhive.agent.user.passwordlessSudo "wheel"; # Matches /bin/bash on NixOS — the harness's claude shell-outs # expect a POSIX shell at $SHELL; bashInteractive is already # the system default for the root user too. shell = pkgs.bashInteractive; } - // lib.optionalAttrs (config.hyperhive.user.uid != null) { - uid = config.hyperhive.user.uid; + // lib.optionalAttrs (config.services.hyperhive.agent.user.uid != null) { + uid = config.services.hyperhive.agent.user.uid; }; users.groups.${userName} = { } - // lib.optionalAttrs (config.hyperhive.user.gid != null) { - gid = config.hyperhive.user.gid; + // lib.optionalAttrs (config.services.hyperhive.agent.user.gid != null) { + gid = config.services.hyperhive.agent.user.gid; }; # `NOPASSWD: ALL` for the agent user. Lets claude's Bash tool # keep working with anything that expected root (systemctl, # nix-env, etc.) without prompting. Flip - # `hyperhive.user.passwordlessSudo = false` to drop both + # `services.hyperhive.agent.user.passwordlessSudo = false` to drop both # the wheel-group membership and this sudoers entry; anything # that needs root then fails loudly instead of silently # succeeding. - security.sudo.extraRules = lib.mkIf config.hyperhive.user.passwordlessSudo [ + security.sudo.extraRules = lib.mkIf config.services.hyperhive.agent.user.passwordlessSudo [ { users = [ userName ]; commands = [ diff --git a/nix/agent-modules/weston-vnc.nix b/nix/agent-modules/weston-vnc.nix index 1167b8eb..05fedb92 100644 --- a/nix/agent-modules/weston-vnc.nix +++ b/nix/agent-modules/weston-vnc.nix @@ -8,8 +8,8 @@ let # GUI processes run as the agent's own non-root user — the same user # hive-agent runs as (declared + home-chowned by ./user.nix) — so # weston, the wayland client, and the agent share one user session. - # `hyperhive.user.name` is set per-agent by the meta-flake renderer. - userName = config.hyperhive.user.name; + # `services.hyperhive.agent.user.name` is set per-agent by the meta-flake renderer. + userName = config.services.hyperhive.agent.user.name; # Static weston config. `[core] idle-time=0` disables the 300s idle / # lock screen; `[vnc] auth-method=none` + the `--disable-transport- @@ -31,7 +31,7 @@ let in { # Optional Weston (Wayland compositor) with the VNC backend, - # surfaced as a per-agent `hyperhive.gui.enable` option. Imported + # surfaced as a per-agent `services.hyperhive.agent.gui.enable` option. Imported # from ./default.nix so every sub-agent + the manager sees the # option; only those that flip it on get the service. # @@ -41,7 +41,7 @@ in # Harness-side WebSocket relay shape: docs/web-ui/agent.md::Per-agent # endpoints (`/screen` + `/screen/ws`). - options.hyperhive.gui.enable = lib.mkOption { + options.services.hyperhive.agent.gui.enable = lib.mkOption { type = lib.types.bool; default = false; description = '' @@ -50,7 +50,7 @@ in relay. Renders in software (pixman) — no GPU, DRM, or VT access, so no extra container capabilities are needed. - Weston binds a fixed VNC port (`hyperhive.gui.vncPort`) on the + Weston binds a fixed VNC port (`services.hyperhive.agent.gui.vncPort`) on the container's own loopback. Network isolation is unconditional (each agent has its own netns), so a fixed port can't collide across containers — no per-agent hashing needed. The harness @@ -60,7 +60,7 @@ in loop instead of blocking `nixos-container update`. Weston, the wayland client and the agent harness run as the - agent's own non-root user (`hyperhive.user.name`), sharing one + agent's own non-root user (`services.hyperhive.agent.user.name`), sharing one session: a fixed `XDG_RUNTIME_DIR=/run/gui`, one wayland display, and one D-Bus session bus at `/run/gui/bus` (gui-dbus.service), so GUI clients need no private `dbus-run-session`. @@ -73,7 +73,7 @@ in # is container-local and can't collide. Internal: the harness reads # the value via the `HIVE_GUI_VNC_PORT` env var the harness service # injects from this option, not directly. - options.hyperhive.gui.vncPort = lib.mkOption { + options.services.hyperhive.agent.gui.vncPort = lib.mkOption { type = lib.types.port; default = 5900; internal = true; @@ -84,14 +84,14 @@ in ''; }; - config = lib.mkIf config.hyperhive.gui.enable { + config = lib.mkIf config.services.hyperhive.agent.gui.enable { # The GUI must run non-root: weston + the wayland client share the # agent's own user session. `user.name` is the agent name for every # spawned agent; only a misconfigured root-named agent would trip this. assertions = [ { assertion = userName != "root"; - message = "hyperhive.gui.enable requires a non-root hyperhive.user.name (the GUI runs as that user)."; + message = "services.hyperhive.agent.gui.enable requires a non-root services.hyperhive.agent.user.name (the GUI runs as that user)."; } ]; @@ -177,7 +177,7 @@ in --config=${westonIni} \ --backend=vnc-backend.so \ --renderer=pixman \ - --port=${toString config.hyperhive.gui.vncPort} \ + --port=${toString config.services.hyperhive.agent.gui.vncPort} \ --socket=wayland-0 \ --disable-transport-layer-security ''; diff --git a/nix/templates/ruth.nix b/nix/templates/ruth.nix index 0902001f..89c19ddc 100644 --- a/nix/templates/ruth.nix +++ b/nix/templates/ruth.nix @@ -10,5 +10,5 @@ # `$HIVE_DOCS_DIR/`, added via `claude --add-dir`). `mkDefault` so a # manager's own `agent.nix` can still turn it off. Other agents default # off. - hyperhive.docs.enable = lib.mkDefault true; + services.hyperhive.agent.docs.enable = lib.mkDefault true; } From 6ea81aae6527880526d2f31bd08e393c62e0efd6 Mon Sep 17 00:00:00 2001 From: atlas Date: Thu, 17 Sep 2026 20:19:30 +0200 Subject: [PATCH 2/2] hive-c0re: render the new agent option paths into generated agent flakes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit meta.rs writes each agent's flake, and it still named the pre-move `hyperhive.*` paths — so every agent rebuild would print a rename deprecation warning about a line no human wrote and no operator could fix. A warning nobody can act on trains everyone to ignore the ones that matter, which is the whole value of the alias shims. Repoints the FORWARDED_VAR_OPTIONS table and every other emitted option assignment (otel.*, docs.source, claudeCodePath, github.enable, user.name, claudeMemoryMaxBytes) to `services.hyperhive.agent.*`, with the test expectations that pin the rendered text. The flake input named `hyperhive` (`hyperhive.url`, `hyperhive.inputs.nixpkgs.follows`, `hyperhive.nixosConfigurations.*`), hive-tier `services.hyperhive.*` paths, and the `@hyperhive.local` git identity share the word and are untouched. Also repoints the same option paths where they appear in comments, rustdoc and runtime message strings across the other crates — a refusal message naming `hyperhive.allowedRecipients` sends an operator to a path that will stop existing. Prose under docs/ is deliberately not in this commit. Refs #4473 --- hive-agent-mcp/src/mcp/mod.rs | 2 +- hive-agent-mcp/src/mcp/render.rs | 2 +- hive-agent-mcp/src/send_allow.rs | 2 +- hive-agent-sock/src/extra_mcp.rs | 6 +- hive-agent-sock/src/lib.rs | 2 +- hive-agent/src/events.rs | 2 +- hive-agent/src/harness_state.rs | 4 +- hive-agent/src/login.rs | 2 +- hive-agent/src/mcp_config.rs | 10 +- hive-agent/src/plugins.rs | 4 +- hive-agent/src/prompt.rs | 4 +- hive-agent/src/turn.rs | 4 +- hive-agent/src/web_ui/mod.rs | 6 +- hive-agent/src/web_ui/proxy.rs | 2 +- hive-agent/src/web_ui/screen.rs | 2 +- hive-agent/src/web_ui/state.rs | 4 +- hive-c0re/src/container_view.rs | 2 +- hive-c0re/src/coordinator.rs | 8 +- hive-c0re/src/meta.rs | 123 ++++++++++++++----------- hive-c0re/src/priv_client.rs | 2 +- hive-matrix-mcp/src/accounts.rs | 2 +- hive-matrix-mcp/src/main.rs | 2 +- hive-matrix-mcp/src/mcp.rs | 34 +++---- hive-matrix-mcp/src/paths.rs | 2 +- hive-priv-sock/src/lib.rs | 2 +- hive-priv/src/main.rs | 4 +- hive-screen-mcp/src/main.rs | 10 +- hive-subagent-mcp/src/mcp_config.rs | 2 +- hive-subagent-mcp/src/session.rs | 2 +- swarm-controller/src/matrix_account.rs | 6 +- 30 files changed, 138 insertions(+), 121 deletions(-) diff --git a/hive-agent-mcp/src/mcp/mod.rs b/hive-agent-mcp/src/mcp/mod.rs index 4d657e0b..a932fc9e 100644 --- a/hive-agent-mcp/src/mcp/mod.rs +++ b/hive-agent-mcp/src/mcp/mod.rs @@ -150,7 +150,7 @@ impl AgentServer { async fn send(&self, Parameters(args): Parameters) -> String { let log = format!("{args:?}"); let to = args.to.clone(); - // Check per-agent allow-list (hyperhive.allowedRecipients). When no + // Check per-agent allow-list (services.hyperhive.agent.allowedRecipients). When no // policy file is present (e.g. manager containers) the check is a no-op. if let Err(refusal) = crate::send_allow::check_send_allowed(&to) { return run_tool_envelope("send", log, async move { refusal }).await; diff --git a/hive-agent-mcp/src/mcp/render.rs b/hive-agent-mcp/src/mcp/render.rs index 48563a1b..ef086450 100644 --- a/hive-agent-mcp/src/mcp/render.rs +++ b/hive-agent-mcp/src/mcp/render.rs @@ -318,7 +318,7 @@ pub(super) struct MatrixRoomUnread { } /// Default port `hive-matrix-daemon` serves its MCP + status endpoints -/// on (`hyperhive.mcp.matrixHttpPort`'s nix default). Overridable via +/// on (`services.hyperhive.agent.mcp.matrixHttpPort`'s nix default). Overridable via /// `HIVE_MATRIX_HTTP_PORT` for parity with the port options nix already /// exposes; unset in practice since a single fixed port is safe (each /// agent container is its own network namespace — see docs/networking/network.md). diff --git a/hive-agent-mcp/src/send_allow.rs b/hive-agent-mcp/src/send_allow.rs index 741de09b..64681a47 100644 --- a/hive-agent-mcp/src/send_allow.rs +++ b/hive-agent-mcp/src/send_allow.rs @@ -49,7 +49,7 @@ pub fn check_send_allowed(to: &str) -> Result<(), String> { return Ok(()); } Err(format!( - "send refused: recipient '{to}' not in hyperhive.allowedRecipients \ + "send refused: recipient '{to}' not in services.hyperhive.agent.allowedRecipients \ (configured in agent.nix). Allowed: {allow:?}. Your structural \ parent is always reachable — route through `send(to: \"{}\", …)` \ if you need to reach someone outside the allow-list.", diff --git a/hive-agent-sock/src/extra_mcp.rs b/hive-agent-sock/src/extra_mcp.rs index 57681c57..a9db756b 100644 --- a/hive-agent-sock/src/extra_mcp.rs +++ b/hive-agent-sock/src/extra_mcp.rs @@ -1,4 +1,4 @@ -//! Shared spec for `hyperhive.extraMcpServers` entries: the on-disk JSON +//! Shared spec for `services.hyperhive.agent.extraMcpServers` entries: the on-disk JSON //! shape the nix module (`nix/agent-modules/mcp.nix`) renders to //! `/etc/hyperhive/extra-mcp.json`, plus the parsing and per-entry //! JSON-rendering logic every consumer needs. Lives in `hive-agent-sock` @@ -16,7 +16,7 @@ use std::path::Path; /// `--mcp-config`. pub const EXTRA_MCP_PATH: &str = "/etc/hyperhive/extra-mcp.json"; -/// An extra MCP server declared via `hyperhive.extraMcpServers`. Two +/// An extra MCP server declared via `services.hyperhive.agent.extraMcpServers`. Two /// transports: `Stdio` (the consumer spawns `command` fresh each turn, talks /// JSON-RPC over its stdin/stdout) and `Http` (point claude at a long-lived /// streamable-http `url` instead — no per-turn spawn, no re-registration @@ -55,7 +55,7 @@ impl ExtraMcpServer { } /// Whether this entry is opted in to subagent sessions - /// (`hyperhive.extraMcpServers..availableToSubagents`, default + /// (`services.hyperhive.agent.extraMcpServers..availableToSubagents`, default /// `false`). Subagents run `--strict-mcp-config` with no discovery, so /// this flag is the only path an entry reaches a subagent's own /// `--mcp-config` at all — see `hive-subagent-mcp`'s `mcp_config` diff --git a/hive-agent-sock/src/lib.rs b/hive-agent-sock/src/lib.rs index 9458f1e0..e818f08e 100644 --- a/hive-agent-sock/src/lib.rs +++ b/hive-agent-sock/src/lib.rs @@ -16,7 +16,7 @@ //! signals its own turn loop directly, so hive-c0re is not in either path — //! no broker round-trip, no long-poll, no marker files. //! -//! **`extra_mcp`**: the `hyperhive.extraMcpServers` spec + parsing, shared by +//! **`extra_mcp`**: the `services.hyperhive.agent.extraMcpServers` spec + parsing, shared by //! `hive-agent` (the main session's `--mcp-config`) and `hive-subagent-mcp` //! (the subagent-eligible subset) — not a socket protocol at all, but this //! crate is the natural home since both consumers already depend on it. diff --git a/hive-agent/src/events.rs b/hive-agent/src/events.rs index a273f8d0..2ad82a8b 100644 --- a/hive-agent/src/events.rs +++ b/hive-agent/src/events.rs @@ -385,7 +385,7 @@ impl Bus { } }; let (tx, _) = broadcast::channel(CHANNEL_CAPACITY); - // Priority: HIVE_DEFAULT_MODEL (from hyperhive.model in agent.nix) > + // Priority: HIVE_DEFAULT_MODEL (from services.hyperhive.agent.model in agent.nix) > // persisted runtime override > compiled-in DEFAULT_MODEL. // The nix config always wins on rebuild; the persisted file is kept // for within-session tracking only (see persist_model / set_model). diff --git a/hive-agent/src/harness_state.rs b/hive-agent/src/harness_state.rs index 24dee94b..92c87126 100644 --- a/hive-agent/src/harness_state.rs +++ b/hive-agent/src/harness_state.rs @@ -189,7 +189,7 @@ pub(crate) fn write_api_key_mode(enabled: bool) { pub const DEFAULT_MODEL: &str = "haiku"; /// Return the model declared in `HIVE_DEFAULT_MODEL` (set from -/// `hyperhive.model` in `agent.nix`), or `None` if the env var is absent / +/// `services.hyperhive.agent.model` in `agent.nix`), or `None` if the env var is absent / /// empty. When `Some`, this takes precedence over any persisted runtime /// override so that nix config changes always take effect on rebuild. #[must_use] @@ -216,7 +216,7 @@ pub fn is_valid_effort(level: &str) -> bool { } /// Return the effort level declared in `HIVE_DEFAULT_EFFORT` (set from -/// `hyperhive.effortLevel` in `agent.nix`), or `None` if absent / empty. +/// `services.hyperhive.agent.effortLevel` in `agent.nix`), or `None` if absent / empty. /// Mirrors [`configured_model`]'s env shape. Unlike model, the persisted /// runtime override takes precedence over this baseline (see `Bus::new`): /// the operator's effort pick sticks across harness restart, with the nix diff --git a/hive-agent/src/login.rs b/hive-agent/src/login.rs index f2e6bdbd..a10284c5 100644 --- a/hive-agent/src/login.rs +++ b/hive-agent/src/login.rs @@ -119,7 +119,7 @@ impl LoginState { /// Whether this agent is configured to authenticate to its backend with an /// API key rather than a Claude OAuth session — set by -/// `hyperhive.useApiKey` via the `HIVE_USE_API_KEY` env var +/// `services.hyperhive.agent.useApiKey` via the `HIVE_USE_API_KEY` env var /// (`nix/agent-modules/agent-service.nix`). Read fresh on every call rather /// than cached: it's an env var, not a value worth a `OnceLock` for. #[must_use] diff --git a/hive-agent/src/mcp_config.rs b/hive-agent/src/mcp_config.rs index faf62fe0..deff0150 100644 --- a/hive-agent/src/mcp_config.rs +++ b/hive-agent/src/mcp_config.rs @@ -1,7 +1,7 @@ //! Claude launch-config layer: resolves the agent's tool-group / capability //! set into the `--allowedTools` / `--tools` argument strings and renders the //! `--mcp-config` blob claude reads at spawn (built-in hyperhive server + -//! any `hyperhive.extraMcpServers`). Pure config-string generation consumed by +//! any `services.hyperhive.agent.extraMcpServers`). Pure config-string generation consumed by //! [`crate::turn`] when it builds the claude command. It never touches the //! running MCP server (a separate binary) — the `send` allow-list check that //! server enforces lives alongside it in the `hive-agent-mcp` crate. @@ -12,7 +12,7 @@ pub const SERVER_NAME: &str = "hyperhive"; /// Default loopback port the built-in hyperhive MCP surface is served on /// (streamable HTTP, via the persistent `hive-mcp-http` daemon). Overridable -/// via `hyperhive.mcp.httpPort`; **must match that option's default** in +/// via `services.hyperhive.agent.mcp.httpPort`; **must match that option's default** in /// `nix/agent-modules/mcp.nix`. Safe as a single fixed value across all /// agents because each container runs in its own private network namespace, /// so `127.0.0.1:` is per-container-private (no cross-agent collision). @@ -65,7 +65,7 @@ fn allowed_capability_tools() -> Vec { /// Tool group an extra (out-of-process) MCP server is gated behind, if any. /// -/// Most `hyperhive.extraMcpServers` entries are ungated — available whenever +/// Most `services.hyperhive.agent.extraMcpServers` entries are ungated — available whenever /// the operator declares them. The `bash` server is the exception: raw shell /// execution is a privilege, so it is only exposed when the agent holds the /// `Execution` tool group. Unlike the in-process hyperhive tools (gated at @@ -135,7 +135,7 @@ pub fn allowed_mcp_tools(groups: &[hive_sh4re::permissions::ToolGroup]) -> Vec__` so claude can call // them without per-tool operator approval. `["*"]` (the default) @@ -177,7 +177,7 @@ pub fn allowed_tools_arg() -> String { /// The built-in `hyperhive` surface is an HTTP entry pointing at the /// persistent `hive-mcp-http` daemon (see [`DEFAULT_MCP_HTTP_PORT`]); there /// is no per-turn stdio child for it. Merges in any extra MCP servers -/// declared via `hyperhive.extraMcpServers` — each one is either a +/// declared via `services.hyperhive.agent.extraMcpServers` — each one is either a /// per-turn stdio bridge or another persistent HTTP entry, per its own /// `type`. #[must_use] diff --git a/hive-agent/src/plugins.rs b/hive-agent/src/plugins.rs index e3bc3cdc..3928ee89 100644 --- a/hive-agent/src/plugins.rs +++ b/hive-agent/src/plugins.rs @@ -1,5 +1,5 @@ //! Boot-time `claude plugin install` driver. Reads the list declared -//! via the `hyperhive.claudePlugins` NixOS option (rendered to +//! via the `services.hyperhive.agent.claudePlugins` NixOS option (rendered to //! `/etc/hyperhive/claude-plugins.json` by the harness module) and //! shells out `claude plugin install ` for each entry. Runs once //! per harness boot before the turn loop; `claude plugin install` @@ -62,7 +62,7 @@ async fn add_marketplaces() { } } -/// Read the `hyperhive.claudePluginsAutoUpdate` flag written by the NixOS +/// Read the `services.hyperhive.agent.claudePluginsAutoUpdate` flag written by the NixOS /// module. Defaults to `false` when the file is absent or unparseable. async fn auto_update_enabled() -> bool { match tokio::fs::read_to_string(AUTO_UPDATE_PATH).await { diff --git a/hive-agent/src/prompt.rs b/hive-agent/src/prompt.rs index 3e0393ac..b1d2edd4 100644 --- a/hive-agent/src/prompt.rs +++ b/hive-agent/src/prompt.rs @@ -40,7 +40,7 @@ pub fn render( .replace("{operator_pronouns}", operator_pronouns) .replace("{hive_identity}", &hive_identity) .replace("{swarm_identity}", &swarm_identity); - // When the reference docs are mounted in-container (`hyperhive.docs.enable` + // When the reference docs are mounted in-container (`services.hyperhive.agent.docs.enable` // wires `HIVE_DOCS_DIR` + `claude --add-dir`), append a single pointer // sentence so the agent knows they exist. Additive: it doesn't replace the // agent's own memory/project instructions. Absent env → no change. @@ -129,7 +129,7 @@ pub async fn write_system_prompt(_socket: &Path, label: &str) -> Result // verbatim (single-hive deployments see no diff). let hive_name = crate::identity::hive_name(); let swarm_name = crate::identity::swarm_name(); - // `hyperhive.docs.enable` sets HIVE_DOCS_DIR (and the harness passes it to + // `services.hyperhive.agent.docs.enable` sets HIVE_DOCS_DIR (and the harness passes it to // claude via `--add-dir`); when present, render() appends a pointer line. let docs_dir = std::env::var("HIVE_DOCS_DIR") .ok() diff --git a/hive-agent/src/turn.rs b/hive-agent/src/turn.rs index 8b8f4a23..903fffec 100644 --- a/hive-agent/src/turn.rs +++ b/hive-agent/src/turn.rs @@ -122,7 +122,7 @@ impl TurnFiles { /// Drop the MCP config blob claude reads from `--mcp-config `. /// The built-in hyperhive surface is served over HTTP by the persistent /// `hive-mcp-http` daemon, so no per-turn stdio child is spawned; extra -/// servers declared via `hyperhive.extraMcpServers` are still stdio bridges. +/// servers declared via `services.hyperhive.agent.extraMcpServers` are still stdio bridges. /// /// # Errors /// @@ -576,7 +576,7 @@ fn session_store() -> hive_claude::SessionStore { /// tool allow-lists and the optional docs `--add-dir`. fn claude_config(bus: &Bus, files: &TurnFiles) -> Config { let mut add_dirs = Vec::new(); - // hyperhive.docs.enable wires HIVE_DOCS_DIR to the in-container reference + // services.hyperhive.agent.docs.enable wires HIVE_DOCS_DIR to the in-container reference // docs; expose it as an additional readable directory when set. if let Some(docs_dir) = std::env::var_os("HIVE_DOCS_DIR") && !docs_dir.is_empty() diff --git a/hive-agent/src/web_ui/mod.rs b/hive-agent/src/web_ui/mod.rs index 15ff79ec..88d848e6 100644 --- a/hive-agent/src/web_ui/mod.rs +++ b/hive-agent/src/web_ui/mod.rs @@ -92,7 +92,7 @@ pub async fn serve( .map(PathBuf::from) .context( "HIVE_STATIC_DIR env var not set — point it at the merged \ - per-agent dist (see hyperhive.frontend.mergedDist in nix)", + per-agent dist (see services.hyperhive.agent.frontend.mergedDist in nix)", )?; if !static_dir.is_dir() { anyhow::bail!( @@ -130,7 +130,7 @@ pub async fn serve( .route("/api/stats", get(stats::api_stats)) .route("/screen/ws", get(screen::screen_ws)) .route("/icon", get(screen::serve_icon)); - // Mount any `hyperhive.extraWebProxies` under `/extra//` before the + // Mount any `services.hyperhive.agent.extraWebProxies` under `/extra//` before the // static fallback so declared proxies win over `ServeDir`. let app = proxy::mount_extra_proxies(app) // Anything else (`/`, `/stats`, `/screen`, `/static/*`) @@ -139,7 +139,7 @@ pub async fn serve( // (so `/stats` → `dist/stats.html`, `/screen` → `dist/ // screen.html`). Per-agent `extraFiles` additions are // already layered into this same directory (see - // hyperhive.frontend.mergedDist in nix). + // services.hyperhive.agent.frontend.mergedDist in nix). .fallback_service(ServeDir::new(&static_dir)) .with_state(state); // `HIVE_WEB_SOCKET` opt-in: when set + non-empty, bind a diff --git a/hive-agent/src/web_ui/proxy.rs b/hive-agent/src/web_ui/proxy.rs index 79115626..047cb079 100644 --- a/hive-agent/src/web_ui/proxy.rs +++ b/hive-agent/src/web_ui/proxy.rs @@ -1,4 +1,4 @@ -//! Extra web proxies (`HIVE_EXTRA_WEB_PROXIES` / `hyperhive.extraWebProxies`). +//! Extra web proxies (`HIVE_EXTRA_WEB_PROXIES` / `services.hyperhive.agent.extraWebProxies`). //! //! Each declared entry mounts a transparent reverse-proxy at `/extra//` //! in the per-agent web UI, forwarding every request (method, headers, body) diff --git a/hive-agent/src/web_ui/screen.rs b/hive-agent/src/web_ui/screen.rs index bb989566..80a3185f 100644 --- a/hive-agent/src/web_ui/screen.rs +++ b/hive-agent/src/web_ui/screen.rs @@ -10,7 +10,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt}; use super::AppState; /// This agent's icon. Serves the operator-configured SVG from -/// `/etc/hyperhive/icon.svg` (set via the `hyperhive.icon` agent.nix +/// `/etc/hyperhive/icon.svg` (set via the `services.hyperhive.agent.icon` agent.nix /// option) when present, otherwise **404** — there is no bundled /// server-side default any more (that was `hive_sh4re::assets:: /// branding_svg`, now removed). Consumers fall back client-side: the diff --git a/hive-agent/src/web_ui/state.rs b/hive-agent/src/web_ui/state.rs index 77907d56..f39e3095 100644 --- a/hive-agent/src/web_ui/state.rs +++ b/hive-agent/src/web_ui/state.rs @@ -255,7 +255,7 @@ enum AgentLinkKind { /// `http://:3000`. Forge, /// `url` is already a fully-qualified absolute URL — use as-is. - /// Agent-declared `hyperhive.dashboardLinks` extras arrive here. + /// Agent-declared `services.hyperhive.agent.dashboardLinks` extras arrive here. External, } @@ -366,7 +366,7 @@ fn agent_links(label: &str, gui_enabled: bool) -> Vec { }); } - // Agent-declared extras (`hyperhive.dashboardLinks` → the + // Agent-declared extras (`services.hyperhive.agent.dashboardLinks` → the // `hive-dashboard-links` NixOS oneshot writes them to // `{state_dir}/hyperhive-dashboard-links.json`). Shape on disk // is `{label, icon, url}` with absolute URLs — those become diff --git a/hive-c0re/src/container_view.rs b/hive-c0re/src/container_view.rs index f07596bb..a037f131 100644 --- a/hive-c0re/src/container_view.rs +++ b/hive-c0re/src/container_view.rs @@ -202,7 +202,7 @@ pub async fn build_all(hive: &crate::coordinator::HiveEnv) -> Vec // stale sentinel state is not meaningful when the harness isn't up. // // The first half doesn't apply to an api-key agent - // (`hyperhive.useApiKey`, stamped as `api_key_mode` in the + // (`services.hyperhive.agent.useApiKey`, stamped as `api_key_mode` in the // consolidated state file by `hive_agent::harness_state::write_api_key_mode`): // its `~/.claude/` is empty by design (no OAuth flow to complete), // so an empty dir there means nothing — only the auth-failed diff --git a/hive-c0re/src/coordinator.rs b/hive-c0re/src/coordinator.rs index 00cb0184..2daeafb9 100644 --- a/hive-c0re/src/coordinator.rs +++ b/hive-c0re/src/coordinator.rs @@ -47,7 +47,7 @@ pub struct Coordinator { pub hyperhive_flake: String, /// URL of the narrow `docs/` source (no fragment). Inlined into the /// meta `flake.nix` as `inputs.hyperhive-docs.url` and threaded to - /// each agent as `hyperhive.docs.source`. Its own store path + /// each agent as `services.hyperhive.agent.docs.source`. Its own store path /// so doc edits don't re-hash `hyperhive_flake`. pub hyperhive_docs_flake: String, /// Store-path URL of the nixpkgs to wire into the meta flake as @@ -59,7 +59,7 @@ pub struct Coordinator { /// `follows = "hyperhive/nixpkgs"` behaviour. pub nixpkgs_flake: String, /// Store path of the `claude-code` build every agent runs, written - /// into each per-agent flake as `hyperhive.claudeCodePath`. Set by + /// into each per-agent flake as `services.hyperhive.agent.claudeCodePath`. Set by /// the NixOS module option `services.hyperhive.c0re.claudeCodePackage` /// (which resolves the package and hands us its path). `None` = every /// agent keeps the `claude-code` from its own nixpkgs. @@ -228,13 +228,13 @@ pub struct HiveEnv { pub hyperhive_flake: String, /// Store-path URL of the narrow `docs/` source, wired into the meta /// flake as `inputs.hyperhive-docs.url` and threaded to each agent as - /// `hyperhive.docs.source`. Separate from `hyperhive_flake` + /// `services.hyperhive.agent.docs.source`. Separate from `hyperhive_flake` /// so a doc edit only re-locks this input, not the whole source. pub hyperhive_docs_flake: String, pub nixpkgs_flake: String, /// Store path of the `claude-code` agents run, or `None` for "each /// agent keeps the one out of its own nixpkgs". Travels into the - /// container as `hyperhive.claudeCodePath` — a plain string, kept + /// container as `services.hyperhive.agent.claudeCodePath` — a plain string, kept /// alive host-side by the module that resolved it. pub claude_code_path: Option, pub dashboard_port: u16, diff --git a/hive-c0re/src/meta.rs b/hive-c0re/src/meta.rs index 8dcffa94..f5988faa 100644 --- a/hive-c0re/src/meta.rs +++ b/hive-c0re/src/meta.rs @@ -739,21 +739,27 @@ const FORWARDED_VAR_OPTIONS: &[(&str, &str)] = &[ // whose option says no queue coordinates and whose env says otherwise logs the // partial-config error rather than half-connecting, which is the same // failure this map's doc describes and the reason both halves are wired. - ("HIVE_AGENT_NATS_URL", "hyperhive.queue.natsUrl"), + ( + "HIVE_AGENT_NATS_URL", + "services.hyperhive.agent.queue.natsUrl", + ), ( "HIVE_AGENT_OIDC_TOKEN_ENDPOINT", - "hyperhive.queue.tokenEndpoint", + "services.hyperhive.agent.queue.tokenEndpoint", ), // Read at build time to decide whether `swarm-logs` is installed at all — // see `nix/agent-modules/logs.nix`. Both halves wired for this map's own // reason: an agent whose option says no log store and whose env says // otherwise has a binary that is not on PATH and coordinates that claim // it is. - ("HIVE_AGENT_LOGS_QUERY_URL", "hyperhive.logs.queryUrl"), - ("HIVE_FORGE_URL", "hyperhive.forge.url"), - ("HIVE_MATRIX_URL", "hyperhive.matrix.url"), - ("HYPERHIVE_HIVE_NAME", "hyperhive.hiveName"), - ("HYPERHIVE_SWARM_NAME", "hyperhive.swarmName"), + ( + "HIVE_AGENT_LOGS_QUERY_URL", + "services.hyperhive.agent.logs.queryUrl", + ), + ("HIVE_FORGE_URL", "services.hyperhive.agent.forge.url"), + ("HIVE_MATRIX_URL", "services.hyperhive.agent.matrix.url"), + ("HYPERHIVE_HIVE_NAME", "services.hyperhive.agent.hiveName"), + ("HYPERHIVE_SWARM_NAME", "services.hyperhive.agent.swarmName"), ]; /// Render the forwarded-var option assignments for one agent's module block. @@ -874,17 +880,17 @@ struct OtelConfig { protocol: String, extra_resource_attributes: Option, metric_interval_ms: Option, - /// `HYPERHIVE_OTEL_DEBUG=1` → `hyperhive.otel.debug = true` → + /// `HYPERHIVE_OTEL_DEBUG=1` → `services.hyperhive.agent.otel.debug = true` → /// `CLAUDE_CODE_OTEL_DIAG_STDERR=1` in every agent's env. debug: bool, } /// Read the hive-wide OTEL config from env, or `None` when OTEL is off. /// Mirrors `hive_ca_source` — host state surfaced to the meta renderer -/// so it can bake build-time `hyperhive.otel.*` config into each agent +/// so it can bake build-time `services.hyperhive.agent.otel.*` config into each agent /// (the per-agent options the harness modules consume). Returns `None` /// when the endpoint signal is absent so the renderer emits no -/// `hyperhive.otel.*` lines and agents keep the disabled default. +/// `services.hyperhive.agent.otel.*` lines and agents keep the disabled default. fn otel_config() -> Option { let endpoint = std::env::var("HYPERHIVE_OTEL_ENDPOINT") .ok() @@ -1101,9 +1107,9 @@ where } // Narrow `docs/` source as its own input so a doc edit only // re-locks THIS input instead of re-hashing the whole `hyperhive` - // source. Threaded to each agent below as `hyperhive.docs.source`. + // source. Threaded to each agent below as `services.hyperhive.agent.docs.source`. // Empty = hive-c0re not built with the option wired up (legacy); - // agents then keep the harness default (`hyperhive.packages.reference-docs`). + // agents then keep the harness default (`services.hyperhive.agent.packages.reference-docs`). if !docs_flake.is_empty() { // `flake = false`: the docs/ tree is a plain source (no flake.nix), // so nix must treat it as raw source, not evaluate it as a flake. @@ -1169,11 +1175,11 @@ where ); // Point the in-container docs dir (`$HIVE_DOCS_DIR`) at the narrow // `hyperhive-docs` input instead of the harness default - // (`hyperhive.packages.reference-docs`, built from the now-docs-stripped source). + // (`services.hyperhive.agent.packages.reference-docs`, built from the now-docs-stripped source). // `inputs."hyperhive-docs"` is reachable via the outputs `@inputs` // capture. Emitted only when the input exists (docs_flake non-empty). if !docs_flake.is_empty() { - out.push_str(" hyperhive.docs.source = inputs.\"hyperhive-docs\".outPath;\n"); + out.push_str(" services.hyperhive.agent.docs.source = inputs.\"hyperhive-docs\".outPath;\n"); } // The `claude-code` agents run, as a bare store path rather than a // flake input: containers share the host's `/nix/store`, so the @@ -1185,7 +1191,10 @@ where // spelled out here is text and references nothing. // `None` = no override; agents keep their own nixpkgs' `claude-code`. if let Some(path) = claude_code_path { - let _ = writeln!(out, " hyperhive.claudeCodePath = \"{path}\";"); + let _ = writeln!( + out, + " services.hyperhive.agent.claudeCodePath = \"{path}\";" + ); } // CA trust: embed the hive's trust anchors so each agent validates // them at build time — the hive's own self-signed CA when active, @@ -1209,41 +1218,44 @@ where ); } // Hive-wide OTEL stats export (`services.hyperhive.otel.*`): inject the - // build-time `hyperhive.otel.*` config the harness modules consume (its + // build-time `services.hyperhive.agent.otel.*` config the harness modules consume (its // otelEnv + otelExecStart wrapper + LoadCredential). Host-driven, so // the same config lands on every agent; emitted only when enabled. // Mirrors the CA-cert injection above — host state -> build-time agent // module config. if let Some(otel) = otel_config() { let esc = |s: &str| s.replace('\\', "\\\\").replace('"', "\\\""); - out.push_str(" hyperhive.otel.enable = true;\n"); + out.push_str(" services.hyperhive.agent.otel.enable = true;\n"); let _ = writeln!( out, - " hyperhive.otel.endpoint = \"{}\";", + " services.hyperhive.agent.otel.endpoint = \"{}\";", esc(&otel.endpoint) ); let _ = writeln!( out, - " hyperhive.otel.protocol = \"{}\";", + " services.hyperhive.agent.otel.protocol = \"{}\";", esc(&otel.protocol) ); if let Some(attrs) = &otel.extra_resource_attributes { let _ = writeln!( out, - " hyperhive.otel.extraResourceAttributes = \"{}\";", + " services.hyperhive.agent.otel.extraResourceAttributes = \"{}\";", esc(attrs) ); } if let Some(ms) = otel.metric_interval_ms { // Int option — emit a bare numeric literal (no quotes). `ms` is a // parsed u64, so it can't inject anything into the rendered nix. - let _ = writeln!(out, " hyperhive.otel.metricIntervalMs = {ms};"); + let _ = writeln!( + out, + " services.hyperhive.agent.otel.metricIntervalMs = {ms};" + ); } if otel.debug { - out.push_str(" hyperhive.otel.debug = true;\n"); + out.push_str(" services.hyperhive.agent.otel.debug = true;\n"); } } - // Agent-facing service URLs (`hyperhive.forge.url`, `hyperhive.matrix.url`): + // Agent-facing service URLs (`services.hyperhive.agent.forge.url`, `services.hyperhive.agent.matrix.url`): // emit the host's real values as build-time agent config, the same // host-state -> agent-module shape as the otel block above. // @@ -1271,21 +1283,21 @@ where // operation the tests exercise directly. push_forwarded_var_options(&mut out, &forwarded_env_vars()); // GitHub integration is on by default in every agent - // (`hyperhive.github.enable`); the host turns it off hive-wide via + // (`services.hyperhive.agent.github.enable`); the host turns it off hive-wide via // `services.hyperhive.github.enable = false`, surfaced here as the // `HYPERHIVE_GITHUB_DISABLED` env on hive-c0re's unit. Only the OFF // override is propagated — the enabled default needs no per-agent line. if std::env::var_os("HYPERHIVE_GITHUB_DISABLED").is_some() { - out.push_str(" hyperhive.github.enable = false;\n"); + out.push_str(" services.hyperhive.agent.github.enable = false;\n"); } out.push_str( r#" # The harness service inside the container runs as a # non-root unix user named after the agent (`damocles`, # `iris`, `root`, …). UID auto-assigned by NixOS; the # per-agent override here is what makes - # `hyperhive.user.name` match the agent's identity + # `services.hyperhive.agent.user.name` match the agent's identity # instead of the harness default of `"agent"`. - hyperhive.user.name = name; + services.hyperhive.agent.user.name = name; # Without this an agent container is called `nixos` and ships # every log line under that: nixpkgs sets the hostname from the # attr name in the `containers.` submodule, and an @@ -1295,7 +1307,7 @@ where # nspawn itself already call this container, so logs join to # both without a prefix transform. networking.hostName = "h-${name}"; - hyperhive.claudeMemoryMaxBytes = memoryMaxBytes; + services.hyperhive.agent.claudeMemoryMaxBytes = memoryMaxBytes; programs.git.config.user = { name = name; email = "${name}@hyperhive.local"; @@ -1418,7 +1430,7 @@ where // Effective `MemoryMax=` for this agent (per-agent override, else // the hive-wide default), turned into a raw byte count so // the agent's claude-settings module can derive a JSC heap ceiling - // (see `hyperhive.claudeMemoryMaxBytes`). `null` when the + // (see `services.hyperhive.agent.claudeMemoryMaxBytes`). `null` when the // effective value is `"infinity"` or a RAM percentage — no // byte count to derive, dependent env var stays unset, same as // today's no-cap behavior. @@ -1759,7 +1771,7 @@ mod tests { "old-style follows must not appear when flake args are set:\n{out}" ); // the narrow docs source is its own non-flake input, and each - // agent's docs dir resolves from it rather than hyperhive.packages.reference-docs. + // agent's docs dir resolves from it rather than services.hyperhive.agent.packages.reference-docs. assert!( out.contains("hyperhive-docs.url = \"path:/nix/store/bbbb-hyperhive-docs-source\""), "expected hyperhive-docs input url:\n{out}" @@ -1769,7 +1781,9 @@ mod tests { "docs source is not a flake, must be flake = false:\n{out}" ); assert!( - out.contains("hyperhive.docs.source = inputs.\"hyperhive-docs\".outPath;"), + out.contains( + "services.hyperhive.agent.docs.source = inputs.\"hyperhive-docs\".outPath;" + ), "expected per-agent docs source wired to the input:\n{out}" ); } @@ -1777,7 +1791,7 @@ mod tests { #[test] fn render_flake_omits_docs_input_when_docs_flake_empty() { // Legacy / not-wired-up: empty docs_flake emits no docs input and - // leaves each agent on the harness default (hyperhive.packages.reference-docs). + // leaves each agent on the harness default (services.hyperhive.agent.packages.reference-docs). let out = render_flake( "github:example/hyperhive", "", @@ -1850,7 +1864,9 @@ mod tests { &[sample_spec("alice", false, 9001)], ); assert!( - out.contains("hyperhive.claudeCodePath = \"/nix/store/cccc-claude-code-2.1.220\";"), + out.contains( + "services.hyperhive.agent.claudeCodePath = \"/nix/store/cccc-claude-code-2.1.220\";" + ), "claude path assigned as a plain string literal:\n{out}" ); assert!( @@ -2046,9 +2062,9 @@ mod tests { ); assert_eq!( out, - " hyperhive.forge.url = \"http://forge.example.test\";\n\ - \x20 hyperhive.matrix.url = \"http://matrix.example.test\";\n\ - \x20 hyperhive.hiveName = \"pr1ma\";\n", + " services.hyperhive.agent.forge.url = \"http://forge.example.test\";\n\ + \x20 services.hyperhive.agent.matrix.url = \"http://matrix.example.test\";\n\ + \x20 services.hyperhive.agent.hiveName = \"pr1ma\";\n", "expected exactly the mapped options, in input order, indented for the module block" ); } @@ -2079,8 +2095,8 @@ mod tests { ); assert_eq!( out, - " hyperhive.queue.natsUrl = \"nats://10.42.0.1:4222\";\n\ - \x20 hyperhive.queue.tokenEndpoint = \"https://auth.t.local/api/oidc/token\";\n" + " services.hyperhive.agent.queue.natsUrl = \"nats://10.42.0.1:4222\";\n\ + \x20 services.hyperhive.agent.queue.tokenEndpoint = \"https://auth.t.local/api/oidc/token\";\n" ); } @@ -2184,11 +2200,11 @@ mod tests { std::env::remove_var("HIVE_MATRIX_URL"); } let forge_opt_at = out - .find("hyperhive.forge.url = \"http://forge.example.test\"") - .expect("hyperhive.forge.url must be rendered from HIVE_FORGE_URL"); + .find("services.hyperhive.agent.forge.url = \"http://forge.example.test\"") + .expect("services.hyperhive.agent.forge.url must be rendered from HIVE_FORGE_URL"); let matrix_opt_at = out - .find("hyperhive.matrix.url = \"http://matrix.example.test\"") - .expect("hyperhive.matrix.url must be rendered from HIVE_MATRIX_URL"); + .find("services.hyperhive.agent.matrix.url = \"http://matrix.example.test\"") + .expect("services.hyperhive.agent.matrix.url must be rendered from HIVE_MATRIX_URL"); let env_block_at = out .find("environment.variables = {") .expect("per-agent environment.variables block must exist"); @@ -2332,8 +2348,8 @@ mod tests { #[test] fn render_flake_injects_otel_when_signalled() { // services.hyperhive.otel.* -> HYPERHIVE_OTEL_* on hive-c0re's unit - // -> injected as build-time hyperhive.otel.* into every agent. With - // no endpoint signal, no hyperhive.otel lines are emitted (agents + // -> injected as build-time services.hyperhive.agent.otel.* into every agent. With + // no endpoint signal, no services.hyperhive.agent.otel lines are emitted (agents // keep the the harness modules disabled default). // // Serialised against every other env-mutating test in the crate. @@ -2376,32 +2392,33 @@ mod tests { let off = render(); assert!( - on_minimal.contains("hyperhive.otel.enable = true;"), + on_minimal.contains("services.hyperhive.agent.otel.enable = true;"), "otel enable must be injected:\n{on_minimal}" ); assert!( - on_minimal.contains("hyperhive.otel.endpoint = \"https://c.example/otel\";"), + on_minimal + .contains("services.hyperhive.agent.otel.endpoint = \"https://c.example/otel\";"), "otel endpoint must be injected:\n{on_minimal}" ); assert!( - on_minimal.contains("hyperhive.otel.protocol = \"grpc\";"), + on_minimal.contains("services.hyperhive.agent.otel.protocol = \"grpc\";"), "otel protocol must be injected:\n{on_minimal}" ); // Optional fields absent when unset. assert!( - !on_minimal.contains("hyperhive.otel.extraResourceAttributes"), + !on_minimal.contains("services.hyperhive.agent.otel.extraResourceAttributes"), "extraResourceAttributes must not appear when unset:\n{on_minimal}" ); assert!( on_full.contains( - "hyperhive.otel.extraResourceAttributes = \"deployment.environment=prod\";" + "services.hyperhive.agent.otel.extraResourceAttributes = \"deployment.environment=prod\";" ), "extraResourceAttributes must be injected when set:\n{on_full}" ); assert!( - !off.contains("hyperhive.otel"), + !off.contains("services.hyperhive.agent.otel"), "no otel lines when disabled:\n{off}" ); } @@ -2409,7 +2426,7 @@ mod tests { #[test] fn render_flake_injects_github_disable_only_when_signalled() { // services.hyperhive.github.enable = false -> HYPERHIVE_GITHUB_DISABLED - // on hive-c0re's unit -> `hyperhive.github.enable = false` injected into + // on hive-c0re's unit -> `services.hyperhive.agent.github.enable = false` injected into // every agent. On by default, so nothing is emitted unless disabled. // // Serialised against every other env-mutating test in the crate — the @@ -2441,11 +2458,11 @@ mod tests { std::env::remove_var("HYPERHIVE_GITHUB_DISABLED"); } assert!( - !on_default.contains("hyperhive.github.enable"), + !on_default.contains("services.hyperhive.agent.github.enable"), "github.enable must not be emitted by default (agents keep the true default):\n{on_default}" ); assert!( - disabled.contains("hyperhive.github.enable = false;"), + disabled.contains("services.hyperhive.agent.github.enable = false;"), "github.enable = false must be injected when the host disables it:\n{disabled}" ); } diff --git a/hive-c0re/src/priv_client.rs b/hive-c0re/src/priv_client.rs index dae34209..978c3ad6 100644 --- a/hive-c0re/src/priv_client.rs +++ b/hive-c0re/src/priv_client.rs @@ -403,7 +403,7 @@ pub async fn write_agent_matrix_token( /// (running as root). Writes `/github-token` 0600, chowned to the agent /// user so the `gh` wrapper / git credential helper can read it from inside the /// container. Single account per agent — no account suffix. The token value is -/// operator-supplied (for the agent's GitHub integration, `hyperhive.github.enable`). +/// operator-supplied (for the agent's GitHub integration, `services.hyperhive.agent.github.enable`). /// /// # Errors /// diff --git a/hive-matrix-mcp/src/accounts.rs b/hive-matrix-mcp/src/accounts.rs index ebbb055b..c20de1c8 100644 --- a/hive-matrix-mcp/src/accounts.rs +++ b/hive-matrix-mcp/src/accounts.rs @@ -8,7 +8,7 @@ //! `/matrix-sdk-state`) and the daemon-wide `HIVE_MATRIX_URL`. //! Any **extra** accounts come from the `HIVE_MATRIX_ACCOUNTS` env var //! (JSON, written by the nix harness module from -//! `hyperhive.matrixAccounts`) and are appended after `main`. +//! `services.hyperhive.agent.matrixAccounts`) and are appended after `main`. //! //! So a single-account agent (no `HIVE_MATRIX_ACCOUNTS`) gets exactly //! `main` — zero config, same behaviour as before: omitting `account` on diff --git a/hive-matrix-mcp/src/main.rs b/hive-matrix-mcp/src/main.rs index cb435f02..f327c24f 100644 --- a/hive-matrix-mcp/src/main.rs +++ b/hive-matrix-mcp/src/main.rs @@ -271,7 +271,7 @@ async fn bring_up_account( ) -> Result> { let Some(homeserver) = cfg.homeserver() else { // No homeserver for this account: the hive has none to offer (no - // matrix vhost) or this agent's `hyperhive.matrix.url` is null. Same + // matrix vhost) or this agent's `services.hyperhive.agent.matrix.url` is null. Same // no-op as a missing token — an absent integration, not a guess at // one. tracing::info!( diff --git a/hive-matrix-mcp/src/mcp.rs b/hive-matrix-mcp/src/mcp.rs index 302b2c2e..3301e87e 100644 --- a/hive-matrix-mcp/src/mcp.rs +++ b/hive-matrix-mcp/src/mcp.rs @@ -11,7 +11,7 @@ //! //! Multi-account: every tool carries an optional `account` arg naming //! which matrix account to act as (a `name` from -//! `hyperhive.matrixAccounts`); omitting it selects the agent's primary +//! `services.hyperhive.agent.matrixAccounts`); omitting it selects the agent's primary //! account (or errors, listing the choices, when more than one account //! is configured — see [`crate::accounts::Registry::resolve`]). @@ -49,7 +49,7 @@ struct SendMessageArgs { /// Message body. Markdown is rendered to HTML by the daemon /// (`text_markdown`); plain text passes through unchanged. body: String, - /// Matrix account to act as (a `name` from `hyperhive.matrixAccounts`). + /// Matrix account to act as (a `name` from `services.hyperhive.agent.matrixAccounts`). /// Omit to use the agent's primary account. #[serde(default)] account: Option, @@ -62,7 +62,7 @@ struct SendDmArgs { /// the recipient. user_id: String, body: String, - /// Matrix account to act as (a `name` from `hyperhive.matrixAccounts`). + /// Matrix account to act as (a `name` from `services.hyperhive.agent.matrixAccounts`). /// Omit to use the agent's primary account. #[serde(default)] account: Option, @@ -79,7 +79,7 @@ struct SendFileArgs { /// Optional caption, sent as a follow-up text message in the room. #[serde(default)] caption: Option, - /// Matrix account to act as (a `name` from `hyperhive.matrixAccounts`). + /// Matrix account to act as (a `name` from `services.hyperhive.agent.matrixAccounts`). /// Omit to use the agent's primary account. #[serde(default)] account: Option, @@ -90,7 +90,7 @@ struct OpenDmArgs { /// Matrix user id (`@user:server`) to open a DM with. The DM room is /// created if one doesn't already exist. user_id: String, - /// Matrix account to act as (a `name` from `hyperhive.matrixAccounts`). + /// Matrix account to act as (a `name` from `services.hyperhive.agent.matrixAccounts`). /// Omit to use the agent's primary account. #[serde(default)] account: Option, @@ -104,7 +104,7 @@ struct SendReactionArgs { /// Reaction key — usually an emoji (`👍`, `❤️`) but any string /// works per matrix spec. key: String, - /// Matrix account to act as (a `name` from `hyperhive.matrixAccounts`). + /// Matrix account to act as (a `name` from `services.hyperhive.agent.matrixAccounts`). /// Omit to use the agent's primary account. #[serde(default)] account: Option, @@ -115,7 +115,7 @@ struct SendReplyArgs { room: String, event_id: String, body: String, - /// Matrix account to act as (a `name` from `hyperhive.matrixAccounts`). + /// Matrix account to act as (a `name` from `services.hyperhive.agent.matrixAccounts`). /// Omit to use the agent's primary account. #[serde(default)] account: Option, @@ -125,7 +125,7 @@ struct SendReplyArgs { struct MarkReadArgs { room: String, event_id: String, - /// Matrix account to act as (a `name` from `hyperhive.matrixAccounts`). + /// Matrix account to act as (a `name` from `services.hyperhive.agent.matrixAccounts`). /// Omit to use the agent's primary account. #[serde(default)] account: Option, @@ -139,7 +139,7 @@ struct SendRedactArgs { /// Optional human-readable reason recorded on the redaction event. #[serde(default)] reason: Option, - /// Matrix account to act as (a `name` from `hyperhive.matrixAccounts`). + /// Matrix account to act as (a `name` from `services.hyperhive.agent.matrixAccounts`). /// Omit to use the agent's primary account. #[serde(default)] account: Option, @@ -147,7 +147,7 @@ struct SendRedactArgs { #[derive(Debug, Deserialize, JsonSchema)] struct ListRoomsArgs { - /// Matrix account to act as (a `name` from `hyperhive.matrixAccounts`). + /// Matrix account to act as (a `name` from `services.hyperhive.agent.matrixAccounts`). /// Omit to use the agent's primary account. #[serde(default)] account: Option, @@ -156,7 +156,7 @@ struct ListRoomsArgs { #[derive(Debug, Deserialize, JsonSchema)] struct ListRoomMembersArgs { room: String, - /// Matrix account to act as (a `name` from `hyperhive.matrixAccounts`). + /// Matrix account to act as (a `name` from `services.hyperhive.agent.matrixAccounts`). /// Omit to use the agent's primary account. #[serde(default)] account: Option, @@ -179,7 +179,7 @@ struct ReadRoomArgs { /// Mutually exclusive with `from`. #[serde(default)] until: Option, - /// Matrix account to act as (a `name` from `hyperhive.matrixAccounts`). + /// Matrix account to act as (a `name` from `services.hyperhive.agent.matrixAccounts`). /// Omit to use the agent's primary account. #[serde(default)] account: Option, @@ -196,7 +196,7 @@ struct DownloadFileArgs { /// named after the attachment; the returned `path` is where to read it. #[serde(default)] dest_path: Option, - /// Matrix account to act as (a `name` from `hyperhive.matrixAccounts`). + /// Matrix account to act as (a `name` from `services.hyperhive.agent.matrixAccounts`). /// Omit to use the agent's primary account. #[serde(default)] account: Option, @@ -204,7 +204,7 @@ struct DownloadFileArgs { #[derive(Debug, Deserialize, JsonSchema)] struct ListInvitesArgs { - /// Matrix account to act as (a `name` from `hyperhive.matrixAccounts`). + /// Matrix account to act as (a `name` from `services.hyperhive.agent.matrixAccounts`). /// Omit to use the agent's primary account. #[serde(default)] account: Option, @@ -214,7 +214,7 @@ struct ListInvitesArgs { struct JoinRoomArgs { /// Matrix room id (`!abc:server`) or canonical alias (`#name:server`). room: String, - /// Matrix account to act as (a `name` from `hyperhive.matrixAccounts`). + /// Matrix account to act as (a `name` from `services.hyperhive.agent.matrixAccounts`). /// Omit to use the agent's primary account. #[serde(default)] account: Option, @@ -228,7 +228,7 @@ struct ResolveInviteArgs { /// What to do with the invite: `"accept"` (join the room) or /// `"reject"` (decline and leave). action: String, - /// Matrix account to act as (a `name` from `hyperhive.matrixAccounts`). + /// Matrix account to act as (a `name` from `services.hyperhive.agent.matrixAccounts`). /// Omit to use the agent's primary account. #[serde(default)] account: Option, @@ -241,7 +241,7 @@ struct InviteUserArgs { room: String, /// Matrix user id of the invitee (`@user:server`). user_id: String, - /// Matrix account to act as (a `name` from `hyperhive.matrixAccounts`). + /// Matrix account to act as (a `name` from `services.hyperhive.agent.matrixAccounts`). /// Omit to use the agent's primary account. #[serde(default)] account: Option, diff --git a/hive-matrix-mcp/src/paths.rs b/hive-matrix-mcp/src/paths.rs index bb6e8f59..98257c0f 100644 --- a/hive-matrix-mcp/src/paths.rs +++ b/hive-matrix-mcp/src/paths.rs @@ -2,7 +2,7 @@ //! //! All paths are overridable via env vars for dev / test scenarios and //! to let the harness point the daemon at non-default locations when -//! the operator overrides `hyperhive.matrix.*` options. +//! the operator overrides `services.hyperhive.agent.matrix.*` options. use std::path::PathBuf; diff --git a/hive-priv-sock/src/lib.rs b/hive-priv-sock/src/lib.rs index 938cb721..60dfdcd2 100644 --- a/hive-priv-sock/src/lib.rs +++ b/hive-priv-sock/src/lib.rs @@ -510,7 +510,7 @@ pub enum PrivRequest { /// Write `github-token` into `AGENT_STATE_ROOT//state/github-token`. /// /// The operator-supplied GitHub personal access token (PAT) for the - /// agent's GitHub integration (`hyperhive.github.enable`). Same write + /// agent's GitHub integration (`services.hyperhive.agent.github.enable`). Same write /// semantics as /// `WriteAgentForgeToken` — validates `agent_name`, creates the state dir /// if absent, writes the file 0600, and chowns it to the agent so the diff --git a/hive-priv/src/main.rs b/hive-priv/src/main.rs index 4b2f3aaa..8eaca8d1 100644 --- a/hive-priv/src/main.rs +++ b/hive-priv/src/main.rs @@ -2812,7 +2812,7 @@ fn validate_name_chars(name: &str) -> Result<()> { /// [`validate_name_chars`] allows on purpose. /// /// An agent name is an `Ident` and lowercase by design. An account name is an -/// attribute name in `hyperhive.matrixAccounts`, typed `attrsOf` with no +/// attribute name in `services.hyperhive.agent.matrixAccounts`, typed `attrsOf` with no /// charset constraint, so `Ops_Relay9` is a key an operator may already have /// written. `swarm_secret_client::path::checked_segment` accepts exactly this /// set for the same name in the secret store — the two must agree, or a @@ -3502,7 +3502,7 @@ mod tests { /// Ported from `hive-c0re`'s `credential.rs`, which used to build this path /// itself. The controls are the load-bearing half: an account name is an - /// attrset key in `hyperhive.matrixAccounts`, so uppercase and underscore + /// attrset key in `services.hyperhive.agent.matrixAccounts`, so uppercase and underscore /// are names an operator can already have written, and the secret store /// accepts exactly this set for the same name. A validator narrower than /// the store's reads a credential out and then refuses to land it. diff --git a/hive-screen-mcp/src/main.rs b/hive-screen-mcp/src/main.rs index 481e4170..47b83501 100644 --- a/hive-screen-mcp/src/main.rs +++ b/hive-screen-mcp/src/main.rs @@ -1,7 +1,7 @@ //! `hive-screen-mcp` — stdio MCP bridge for GUI agents. //! //! Provides screenshot, keyboard-input, and mouse tools for agents running a -//! Weston Wayland compositor (`hyperhive.gui.enable = true`). Each tool: +//! Weston Wayland compositor (`services.hyperhive.agent.gui.enable = true`). Each tool: //! //! - `screenshot` → `grim` (always available with gui enabled) //! - `type_text` → `wtype` (Wayland virtual-keyboard protocol, no daemon) @@ -18,7 +18,7 @@ //! //! `WAYLAND_DISPLAY` and `XDG_RUNTIME_DIR` are injected globally by the //! `weston-vnc` module. `HIVE_GUI_VNC_PORT` is set by the harness service -//! when `hyperhive.gui.enable = true` (defaults to 5900 if absent). +//! when `services.hyperhive.agent.gui.enable = true` (defaults to 5900 if absent). use anyhow::Result; use rmcp::{ @@ -203,7 +203,7 @@ impl ScreenMcp { description = "Take a screenshot of the agent's Wayland display and save it as a PNG \ file under `/tmp/`. Returns the path to the saved file — pass that path to the \ `Read` tool to view the image visually. Requires `grim` and a live \ - `WAYLAND_DISPLAY` (provided automatically when `hyperhive.gui.enable = true`)." + `WAYLAND_DISPLAY` (provided automatically when `services.hyperhive.agent.gui.enable = true`)." )] async fn screenshot(&self) -> String { let ms = std::time::SystemTime::now() @@ -222,7 +222,7 @@ impl ScreenMcp { injection). Supports arbitrary Unicode. For special keys (Enter, Tab, Escape, \ function keys, arrow keys) or modifier combos, use `key_press` instead. \ Requires a live `WAYLAND_DISPLAY` (provided automatically when \ - `hyperhive.gui.enable = true`)." + `services.hyperhive.agent.gui.enable = true`)." )] async fn type_text(&self, Parameters(args): Parameters) -> String { cmd_result(run_cmd("wtype", &[&args.text]).await) @@ -236,7 +236,7 @@ impl ScreenMcp { with `+`-separated parts where all but the last are modifiers (`ctrl+c`, \ `ctrl+shift+t`, `super+l`, `alt+F4`). Modifier names: `ctrl`, `shift`, `alt`, \ `super`. Requires a live `WAYLAND_DISPLAY` (provided automatically when \ - `hyperhive.gui.enable = true`)." + `services.hyperhive.agent.gui.enable = true`)." )] async fn key_press(&self, Parameters(args): Parameters) -> String { // Parse "mod1+mod2+key" into: -M mod1 -M mod2 -k key -m mod2 -m mod1 diff --git a/hive-subagent-mcp/src/mcp_config.rs b/hive-subagent-mcp/src/mcp_config.rs index 3818cdcd..1e5198c6 100644 --- a/hive-subagent-mcp/src/mcp_config.rs +++ b/hive-subagent-mcp/src/mcp_config.rs @@ -1,5 +1,5 @@ //! Builds a subagent's own filtered `--mcp-config`: only -//! `hyperhive.extraMcpServers` entries with `availableToSubagents = true` +//! `services.hyperhive.agent.extraMcpServers` entries with `availableToSubagents = true` //! (see `hive_agent_sock::extra_mcp::ExtraMcpServer::available_to_subagents`) ever reach //! a subagent's claude invocation, plus this daemon's own two-tool signal //! surface. Everything else — the built-in hyperhive diff --git a/hive-subagent-mcp/src/session.rs b/hive-subagent-mcp/src/session.rs index 071a3064..ee8bb91c 100644 --- a/hive-subagent-mcp/src/session.rs +++ b/hive-subagent-mcp/src/session.rs @@ -814,7 +814,7 @@ fn validate_name(name: &str) -> anyhow::Result<()> { .map_err(|e| anyhow::anyhow!("invalid subagent name {name:?}: {e}")) } -/// Env var carrying the nix-configured `hyperhive.availableModels` as a +/// Env var carrying the nix-configured `services.hyperhive.agent.availableModels` as a /// comma-separated list, forwarded onto this daemon's unit by /// `nix/agent-modules/mcp.nix` the same way `HIVE_TOOL_GROUPS` is. const AVAILABLE_MODELS_ENV: &str = "HIVE_AVAILABLE_MODELS"; diff --git a/swarm-controller/src/matrix_account.rs b/swarm-controller/src/matrix_account.rs index 1893573c..971c97c6 100644 --- a/swarm-controller/src/matrix_account.rs +++ b/swarm-controller/src/matrix_account.rs @@ -100,7 +100,7 @@ pub struct PutMatrixAccountRequest { /// flight, with nowhere to reconstruct it from on a re-delivery. /// /// Optional in token mode (omitted means "resolve to the agent's own - /// `hyperhive.matrix.url` on the hive side" — this route never needs to + /// `services.hyperhive.agent.matrix.url` on the hive side" — this route never needs to /// know it itself for a blind store). **Required** in password mode: /// logging in needs somewhere to log in against, and unlike token mode /// there is no hive-side fallback to defer to. @@ -163,7 +163,7 @@ pub async fn put_matrix_account( let secret_path = matrix::account_path(&agent, &account) .map_err(|e| error_problem(StatusCode::BAD_REQUEST, &e.to_string()))?; // `main` is the hive-internal account `nix/agent-modules/matrix.nix` - // synthesizes per agent from `hyperhive.matrix.url` — the schema there + // synthesizes per agent from `services.hyperhive.agent.matrix.url` — the schema there // forbids declaring a key by that name for the same reason this route // refuses to write one: an extra account literally named `main` would // not overwrite the real one (it lands at a different token-file suffix) @@ -174,7 +174,7 @@ pub async fn put_matrix_account( return Err(error_problem( StatusCode::BAD_REQUEST, "'main' is the hive-internal account, synthesized per agent from \ - hyperhive.matrix.url — it cannot be set through this route.", + services.hyperhive.agent.matrix.url — it cannot be set through this route.", )); }