252 lines
11 KiB
Nix
252 lines
11 KiB
Nix
# The hive-agent harness service itself, plus the per-agent knobs it
|
|
# reads from its environment: model selection, effort level,
|
|
# compaction watermark, and the extra reverse-proxies of the per-agent
|
|
# web UI.
|
|
{
|
|
pkgs,
|
|
lib,
|
|
config,
|
|
...
|
|
}:
|
|
let
|
|
userName = config.hyperhive.user.name;
|
|
homeDir = "/home/${userName}";
|
|
in
|
|
{
|
|
options.hyperhive.model = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "haiku";
|
|
example = "sonnet";
|
|
description = ''
|
|
Claude model for this agent. Sets the `HIVE_DEFAULT_MODEL`
|
|
environment variable; the harness applies it at boot and it takes
|
|
priority over any persisted runtime override. The operator can still
|
|
switch the model at runtime via the per-agent web UI — that choice
|
|
is tracked in the state dir for the current session but is reset by
|
|
any rebuild that changes this option.
|
|
|
|
Valid values are the short model names that `claude --model` accepts:
|
|
`"haiku"`, `"sonnet"`, `"opus"` (or any future identifier). Context
|
|
window sizes are looked up at runtime from the
|
|
`HIVE_CONTEXT_WINDOW_TOKENS_<KEY_UPPER>` env vars injected by the
|
|
meta flake; override sizes via `services.hyperhive.c0re.contextWindowTokens`
|
|
on the host.
|
|
'';
|
|
};
|
|
|
|
options.hyperhive.availableModels = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [
|
|
"haiku"
|
|
"sonnet"
|
|
"opus"
|
|
];
|
|
example = [
|
|
"sonnet"
|
|
"opus"
|
|
];
|
|
description = ''
|
|
Models offered in the per-agent web UI's model quick-picker. Rendered
|
|
into the `HIVE_AVAILABLE_MODELS` environment variable (comma-separated)
|
|
which the harness surfaces to the agent UI, so the picker lists exactly
|
|
these models instead of a hardcoded set.
|
|
|
|
Configure hive-wide by setting a shared default (e.g. in your
|
|
`agent.nix` shared defaults) or per-agent to narrow the menu — for example a
|
|
haiku-only agent can hide `opus` and `sonnet`. The *current* model is
|
|
still set by `hyperhive.model` and remains switchable at runtime via the
|
|
UI; this option only controls which choices the picker presents.
|
|
|
|
Values are the short model names that `claude --model` accepts:
|
|
`"haiku"`, `"sonnet"`, `"opus"` (or any future identifier).
|
|
'';
|
|
};
|
|
|
|
options.hyperhive.effortLevel = lib.mkOption {
|
|
type = lib.types.enum [
|
|
"low"
|
|
"medium"
|
|
"high"
|
|
"xhigh"
|
|
"max"
|
|
];
|
|
default = "medium";
|
|
example = "high";
|
|
description = ''
|
|
Baseline claude effort level for this agent. Rendered into the
|
|
`HIVE_DEFAULT_EFFORT` environment variable; the harness resolves
|
|
effort as operator-override-file → this env → built-in `"medium"`,
|
|
and passes the result to `claude --effort` at turn launch.
|
|
|
|
Ascending scale: `"low"` (minimal thinking budget), `"medium"`
|
|
(default — balanced), `"high"` (platform default), `"xhigh"`
|
|
(recommended for autonomous coding on capable models), `"max"`
|
|
(maximum thinking budget, highest cost). The operator can override
|
|
at runtime per-agent via the web UI (applied on the next session);
|
|
any rebuild that changes this option resets that override.
|
|
'';
|
|
};
|
|
|
|
options.hyperhive.autoCompact = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = ''
|
|
Enable proactive watermark-based compaction. When `true` (the
|
|
default) the harness automatically runs a notes-checkpoint turn
|
|
followed by `/compact` once the context window crosses 75% of
|
|
the model's limit, keeping later turns from hitting the hard
|
|
overflow path. Set to `false` to disable proactive compaction
|
|
entirely (`HIVE_COMPACT_WATERMARK_TOKENS=0`); the reactive path
|
|
(compact-on-overflow when the session is already past the limit)
|
|
still applies.
|
|
|
|
Disable for agents that run large-context models (sonnet/opus)
|
|
where the heuristic fires too early and discards useful history
|
|
before the session is actually close to the limit.
|
|
'';
|
|
};
|
|
|
|
options.hyperhive.extraWebProxies = lib.mkOption {
|
|
type = lib.types.attrsOf lib.types.str;
|
|
default = { };
|
|
example = lib.literalExpression ''{ "stats" = "http://127.0.0.1:3737"; }'';
|
|
description = ''
|
|
Transparent reverse-proxies mounted under `/extra/` in the per-agent web UI.
|
|
Each attribute name becomes the sub-path and the value is the upstream.
|
|
E.g. `{ "stats" = "http://127.0.0.1:3737"; }` mounts a proxy at
|
|
`/agent/<name>/extra/stats/` that forwards to port 3737 with the prefix
|
|
stripped. All user-declared proxies live under `/extra/` so they can
|
|
never conflict with native agent endpoints (`/api/*`, `/events/*`, etc.).
|
|
|
|
The upstream value is either an `http(s)://` URL or a Unix domain
|
|
socket, spelled `unix:<path>` (e.g. `unix:/run/myapp/http.sock`) — for
|
|
agents whose secondary web server only listens on a UDS.
|
|
|
|
Intended for agents that run secondary web servers in the same container.
|
|
Static assets served by the secondary app must use relative paths to
|
|
resolve correctly under the sub-path prefix.
|
|
|
|
Sets the `HIVE_EXTRA_WEB_PROXIES` environment variable (JSON object)
|
|
on the harness service unit.
|
|
'';
|
|
};
|
|
|
|
config = {
|
|
assertions = [
|
|
# hyperhive.model must be a non-empty string — an empty value causes
|
|
# the harness to pass an invalid model flag to claude.
|
|
{
|
|
assertion = config.hyperhive.model != "";
|
|
message = "hyperhive.model must not be empty (set it to e.g. \"haiku\" or \"sonnet\")";
|
|
}
|
|
# The current model must appear in the quick-picker menu, otherwise the
|
|
# UI would offer no way back to the model the agent is actually running.
|
|
{
|
|
assertion =
|
|
config.hyperhive.availableModels == [ ]
|
|
|| builtins.elem config.hyperhive.model config.hyperhive.availableModels;
|
|
message =
|
|
"hyperhive.model (\"${config.hyperhive.model}\") must be one of "
|
|
+ "hyperhive.availableModels ([ ${lib.concatStringsSep " " config.hyperhive.availableModels} ]) "
|
|
+ "— add it to the list or change the model.";
|
|
}
|
|
];
|
|
|
|
# HIVE_DEFAULT_MODEL seeds the initial model selection when no
|
|
# persisted model choice exists in the state dir.
|
|
environment.variables = {
|
|
HIVE_DEFAULT_MODEL = config.hyperhive.model;
|
|
# Comma-separated menu for the per-agent UI model quick-picker
|
|
# (see hyperhive.availableModels). The harness surfaces it to the
|
|
# frontend; an empty value falls back to the built-in default list.
|
|
HIVE_AVAILABLE_MODELS = lib.concatStringsSep "," config.hyperhive.availableModels;
|
|
# Per-agent baseline effort (see hyperhive.effortLevel). The
|
|
# harness resolves operator-override-file → this env → "medium"
|
|
# and passes it to claude --effort at turn launch.
|
|
HIVE_DEFAULT_EFFORT = config.hyperhive.effortLevel;
|
|
}
|
|
// lib.optionalAttrs (!config.hyperhive.autoCompact) {
|
|
# Zero watermark disables proactive compaction; the reactive path
|
|
# (compact-on-overflow) still fires when the session is truly full.
|
|
HIVE_COMPACT_WATERMARK_TOKENS = "0";
|
|
};
|
|
|
|
# Harness systemd unit. Unit shape (PATH wrapper-dir trick, env vars,
|
|
# RuntimeDirectory, User=, standalone-eval fallbacks):
|
|
# docs/agent-hierarchy.md::Harness systemd unit shape. PATH /bin
|
|
# auto-append behaviour: docs/gotchas.md::systemd.services.*.path
|
|
# appends /bin to every entry.
|
|
systemd.services.hive-agent =
|
|
let
|
|
binary = "hive-agent";
|
|
in
|
|
{
|
|
description = "${binary} harness";
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [ "network.target" ];
|
|
# `/run/wrappers` before `/run/current-system/sw` so setuid
|
|
# `sudo` resolves first. Passing the bare prefixes (no trailing
|
|
# `/bin`) is intentional — see docs pointer above.
|
|
path = [
|
|
"/run/wrappers"
|
|
"/run/current-system/sw"
|
|
];
|
|
environment = {
|
|
SHELL = "${pkgs.bashInteractive}/bin/bash";
|
|
HOME = homeDir;
|
|
HIVE_STATIC_DIR = "${config.hyperhive.frontend.mergedDist}";
|
|
HIVE_ASSETS_DIR = "${config.hyperhive.packages.assets}/share/hyperhive";
|
|
# Unix-socket path for the harness web UI. All agents always bind
|
|
# here; there is no TCP fallback. Path matches
|
|
# `hive_c0re::agent_sockets::socket_path_for(name)` so lifecycle
|
|
# bind-mounts and gateway upstream config stay in sync.
|
|
HIVE_WEB_SOCKET = "/run/hive-agent/${userName}/web.sock";
|
|
# In-agent socket (loose-ends v2): the harness binds this and
|
|
# serves the `hive-agent-sock` todo protocol to the in-container
|
|
# producers (matrix daemon) + the MCP bridge (`get_loose_ends`).
|
|
# Same per-agent runtime dir as the web socket so all agent-user
|
|
# services in this container can reach it; purely in-container
|
|
# (never bind-mounted to the host — unlike hive-c0re's mcp.sock).
|
|
HIVE_AGENT_SOCKET = "/run/hive-agent/${userName}/agent.sock";
|
|
# Loopback URL of the persistent `hive-mcp-http` daemon that
|
|
# `render_claude_config` points claude at for the built-in
|
|
# surface (HTTP is the sole transport — no per-turn stdio child).
|
|
# Kept in sync with the `hive-mcp-http` unit's `--http` port
|
|
# (see ./mcp.nix) via the same option. Always set — network
|
|
# isolation is unconditional, so a fixed per-container port is
|
|
# collision-free.
|
|
HYPERHIVE_MCP_HTTP_PORT = toString config.hyperhive.mcp.httpPort;
|
|
}
|
|
// lib.optionalAttrs config.hyperhive.gui.enable {
|
|
# Tells the harness which fixed VNC port weston bound, and (by
|
|
# its presence) that gui is enabled — the harness `/screen/ws`
|
|
# relay reads this instead of a runtime marker file. The port is
|
|
# container-local + fixed (network isolation is unconditional),
|
|
# so the same value for every gui agent is fine. See
|
|
# ./weston-vnc.nix::hyperhive.gui.vncPort.
|
|
HIVE_GUI_VNC_PORT = toString config.hyperhive.gui.vncPort;
|
|
}
|
|
// lib.optionalAttrs (config.hyperhive.extraWebProxies != { }) {
|
|
# JSON object {"<path>": "<upstream>"} for the transparent
|
|
# reverse-proxies. See `hyperhive.extraWebProxies` option
|
|
# and `web_ui/proxy.rs::extra_proxy_service`.
|
|
HIVE_EXTRA_WEB_PROXIES = builtins.toJSON config.hyperhive.extraWebProxies;
|
|
};
|
|
serviceConfig = {
|
|
ExecStart = "${config.hyperhive.packages.hive-agent}/bin/${binary}";
|
|
# Pin the journal identity to the binary name (otherwise systemd
|
|
# derives SyslogIdentifier from the ExecStart basename).
|
|
SyslogIdentifier = binary;
|
|
Restart = "on-failure";
|
|
RestartSec = 2;
|
|
# Per-service runtime dir owned by `User=` below; the harness
|
|
# writes its regenerated claude-{mcp-config,settings,system-prompt}
|
|
# files here (`paths::config_dir`). Separate from /run/hive,
|
|
# which holds hive-c0re's mcp.sock.
|
|
RuntimeDirectory = "hive-config";
|
|
User = userName;
|
|
Group = userName;
|
|
};
|
|
};
|
|
};
|
|
}
|