docs/agent-hierarchy.md: extract harness systemd unit shape (#718 batch 4)
Move the harness systemd unit rationale (role-driven unit name, manager-only forge defaults, PATH wrapper-dir trick, env vars, standalone-eval fallbacks, RuntimeDirectory + User= reasoning) from `nix/templates/harness-base.nix` to a new `docs/agent-hierarchy.md::Harness systemd unit shape (per-role)` section. In-code comments trim to short purpose statements + pointers; the PATH /bin auto-append behaviour is already documented in docs/gotchas.md, so the harness file just cross-refs both. `description = ''…''` blocks (operator-facing options docs) preserved per iris #718. `nix flake check` clean; `nix fmt` clean (after formatter pass).
This commit is contained in:
parent
35a7ff03b7
commit
8a50f36c0c
2 changed files with 121 additions and 93 deletions
|
|
@ -206,6 +206,99 @@ a full 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)
|
||||
|
||||
One harness binary (`hive`), one `harness-base.nix` template, two
|
||||
systemd units depending on `hyperhive.role`:
|
||||
|
||||
- `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 hardcoded `HIVE_PORT` / `HIVE_LABEL` env values:
|
||||
|
||||
- `HIVE_PORT = "8875"` — FNV-1a(`"hm1nd"`) % 900 + 8100, matching
|
||||
`lifecycle::agent_web_port`. Sub-agents have the same shape via
|
||||
the meta-flake-generated `applied/<name>/flake.nix`.
|
||||
- `HIVE_LABEL = "hm1nd"` — container 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.
|
||||
|
||||
### Environment variables set on the unit
|
||||
|
||||
- `HOME = /home/<userName>` — systemd defaults `HOME` to `/` for
|
||||
services without `User=` set; with the per-agent user (#658) the
|
||||
harness needs the right home so claude finds its bind-mounted
|
||||
`~/.claude/` session dir.
|
||||
- `HIVE_STATIC_DIR = <mergedDist>` — `tower_http::ServeDir` root for
|
||||
the per-agent web UI; merged dist = agent default + every
|
||||
`hyperhive.frontend.extraFiles` overlay.
|
||||
- `HIVE_ASSETS_DIR = pkgs.hyperhive-assets/share/hyperhive` — set
|
||||
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)
|
||||
|
||||
```nix
|
||||
path = [ "/run/wrappers" "/run/current-system/sw" ];
|
||||
```
|
||||
|
||||
`/run/wrappers` comes first so setuid wrappers (notably `sudo`)
|
||||
resolve before bare nix-store binaries. NixOS's
|
||||
`systemd.services.<unit>.path` appends `/bin` to every entry via
|
||||
`lib.makeBinPath`; passing `/run/wrappers/bin` directly produces
|
||||
`/run/wrappers/bin/bin` which doesn't exist (`docs/gotchas.md::
|
||||
systemd.services.*.path appends /bin to every entry`). Post-#658
|
||||
when the harness runs as the per-agent user this matters: without
|
||||
the wrapper dir on PATH, `sudo` resolves to the un-setuid nix-store
|
||||
binary and rejects with `must be owned by uid 0 and have the setuid
|
||||
bit set` regardless of `hyperhive.user.passwordlessSudo`.
|
||||
|
||||
### `serviceConfig` highlights
|
||||
|
||||
- `ExecStart = pkgs.hyperhive/bin/hive serve` — single binary,
|
||||
surface picked from `HIVE_ROLE`.
|
||||
- `Restart = on-failure`, `RestartSec = 2` — keeps the harness
|
||||
resilient across transient crashes without thundering retries.
|
||||
- `RuntimeDirectory = "hive-config"` → `/run/hive-config/` owned by
|
||||
`User=`, auto-cleared on stop. The harness writes regenerated
|
||||
`claude-{mcp-config,settings,system-prompt}` files there
|
||||
(`paths::config_dir`). Deliberately separate from `/run/hive`,
|
||||
which the host bind-mounts in root-owned and which holds
|
||||
hive-c0re's `mcp.sock` (#658 fixup).
|
||||
- `User = Group = userName` — drops root inside the container; sudo
|
||||
is the explicit escalation surface
|
||||
(`hyperhive.user.passwordlessSudo`).
|
||||
|
||||
## Cross-references
|
||||
|
||||
- Milestone: [#361 "Agent privileges and sub-agents"](http://localhost:3000/hyperhive/hyperhive/issues/361)
|
||||
|
|
|
|||
Loading…
Reference in a new issue