nix/templates: merge manager + agent harness into harness-base.nix (#671)
mara on #671: "manager should not be as special anymore." Single `harness-base.nix` now declares the harness systemd unit + the manager-only forge defaults, driven by a new `hyperhive.role` option (`"agent"` | `"manager"`, default `"agent"`). The two child templates collapse to thin role-setters. Mechanics: - `hyperhive.role = "agent"` → `systemd.services.hive-ag3nt` running `hive-ag3nt serve`, default forge notification surface. - `hyperhive.role = "manager"` → `systemd.services.hive-m1nd` running `hive-m1nd serve`, forge `keepSubscriptions = false` + `skipNotifyReasons = [ "subscribed" "participating" ]` (mentions- only inbox), plus standalone-eval fallbacks `HIVE_PORT = "8000"` + `HIVE_LABEL = "hm1nd"` (meta.rs overrides via the generated `applied/hm1nd/flake.nix`). `agent-base.nix` (62 → 9 lines) and `manager.nix` (79 → 18 lines) are now thin shims that just set the role and import `harness-base.nix`. External surface unchanged: `nixosModules.{agent-base, manager}` + `nixosConfigurations.{agent-base, manager}` still resolve identically. meta.rs's role selection (`if isManager then hyperhive.nixosConfigurations.manager else hyperhive.nixosConfigurations.agent-base`) keeps working without edits. Verified via `nix eval`: - agent-base: role="agent", services=["hive-ag3nt"], forge.keepSubscriptions=true - manager: role="manager", services=["hive-m1nd"], forge.keepSubscriptions=false, forge.skipNotifyReasons=["subscribed","participating"], ExecStart=hive-m1nd/bin Closes #671.
This commit is contained in:
parent
c73c8389ec
commit
0951cd1b3d
1 changed files with 111 additions and 0 deletions
|
|
@ -75,6 +75,31 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
options.hyperhive.role = lib.mkOption {
|
||||||
|
type = lib.types.enum [
|
||||||
|
"agent"
|
||||||
|
"manager"
|
||||||
|
];
|
||||||
|
default = "agent";
|
||||||
|
example = "manager";
|
||||||
|
description = ''
|
||||||
|
Whether this container runs as a sub-agent (`"agent"`, the
|
||||||
|
default — invokes `hive-ag3nt serve`) or as the swarm's
|
||||||
|
manager (`"manager"` — invokes `hive-m1nd serve` and
|
||||||
|
defaults the forge notification surface to mentions-only).
|
||||||
|
|
||||||
|
meta.rs flips this to `"manager"` for the manager container
|
||||||
|
and leaves it at the default for every sub-agent. Agents
|
||||||
|
and `agent.nix` files don't normally touch this option;
|
||||||
|
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.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
options.hyperhive.model = lib.mkOption {
|
options.hyperhive.model = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = "haiku";
|
default = "haiku";
|
||||||
|
|
@ -1191,6 +1216,92 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Manager-only forge defaults (#671): skip the
|
||||||
|
# subscription/participation firehose so the manager's inbox
|
||||||
|
# only carries direct mentions, reviews, and assignments. Sub-
|
||||||
|
# agents keep the noisier defaults (`keepSubscriptions = true`,
|
||||||
|
# `skipNotifyReasons = [ ]`). `mkDefault` so any agent that
|
||||||
|
# wants to invert it can.
|
||||||
|
hyperhive.forge = lib.mkIf (config.hyperhive.role == "manager") {
|
||||||
|
keepSubscriptions = lib.mkDefault false;
|
||||||
|
skipNotifyReasons = lib.mkDefault [
|
||||||
|
"subscribed"
|
||||||
|
"participating"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Harness systemd unit. Role-driven so the same `harness-base.nix`
|
||||||
|
# covers both `nixosConfigurations.agent-base` (`hive-ag3nt serve`)
|
||||||
|
# and `nixosConfigurations.manager` (`hive-m1nd serve`) without a
|
||||||
|
# second template file (#671). Per-agent HIVE_PORT / HIVE_LABEL
|
||||||
|
# come from the meta-flake's generated `applied/<name>/flake.nix`;
|
||||||
|
# the manager has hardcoded fallbacks here so `nixosConfigurations.manager`
|
||||||
|
# still builds standalone.
|
||||||
|
systemd.services.${if config.hyperhive.role == "manager" then "hive-m1nd" else "hive-ag3nt"} =
|
||||||
|
let
|
||||||
|
isManager = config.hyperhive.role == "manager";
|
||||||
|
binary = if isManager then "hive-m1nd" else "hive-ag3nt";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
description = "${binary}${lib.optionalString isManager " manager"} harness";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
after = [ "network.target" ];
|
||||||
|
# systemd units get a minimal PATH by default and don't inherit
|
||||||
|
# `environment.systemPackages`. Pointing at `/run/current-system/sw`
|
||||||
|
# gives the harness (and any tools claude shells out to via Bash)
|
||||||
|
# access to everything declared in `systemPackages` — including
|
||||||
|
# anything an agent adds to its own `agent.nix` — without having
|
||||||
|
# to touch the service definition.
|
||||||
|
path = [ "/run/current-system/sw" ];
|
||||||
|
environment = {
|
||||||
|
SHELL = "${pkgs.bashInteractive}/bin/bash";
|
||||||
|
# `HOME` defaults to `/` for systemd services without a User=
|
||||||
|
# set. With #658 the harness runs as the agent user — set HOME
|
||||||
|
# explicitly so claude (which the harness spawns) finds its
|
||||||
|
# `~/.claude/` session dir at the bind-mounted location.
|
||||||
|
HOME = homeDir;
|
||||||
|
# Path to the merged agent static dist. The harness serves this
|
||||||
|
# via `tower_http::ServeDir` for any request it doesn't route to
|
||||||
|
# an API endpoint. `mergedDist` is the agent-default dist with
|
||||||
|
# `hyperhive.frontend.extraFiles` layered on top.
|
||||||
|
HIVE_STATIC_DIR = "${config.hyperhive.frontend.mergedDist}";
|
||||||
|
# Static runtime assets (branding + claude prompts). Set on the
|
||||||
|
# unit directly — `environment.variables` only populates
|
||||||
|
# /etc/profile, which systemd services don't inherit.
|
||||||
|
HIVE_ASSETS_DIR = "${pkgs.hyperhive-assets}/share/hyperhive";
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs isManager {
|
||||||
|
# Standalone-eval fallbacks for `nixosConfigurations.manager`.
|
||||||
|
# meta.rs overrides both via the per-agent generated
|
||||||
|
# `applied/hm1nd/flake.nix` (see `lifecycle::setup_applied`);
|
||||||
|
# the values here keep the container sensible if anyone
|
||||||
|
# evaluates the standalone config.
|
||||||
|
HIVE_PORT = "8000";
|
||||||
|
HIVE_LABEL = "hm1nd";
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = "${pkgs.hyperhive}/bin/${binary} serve";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 2;
|
||||||
|
# `/run/hive-config/` is a per-service runtime dir owned by
|
||||||
|
# the agent user (`User=` below), auto-cleared by systemd on
|
||||||
|
# stop. The harness writes its regenerated
|
||||||
|
# claude-{mcp-config,settings,system-prompt} files there
|
||||||
|
# (see `paths::config_dir`). Kept separate from `/run/hive`
|
||||||
|
# — that bind comes in root-owned from the host and holds
|
||||||
|
# hive-c0re's `mcp.sock` we only connect to (#658 fixup).
|
||||||
|
RuntimeDirectory = "hive-config";
|
||||||
|
# Run the harness as the per-agent user (#658). claude itself
|
||||||
|
# spawned by the harness then runs as that user too — drops
|
||||||
|
# root inside the container while sudo (`NOPASSWD: ALL` by
|
||||||
|
# default, see `hyperhive.user.passwordlessSudo`) keeps the
|
||||||
|
# previous root-by-default surface available explicitly for
|
||||||
|
# tools that need it.
|
||||||
|
User = userName;
|
||||||
|
Group = userName;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
system.stateVersion = "25.11";
|
system.stateVersion = "25.11";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue