diff --git a/CLAUDE.md b/CLAUDE.md index 4405782d..5fb6c70c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -328,11 +328,12 @@ nix/ `services.hyperhive.domain`; gateway serves `.well-known/matrix/{client,server}` for auto-discovery; federation on, e2ee deferred - templates/harness-base.nix shared harness for all containers; `hyperhive.role` - (`"agent"` | `"manager"`) drives service unit + forge - defaults; `hyperhive.model` option (HIVE_DEFAULT_MODEL) - templates/agent-base.nix thin role-setter (`hyperhive.role = "agent"`) - templates/manager.nix thin role-setter (`hyperhive.role = "manager"`) + templates/harness-base.nix shared harness for all containers; single + `hive-ag3nt` service unit; `hyperhive.model` + option (HIVE_DEFAULT_MODEL) + templates/agent-base.nix entry-point for sub-agent containers + templates/manager.nix entry-point for ruth; adds forge noise-reduction + defaults (keepSubscriptions=false etc.) templates/weston-vnc.nix optional `hyperhive.gui.enable` — weston + VNC backend systemd unit; writes /etc/hyperhive/gui.json (vnc_port + auth) for diff --git a/docs/agent-hierarchy.md b/docs/agent-hierarchy.md index 087326f6..2e3ce4b7 100644 --- a/docs/agent-hierarchy.md +++ b/docs/agent-hierarchy.md @@ -202,46 +202,19 @@ nspawn agent. Open questions, not yet wired: - Filesystem: share parent's `/state` RW, or a sub-dir? - Identity: distinct broker recipient name, or address the parent? -## Harness systemd unit shape (per-role) +## Harness systemd unit shape -One harness binary (`hive`), one `harness-base.nix` template, two -systemd units depending on `hyperhive.role`: +One harness binary (`hive`), one `harness-base.nix` template, one +service unit (`systemd.services.hive-ag3nt`) for all agents. There +is no longer a separate manager service name or role distinction in +the harness — privilege differences live server-side in the broker +socket (which tool groups and manager-surface calls each agent +receives). -- `agent-base.nix` (`role = "agent"`) → `systemd.services.hive-ag3nt` -- `manager.nix` (`role = "manager"`) → `systemd.services.hive-m1nd` - -The unit names diverge but the binary is the same. `HIVE_ROLE` env -var picks the surface at startup (agent vs manager); naming the -units after the historical per-role binaries keeps dashboard log -queries, ExecStartPre paths, and ancestor PR diffs working without a -rename cascade. - -### Manager-only defaults - -`harness-base.nix` flips these when `hyperhive.role == "manager"`, -via `lib.mkDefault` so any agent can invert if needed: - -- `hyperhive.forge.keepSubscriptions = false` -- `hyperhive.forge.skipNotifyReasons = [ "subscribed" "participating" ]` - -Skips the subscription / participation firehose so the manager's -inbox only carries direct mentions, reviews, and assignments. Sub- -agents keep the noisier defaults so they see anything aimed at the -repos they're working on. - -### Standalone-eval fallbacks - -`nixosConfigurations.manager` must build standalone (without the -meta-flake's per-agent flake.nix wrapper). For the manager unit -that means a hardcoded `HIVE_LABEL` env value: - -- `HIVE_LABEL = "ruth"` — logical agent name; matches what `meta.rs` - injects at deploy time. - -Real deploys never read these — `meta::render_flake` overrides them -via the generated wrapper. They exist so the manager -`nixosConfigurations` evaluates cleanly even outside the meta-flake -boundary. +`agent-base.nix` and `manager.nix` both import `harness-base.nix`. +`manager.nix` additionally sets forge defaults to suppress the +subscription/participation firehose so ruth's inbox stays focused +on direct mentions, reviews, and assignments. ### Environment variables set on the unit @@ -256,8 +229,6 @@ boundary. directly on the unit, **not** via `environment.variables`, because the latter only populates `/etc/profile` which systemd services don't inherit. -- `HIVE_ROLE = config.hyperhive.role` — picks the binary surface - (agent / manager) at startup. ### `PATH` setup (the wrapper-dir trick) @@ -278,8 +249,7 @@ bit set` regardless of `hyperhive.user.passwordlessSudo`. ### `serviceConfig` highlights -- `ExecStart = pkgs.hyperhive/bin/hive serve` — single binary, - surface picked from `HIVE_ROLE`. +- `ExecStart = pkgs.hyperhive/bin/hive serve` — single binary. - `Restart = on-failure`, `RestartSec = 2` — keeps the harness resilient across transient crashes without thundering retries. - `RuntimeDirectory = "hive-config"` → `/run/hive-config/` owned by diff --git a/docs/turn-loop.md b/docs/turn-loop.md index 88ac9b82..9e5955ad 100644 --- a/docs/turn-loop.md +++ b/docs/turn-loop.md @@ -5,8 +5,7 @@ claude has access to in return. ## The loop -Each agent harness (`hive serve`, role set via `$HIVE_ROLE` — always -`"agent"`, one binary) runs: +Each agent harness (`hive serve` — one binary for all agents) runs: 1. Long-poll `Recv` on its socket. The host-side broker (`broker.rs::recv_blocking_batch`) returns immediately if there's @@ -57,15 +56,11 @@ Each agent harness (`hive serve`, role set via `$HIVE_ROLE` — always ## Harness binary shape -One `hive` binary serves both roles. The split into +One `hive` binary for all agents. The earlier split into `hive-ag3nt` + `hive-m1nd` was collapsed because the privilege boundary lives server-side at the broker socket -(`/run/hive/mcp.sock`): an agent-flavor socket refuses -`ManagerRequest` calls regardless of who sends them, so there's no -escalation risk in shipping the same code to both. `main()` reads -`$HIVE_ROLE` (set by `harness-base.nix` from `hyperhive.role`; -defaults to `"agent"` for standalone `nix run` invocations) and -dispatches. +(`/run/hive/mcp.sock`): `ManagerRequest` calls are refused by the +standard agent socket regardless of who sends them. Three subcommands: diff --git a/hive-ag3nt/Cargo.toml b/hive-ag3nt/Cargo.toml index ed5de53b..655b5ab3 100644 --- a/hive-ag3nt/Cargo.toml +++ b/hive-ag3nt/Cargo.toml @@ -28,10 +28,8 @@ tracing-subscriber.workspace = true tempfile = "3" [[bin]] -# Unified harness binary: both `agent` and `manager` code paths live -# here; the binary picks its role at startup from `HIVE_ROLE` (set by -# `harness-base.nix` from `hyperhive.role`). The privilege boundary is -# enforced server-side at the socket, so shipping both surfaces in one -# binary is safe. See `docs/turn-loop.md::Harness binary shape`. +# Unified harness binary for all agents. Privilege boundary is +# enforced server-side at the socket (tool groups / manager surface). +# See `docs/turn-loop.md::Harness binary shape`. name = "hive" path = "src/bin/hive.rs" diff --git a/hive-ag3nt/src/bin/hive.rs b/hive-ag3nt/src/bin/hive.rs index b90494ca..a565b6ce 100644 --- a/hive-ag3nt/src/bin/hive.rs +++ b/hive-ag3nt/src/bin/hive.rs @@ -198,7 +198,6 @@ trait Surface { struct AgentSurface; impl Surface for AgentSurface { - async fn ack_turn(socket: &Path) { match client::request::<_, AgentResponse>(socket, &AgentRequest::AckTurn).await { Ok(AgentResponse::Ok) => {} diff --git a/hive-ag3nt/src/prompt.rs b/hive-ag3nt/src/prompt.rs index 8173fc7e..a86d9054 100644 --- a/hive-ag3nt/src/prompt.rs +++ b/hive-ag3nt/src/prompt.rs @@ -269,13 +269,7 @@ shared closer // Real template's first agent line — keeps the renderer // honest about the {label} / {operator_pronouns} pair the // harness already relied on. - let rendered = render( - &PRODUCTION_TEMPLATE, - "alice", - "they/them", - None, - None, - ); + let rendered = render(&PRODUCTION_TEMPLATE, "alice", "they/them", None, None); assert!(rendered.contains("hyperhive agent `alice`")); assert!(rendered.contains("**they/them** pronouns")); assert!(!rendered.contains("{label}")); @@ -285,13 +279,7 @@ shared closer #[test] fn render_no_role_markers_in_output() { // No raw role markers should survive into the rendered prompt. - let rendered = render( - &PRODUCTION_TEMPLATE, - "alice", - "she/her", - None, - None, - ); + let rendered = render(&PRODUCTION_TEMPLATE, "alice", "she/her", None, None); assert!(!rendered.contains("