From c280664d7424188a7b0c4757eadb19e852e9dc54 Mon Sep 17 00:00:00 2001 From: damocles Date: Wed, 9 Sep 2026 18:15:31 +0200 Subject: [PATCH] nix: wire the independent hive-subagent-daemon systemd unit and MCP server --- flake.nix | 1 + hive-c0re/src/dashboard/journal.rs | 1 + nix/agent-modules/default.nix | 3 +- nix/agent-modules/mcp.nix | 71 ++++++++++++++++++++++++++++++ nix/agent-modules/packages.nix | 2 +- nix/packages/default.nix | 1 + 6 files changed, 77 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index 9e6d2ad4..a1f0b566 100644 --- a/flake.nix +++ b/flake.nix @@ -93,6 +93,7 @@ hive-matrix-daemon hive-metric hive-screen-mcp + hive-subagent-daemon assets frontend reference-docs diff --git a/hive-c0re/src/dashboard/journal.rs b/hive-c0re/src/dashboard/journal.rs index 3898b5c9..f09908c5 100644 --- a/hive-c0re/src/dashboard/journal.rs +++ b/hive-c0re/src/dashboard/journal.rs @@ -110,6 +110,7 @@ pub(super) async fn get_journal( "hive-agent.service", "hive-mcp-http.service", "hive-bash-daemon.service", + "hive-subagent-daemon.service", "hive-matrix-daemon.service", ]; let unit = if u.ends_with(".service") { diff --git a/nix/agent-modules/default.nix b/nix/agent-modules/default.nix index 9cdbf52f..94f5e680 100644 --- a/nix/agent-modules/default.nix +++ b/nix/agent-modules/default.nix @@ -203,7 +203,8 @@ # unreachable from inside a container — wrapped with # `wireguard-tools` for `hivectl wg`). The daemon/harness/MCP bins # the harness execs (hive-agent{,-mcp}, hive-bash-daemon, - # hive-matrix-daemon, hive-matrix-mcp) are wired via their own + # hive-subagent-daemon, hive-matrix-daemon, hive-matrix-mcp) are + # wired via their own # ExecStart/command lines in the sibling modules — they don't need # to be on PATH too. Only this one is actually looked up on PATH # by claude/shell code inside the container: diff --git a/nix/agent-modules/mcp.nix b/nix/agent-modules/mcp.nix index 6ffc80c5..920d6f34 100644 --- a/nix/agent-modules/mcp.nix +++ b/nix/agent-modules/mcp.nix @@ -197,6 +197,23 @@ in ''; }; + options.hyperhive.mcp.subagentHttpPort = lib.mkOption { + type = lib.types.port; + default = 8793; + example = 8794; + description = '' + Loopback port `hive-subagent-daemon` serves its MCP tools + (`start`/`continue`/`interrupt`) on. Independent daemon (own crate, + `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 + 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. + ''; + }; + config = { # Assert the transport-specific required field is actually set — # `command`/`url` are both `nullOr` so the submodule schema stays @@ -225,6 +242,17 @@ in allowedTools = [ "*" ]; }; + # Auto-inject the subagent MCP server — default-on for every agent for + # now (operator's call: "default on for now, should be a capability + # 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 { + type = "http"; + url = "http://127.0.0.1:${toString config.hyperhive.mcp.subagentHttpPort}/mcp"; + allowedTools = [ "*" ]; + }; + environment.etc."hyperhive/extra-mcp.json".text = builtins.toJSON config.hyperhive.extraMcpServers; environment.etc."hyperhive/send-allow.json".text = @@ -278,6 +306,49 @@ in }; }; + # Subagent task runner daemon — independent of `hive-bash-daemon` (own + # crate, own process): spawns nested claude sessions on request, serves + # the `start`/`continue`/`interrupt` MCP tools directly over + # streamable-http on `hyperhive.mcp.subagentHttpPort`. No task files — + # this daemon's only state is an in-memory map of currently-running + # processes, live only as long as the process is (see + # `hive-subagent-mcp/src/session.rs`'s module doc); a restart stops + # whatever's running, the actual claude session survives independently. + systemd.services.hive-subagent-daemon = { + description = "subagent task runner + MCP daemon for hive-subagent"; + wantedBy = [ "multi-user.target" ]; + before = [ "hive-agent.service" ]; + # A subagent task runs its own nested `claude` invocation (via + # `hive-claude`), which needs to resolve `claude` itself off PATH — + # same reasoning as `hive-bash-daemon`'s `path` above, even though + # this daemon never shells out to `bash -c` directly. + path = [ + "/run/wrappers" + "/run/current-system/sw" + ]; + environment = { + # Same in-agent todo socket as `hive-bash-daemon` — both push task + # todos to the one harness socket. Must match the harness's + # HIVE_AGENT_SOCKET (agent-service.nix). + HIVE_AGENT_SOCKET = "/run/hive-agent/${userName}/agent.sock"; + RUST_LOG = "info"; + # 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}"; + SyslogIdentifier = "hive-subagent-daemon"; + # `always`, same reasoning as `hive-bash-daemon`: the MCP tool is + # served in-process, so a down window is total loss of + # `spawn_subagent` with no stdio fallback. + Restart = "always"; + RestartSec = 3; + User = userName; + Group = userName; + }; + }; + # Persistent streamable-http MCP daemon for the built-in hyperhive # surface — the *sole* transport for that surface; always # wired. Long-lived so claude reconnects to the stable URL each turn diff --git a/nix/agent-modules/packages.nix b/nix/agent-modules/packages.nix index 0e4fe36d..cc14d826 100644 --- a/nix/agent-modules/packages.nix +++ b/nix/agent-modules/packages.nix @@ -11,7 +11,7 @@ description = '' hyperhive package outputs consumed by the harness modules: the per-binary daemon/CLI packages (`hive-agent`, `hive-agent-mcp`, - `hive-bash-daemon`, + `hive-bash-daemon`, `hive-subagent-daemon`, `hive-forge`, `hive-forge-notify`, `hive-github-notify`, `hive-matrix-daemon`, `hive-metric`, `hive-screen-mcp`) plus the `assets`, `frontend`, diff --git a/nix/packages/default.nix b/nix/packages/default.nix index d3241997..38d41522 100644 --- a/nix/packages/default.nix +++ b/nix/packages/default.nix @@ -34,6 +34,7 @@ let hive-agent = "hyperhive in-container agent harness serve loop"; hive-agent-mcp = "hyperhive agent-surface MCP server"; hive-bash-daemon = "hyperhive per-agent bash-task runner daemon (serves its MCP tools directly over streamable-http)"; + hive-subagent-daemon = "hyperhive per-agent claude-subagent task runner daemon (serves its MCP tools directly over streamable-http)"; hive-matrix-daemon = "hyperhive per-agent matrix-sdk daemon (serves its MCP tools directly over streamable-http)"; hive-metric = "hyperhive agent-emitted custom metrics CLI"; hive-screen-mcp = "hyperhive screen MCP bridge (screenshot + input for GUI agents)";