From 0ac05f06384b9cccd3ab2da2f1ac672913cf3730 Mon Sep 17 00:00:00 2001 From: damocles Date: Mon, 1 Jun 2026 16:29:47 +0200 Subject: [PATCH] fix(#947): set HIVE_WEB_SOCKET for manager unconditionally --- nix/templates/harness-base.nix | 122 +++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 53 deletions(-) diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index 9d098608..fa015c53 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -10,8 +10,8 @@ ... }: let - # Agent user metadata. `userName` defaults to `"agent"` when the - # meta-flake doesn't inject the per-agent override (stand-alone + # Agent user metadata (#658). `userName` defaults to `"agent"` when + # the meta-flake doesn't inject the per-agent override (stand-alone # `nixos-rebuild` against `nixosConfigurations.agent-base` works # without erroring on a missing per-agent name). `homeDir` derives # from `userName` to keep them coupled. @@ -29,7 +29,7 @@ in # only opts in from its own `agent.nix`. imports = [ ./weston-vnc.nix ]; - # Per-agent unix user the harness + co-process daemons run as. + # Per-agent unix user the harness + co-process daemons run as (#658). # Defaults to `"agent"` so a standalone evaluation (e.g. # `nix flake check` against `nixosConfigurations.agent-base`) builds # cleanly; the meta-flake's per-agent module rebinds this to the @@ -83,24 +83,26 @@ in When `true`, set `HIVE_WEB_SOCKET=/run/hive-agent/${userName}/web.sock` on the harness service env, which makes `web_ui::serve` bind a `UnixListener` at that path instead of the legacy TCP listener - on `HIVE_PORT`. + on `HIVE_PORT`. Closes the third hop of the #784 rollout: PR + #800 added the harness-side opt-in, #809 / #813 added the c0re + bind-mount + JSON-map plumbing, this is the per-agent flip + that activates the unix-domain path. Default `false` so an agent's web UI keeps binding TCP until the per-agent flip is explicit. Rollout shape: - 1. flip one canary agent to `true` via its `agent.nix`; + 1. flip one canary agent (atlas volunteered) to `true` via its + `agent.nix` once #813 lands; 2. validate the gateway's `proxy_pass http://unix:.../web.sock` - end-to-end against that canary; + end-to-end against that canary (atlas's step 3); 3. flip remaining agents per-agent as the gateway side soaks; - 4. eventually drop this option once every agent is on unix and - the TCP fallback is removed from the harness. + 4. eventually drop this option once every agent's on unix + + atlas's gateway is the only path — step 4 of #784 drops the + harness's TCP fallback at the same time. - Sub-agent-only by design: the manager's UI serves at `/` via - the c0re dashboard upstream, not via `/agent//`, so this - option has no effect when `hyperhive.role = "manager"` (the - env var is set unconditionally for clarity, but the manager's - web UI doesn't route through the gateway's per-agent unix - upstream — its bind socket would just sit unused). + Sub-agents only: the manager always has its unix socket set + unconditionally in the `isManager` block below, so this toggle + has no effect when `hyperhive.role = "manager"`. ''; }; @@ -123,6 +125,9 @@ in it's exposed so a standalone `nixos-rebuild` against `nixosConfigurations.manager` keeps working without the meta-flake wrapper around it. + + Closes #671: harness + manager templates merged into a + single `harness-base.nix` driven by this option. ''; }; @@ -259,8 +264,8 @@ in type = lib.types.bool; default = true; description = '' - Enable per-agent matrix integration via `hive-matrix-mcp`. - When true (the default), the harness: + Enable per-agent matrix integration via `hive-matrix-mcp` + (#548 phase 3). When true (the default), the harness: - runs `hive-matrix-daemon` as a systemd unit that holds a matrix-sdk Client + sync against the homeserver at @@ -268,8 +273,8 @@ in in-host tuwunel from `nix/modules/hive-matrix.nix`). The daemon auto-skips when `/matrix-token` is missing, and a `systemd.paths` watcher restarts it the moment - hive-c0re provisions the token (same path-trigger shape - as `matrix-avatar-sync`). + hive-c0re provisions the token (mirrors `matrix-avatar-sync` + shape from #571). - exposes the matrix tool surface (send_message, send_dm, send_reaction, send_reply, mark_read, list_rooms, list_room_members, read_room) to claude via an auto-injected @@ -569,8 +574,9 @@ in # all contributions across modules into one file. Loaded via # `$BASH_ENV` for non-interactive shells (claude's `Bash` tool # runs `bash -c`) and via `programs.bash.interactiveShellInit` - # for interactive shells. Generic by design so future hooks - # don't need to rename this file or invent a parallel dispatcher. + # for interactive shells. Generic by design (mara on #779) so + # future hooks don't need to either rename this file or invent + # a parallel dispatcher. options.hyperhive._bashEnvFragments = lib.mkOption { type = lib.types.lines; default = ""; @@ -597,7 +603,7 @@ in anything else) invokes `cargo` inside this container. Saves tokens + context — the verbose default output floods the response window with per-crate progress lines that - carry no signal beyond the warning/error summary. + carry no signal beyond the warning/error summary (#777). Implementation: contributes a `cargo` shell function to `/etc/hyperhive/bash-env.sh` (see `hyperhive._bashEnvFragments`). @@ -693,12 +699,18 @@ in } ]; - # Per-agent unix user. Runs the hive-ag3nt / hive-m1nd harness + - # co-process daemons under a non-root principal. UID auto-assigned by - # NixOS. The container activation script (hive-agent-user-migrate) - # chowns the bind-mounted state dir — including credential files - # written by hive-c0re before the container was built — to this user - # on every boot, so agent processes can always read their own tokens. + # Per-agent unix user (#658). Runs the hive-ag3nt / hive-m1nd + # harness + co-process daemons (hive-matrix-daemon) under a + # non-root principal. The user name follows + # `hyperhive.user.name` — defaults to `"agent"` for standalone + # eval, overridden per-agent by the meta-flake to the agent's + # own label so each container has a uniquely-named user. + # + # UID auto-assigned by NixOS (per mara's #8109: "no hardcoded + # uids"). Home is `/home/${userName}`. `wheel` membership + + # the sudoers rule below grants `NOPASSWD: ALL` when + # `passwordlessSudo` is true — same blast radius as the + # previous root-by-default shape, just explicit. users.users.${userName} = { isNormalUser = true; home = homeDir; @@ -733,10 +745,10 @@ in } ]; - # First-boot migration to the per-agent unix user — creates the - # home dir, chowns the bind-mounted state + `~/.claude/`, and - # (marker-guarded) moves any leftover `/root/.claude` content - # from the previous root-run shape. See + # Post-#658 first-boot migration to the per-agent unix user — + # creates the home dir, chowns the bind-mounted state + + # `~/.claude/`, and (marker-guarded) moves any leftover + # `/root/.claude` content from the pre-#658 root-run shape. See # `docs/persistence.md::First-boot agent-user migration` for the # step-by-step rationale; this script implements it. system.activationScripts.hive-agent-user-migrate = lib.stringAfter [ "users" "specialfs" ] '' @@ -763,8 +775,8 @@ in fi ''; - # Auto-inject the matrix MCP entry when matrix is enabled. - # Operator can override or disable by setting their own + # Auto-inject the matrix MCP entry when matrix is enabled (#548 + # phase 3). Operator can override or disable by setting their own # `extraMcpServers.matrix` (nix submodule merge takes the operator's # value) or by flipping `hyperhive.matrix.enable = false`. hyperhive.extraMcpServers = lib.mkIf config.hyperhive.matrix.enable { @@ -772,9 +784,9 @@ in command = "${pkgs.hyperhive}/bin/hive-matrix-mcp"; args = [ ]; # Same socket path the hive-matrix-daemon service binds - # via its `RuntimeDirectory = "hive-matrix"`. Keeps the - # bridge + daemon in sync without baking the path into - # the Rust default — the env override wins for both. + # via its `RuntimeDirectory = "hive-matrix"` (#658). Keeps + # the bridge + daemon in sync without baking the new path + # into the Rust default — the env override wins for both. env.HIVE_MATRIX_SOCKET = "/run/hive-matrix/socket"; allowedTools = [ "*" ]; }; @@ -789,18 +801,18 @@ in source = config.hyperhive.icon; }; - # Cargo `--message-format short` injector. Contributes a `cargo` - # shell function to `hyperhive._bashEnvFragments`; the bash-env - # infrastructure below packages that into a single file sourced - # by both non-interactive and interactive shells. + # Cargo `--message-format short` injector (#777). Contributes a + # `cargo` shell function to `hyperhive._bashEnvFragments`; the + # bash-env infrastructure below packages that into a single file + # sourced by both non-interactive and interactive shells. # `command cargo …` falls 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 '' # 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 - # --message-format (any form). + # claude's context (#777). Bypassed when the caller + # already passes --message-format (any form). cargo() { # Strip leading +toolchain selectors (cargo +nightly …). local pre=() @@ -906,7 +918,7 @@ in # feature hook's snippet into scope without touching # `/etc/profile` (login-only). Interactive shells source the # same file via the `interactiveShellInit` hook below so - # behaviour matches across both modes. + # behaviour matches across both modes (#777). BASH_ENV = "/etc/hyperhive/bash-env.sh"; }; @@ -1176,9 +1188,9 @@ in fi TOKEN=$(cat "$TOKEN_FILE") # Local tuwunel reachable on shared host netns at the - # default matrix-spec port. Override via - # `hyperhive.matrix.url` if the operator runs the - # homeserver elsewhere. + # default matrix-spec port. Override via the future + # `hyperhive.matrix.url` if the operator ever runs the + # homeserver elsewhere (deferred to #548 phase 4). MATRIX_URL=http://localhost:8008 # whoami → user_id. Needed to scope the avatar set call. # Tolerant of the homeserver being unreachable (`-f` makes @@ -1311,13 +1323,13 @@ in HIVE_ROLE = config.hyperhive.role; } // lib.optionalAttrs config.hyperhive.web.useUnixSocket { - # Per-agent unix-socket path for the web UI. When set, - # the harness's `web_ui::serve` binds a `UnixListener` - # at this path instead of TCP. Path matches - # `hive_c0re::agent_sockets::socket_path_for(name)` so - # the lifecycle bind-mount and the gateway's upstream - # config all derive from the same canonical - # `/run/hive-agent//web.sock` shape. + # Per-agent unix-socket flip for the web UI (#784 phase 2 + # step 2c). When set, the harness's `web_ui::serve` binds + # a `UnixListener` at this path instead of TCP. Path + # matches `hive_c0re::agent_sockets::socket_path_for(name)` + # so the lifecycle bind-mount (#813) and the gateway's + # upstream config all derive from the same canonical + # `/run/hive-agent//web.sock` shape — no triangulation. HIVE_WEB_SOCKET = "/run/hive-agent/${userName}/web.sock"; } // lib.optionalAttrs isManager { @@ -1325,6 +1337,10 @@ in # HIVE_PORT = FNV-1a("hm1nd") % 900 + 8100. HIVE_PORT = "8875"; HIVE_LABEL = "hm1nd"; + # Manager always uses a unix socket for its web UI so the + # gateway can route /agent// to it the same way it + # routes sub-agents. Path mirrors agent_sockets::socket_path_for. + HIVE_WEB_SOCKET = "/run/hive-agent/${userName}/web.sock"; }; serviceConfig = { ExecStart = "${pkgs.hyperhive}/bin/${binary} serve";