refactor: nix/host-modules + nix/agent-modules layout, update doc paths
This commit is contained in:
parent
cb755b677c
commit
4a48ce5024
52 changed files with 48 additions and 44 deletions
245
nix/agent-modules/agent-service.nix
Normal file
245
nix/agent-modules/agent-service.nix
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
# The hive-ag3nt 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-ag3nt =
|
||||
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";
|
||||
# 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;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue