docs(turn-loop): move harness systemd unit shape out of agent-roster.md
Moves the "Harness systemd unit shape" section from docs/agent-lifecycle/agent-roster.md into docs/turn-loop/README.md: it describes the per-agent harness systemd unit (env vars, PATH wiring, serviceConfig), which is turn-loop material, not roster material. Fixes two facts while moving: the ExecStart package is `hive-agent`, not `hyperhive` (no package by that name exists); and `ruth.nix` doesn't set any forge subscription default — it only defaults `services.hyperhive.agent.docs.enable`. Updates the inbound pointers in docs/turn-loop/config.md and the module comment at nix/agent-modules/agent-service.nix. Refs #3902
This commit is contained in:
parent
46f1f3cbdb
commit
2b2608a491
4 changed files with 75 additions and 68 deletions
|
|
@ -136,69 +136,6 @@ through it the same way everywhere.
|
|||
None of the above is a stable interface — treat the module doc
|
||||
comments as the source of truth for exactly which checks exist today.
|
||||
|
||||
## Harness systemd unit shape
|
||||
|
||||
One harness serve binary (`hive-agent`, with its `hive-agent-mcp`
|
||||
sibling), one shared `nix/agent-modules/` tree, one service unit
|
||||
(`systemd.services.hive-agent`) for all agents. No separate manager
|
||||
service name or role distinction exists in the harness — privilege
|
||||
differences live server-side in the broker socket (which tool groups
|
||||
and manager-surface calls each agent receives).
|
||||
|
||||
`agent.nix` and `ruth.nix` both import the shared `nix/agent-modules/`.
|
||||
`ruth.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
|
||||
|
||||
- `HOME = /home/<userName>` — systemd defaults `HOME` to `/` for
|
||||
services without `User=` set; with the per-agent user 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
|
||||
`services.hyperhive.agent.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.
|
||||
|
||||
### `PATH` setup (the wrapper-dir trick)
|
||||
|
||||
```nix
|
||||
path = [ "/run/wrappers" "/run/current-system/sw" ];
|
||||
```
|
||||
|
||||
<!-- vale write-good.Passive = NO -->
|
||||
|
||||
`/run/wrappers` (not `/run/wrappers/bin`) comes first so setuid
|
||||
wrappers — notably `sudo` — resolve before bare nix-store binaries; see
|
||||
[`docs/process/gotchas.md`](../process/gotchas.md) ("`systemd.services.*.path` appends
|
||||
`/bin` to every entry") for why the trailing `/bin` matters in
|
||||
general. It's load-bearing here because the harness runs as the
|
||||
per-agent user: without the wrapper dir on `PATH`, `sudo` resolves to
|
||||
the non-setuid nix-store binary and every
|
||||
`services.hyperhive.agent.user.passwordlessSudo` grant fails with "must be owned by
|
||||
uid 0 and have the setuid bit set."
|
||||
|
||||
<!-- vale write-good.Passive = YES -->
|
||||
|
||||
### `serviceConfig` highlights
|
||||
|
||||
- `ExecStart = pkgs.hyperhive/bin/hive-agent` — same binary for every
|
||||
agent.
|
||||
- `Restart = on-failure`, `RestartSec = 2` — keeps the harness
|
||||
resilient across transient crashes without thundering retries.
|
||||
- `RuntimeDirectory = "hive-config"` → `/run/hive-config/` owned by
|
||||
`User=`, autocleared 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`.
|
||||
- `User = Group = userName` — drops root inside the container; sudo is
|
||||
the explicit escalation surface (`services.hyperhive.agent.user.passwordlessSudo`).
|
||||
|
||||
## Cross-references
|
||||
|
||||
- Milestone: "Agent privileges and sub-agents" (tracked internally)
|
||||
|
|
|
|||
|
|
@ -170,6 +170,73 @@ those drive the next one is strictly better than parking in-process:
|
|||
it checkpoints the session and observes wakes that only reach the
|
||||
harness between turns.
|
||||
|
||||
## Harness systemd unit shape
|
||||
|
||||
One harness serve binary (`hive-agent`, with its `hive-agent-mcp`
|
||||
sibling), one shared `nix/agent-modules/` tree, one service unit
|
||||
(`systemd.services.hive-agent`) for all agents. Privilege
|
||||
differences live server-side in the broker socket (which tool groups
|
||||
and manager-surface calls each agent receives).
|
||||
|
||||
`agent.nix` and `ruth.nix` both import the shared `nix/agent-modules/`.
|
||||
`ruth.nix` additionally defaults
|
||||
`services.hyperhive.agent.docs.enable` to `true`, so the root/manager
|
||||
gets the hyperhive reference docs readable at `$HIVE_DOCS_DIR` by
|
||||
default; other agents opt in per `agent.nix` (see
|
||||
[config.md](config.md), "Reference docs").
|
||||
|
||||
### Environment variables set on the unit
|
||||
|
||||
- `HOME = /home/<userName>` — systemd defaults `HOME` to `/` for
|
||||
services without `User=` set; with the per-agent user 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
|
||||
`services.hyperhive.agent.frontend.extraFiles` overlay.
|
||||
- `HIVE_ASSETS_DIR = "${config.services.hyperhive.agent.packages.assets}/share/hyperhive"`
|
||||
(`nix/agent-modules/agent-service.nix:480`; `packages.assets` resolves
|
||||
to the `hyperhive-assets` derivation) — set directly on the unit,
|
||||
**not** via `environment.variables`, because the latter only
|
||||
populates `/etc/profile` which systemd services don't inherit.
|
||||
|
||||
### `PATH` setup (the wrapper-dir trick)
|
||||
|
||||
```nix
|
||||
path = [ "/run/wrappers" "/run/current-system/sw" ];
|
||||
```
|
||||
|
||||
<!-- vale write-good.Passive = NO -->
|
||||
|
||||
`/run/wrappers` (not `/run/wrappers/bin`) comes first so setuid
|
||||
wrappers — notably `sudo` — resolve before bare nix-store binaries; see
|
||||
[`docs/process/gotchas.md`](../process/gotchas.md) ("`systemd.services.*.path` appends
|
||||
`/bin` to every entry") for why the trailing `/bin` matters in
|
||||
general. It's load-bearing here because the harness runs as the
|
||||
per-agent user: without the wrapper dir on `PATH`, `sudo` resolves to
|
||||
the non-setuid nix-store binary and every
|
||||
`services.hyperhive.agent.user.passwordlessSudo` grant fails with "must be owned by
|
||||
uid 0 and have the setuid bit set."
|
||||
|
||||
<!-- vale write-good.Passive = YES -->
|
||||
|
||||
### `serviceConfig` highlights
|
||||
|
||||
- `ExecStart = "${config.services.hyperhive.agent.packages.hive-agent}/bin/${binary}"`
|
||||
(`nix/agent-modules/agent-service.nix:535`); `packages.hive-agent` is
|
||||
wired to the flake's `hyperhive.packages.<system>.hive-agent` output
|
||||
(`nix/agent-modules/packages.nix:8-22`) — same binary for every agent.
|
||||
- `Restart = on-failure`, `RestartSec = 2` — keeps the harness
|
||||
resilient across transient crashes without thundering retries.
|
||||
- `RuntimeDirectory = "hive-config"` → `/run/hive-config/` owned by
|
||||
`User=`, autocleared 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`.
|
||||
- `User = Group = userName` — drops root inside the container; sudo is
|
||||
the explicit escalation surface (`services.hyperhive.agent.user.passwordlessSudo`).
|
||||
|
||||
## Sub-pages
|
||||
|
||||
The rest lives alongside this page, in three topic files:
|
||||
|
|
|
|||
|
|
@ -57,10 +57,13 @@ Set to `false` for agents that should be strictly unprivileged.
|
|||
Any tool invocation that needs root then fails loudly with the standard
|
||||
sudo rejection rather than silently succeeding — easier to audit.
|
||||
|
||||
`services.hyperhive.agent.user.uid`, `services.hyperhive.agent.user.gid`, and
|
||||
`services.hyperhive.agent.user.name` are the companion options; see
|
||||
`docs/agent-lifecycle/agent-roster.md` — "Harness systemd unit shape" for the full
|
||||
`user.*` surface.
|
||||
`services.hyperhive.agent.user.name` is the unix user the harness and
|
||||
its co-process daemons run as inside the container (default `"agent"`
|
||||
for a standalone evaluation; the meta flake rebinds it to the agent's
|
||||
own name). `services.hyperhive.agent.user.uid` / `.gid` are optional
|
||||
fixed UID/GID (`null` default lets NixOS assign one automatically from
|
||||
the normal-user range) — set them only when something outside the
|
||||
container needs a stable numeric id across a full destroy + recreate.
|
||||
|
||||
## Dashboard links
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue