The subagent daemon now reads the parent agent's runtime at startup (`hive_runtime::RuntimeSpec`, from the harness's `HIVE_RUNTIME` / `HIVE_ACP_*`, which `mcp.nix` forwards onto its unit). On claude nothing changes. On ACP, each run drives an `AcpRuntime` whose session id is kept per name under the harness dir: `start` archives the old one, `continue` loads it (and fails when none is recorded), `interrupt` sends `session/cancel`, a role goes in front of the first prompt, and permission requests get the answers a claude subagent's tool list gives. The unit loads `backendEnvironmentFile` on ACP only, so the agent can authenticate. The end-of-turn handling moves out of the claude loop into `after_turn` unchanged, so both loops share it. Refs #4391
550 lines
24 KiB
Nix
550 lines
24 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.services.hyperhive.agent.user.name;
|
|
homeDir = "/home/${userName}";
|
|
acp = config.services.hyperhive.agent.acp;
|
|
isAcp = config.services.hyperhive.agent.runtime == "acp";
|
|
preset = if acp.preset == null then null else acp.presets.${acp.preset} or null;
|
|
oc = acp.opencode;
|
|
|
|
# Tools opencode must not offer, mirroring claude's built-in allow-list
|
|
# (hive_sh4re::permissions): no built-in shell (shell is `mcp__bash__run`),
|
|
# no nested agents, no in-session todo list, no web search. `webfetch`
|
|
# asks, and the harness answers per the `web_tools` tool group. Passed as
|
|
# OPENCODE_PERMISSION because opencode merges that over every config file,
|
|
# including ones the agent can write.
|
|
opencodePermission = {
|
|
bash = "deny";
|
|
task = "deny";
|
|
todowrite = "deny";
|
|
websearch = "deny";
|
|
webfetch = "ask";
|
|
};
|
|
opencodeConfig = pkgs.writeText "opencode.json" (
|
|
builtins.toJSON {
|
|
"$schema" = "https://opencode.ai/config.json";
|
|
autoupdate = false;
|
|
share = "disabled";
|
|
model = "${oc.provider.id}/${oc.model}";
|
|
provider.${oc.provider.id} = {
|
|
npm = "@ai-sdk/openai-compatible";
|
|
inherit (oc.provider) name;
|
|
options = {
|
|
baseURL = oc.provider.baseUrl;
|
|
# Substituted by opencode from its environment at startup, so the
|
|
# key stays in backendEnvironmentFile and out of the store.
|
|
apiKey = "{env:${oc.provider.apiKeyEnv}}";
|
|
};
|
|
# `limit.context` is what opencode reports as the window in its
|
|
# `usage_update`, i.e. the harness's ctx %.
|
|
models.${oc.model} = {
|
|
name = oc.model;
|
|
limit = {
|
|
context = oc.contextWindow;
|
|
output = oc.outputLimit;
|
|
};
|
|
};
|
|
};
|
|
}
|
|
);
|
|
in
|
|
{
|
|
options.services.hyperhive.agent.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.services.hyperhive.agent.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 `services.hyperhive.agent.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.services.hyperhive.agent.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.services.hyperhive.agent.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.services.hyperhive.agent.useApiKey = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = ''
|
|
Authenticate this agent's `claude` invocations with an API key
|
|
(`ANTHROPIC_API_KEY`/`ANTHROPIC_BASE_URL`, e.g. OpenRouter) rather
|
|
than a Claude OAuth session. Sets `HIVE_USE_API_KEY=1`, which the
|
|
harness reads (`hive_agent::login::using_api_key`) to report
|
|
`LoginState::Online` at boot without checking `~/.claude/` — an
|
|
api-key agent has no OAuth session to wait for, so it must never
|
|
park the turn loop expecting one. Also stamped into the consolidated
|
|
harness state file so hive-c0re's dashboard stops reading an empty
|
|
`~/.claude/` as "needs login" for this agent (see
|
|
`hive_c0re::container_view`'s `needs_login` computation).
|
|
|
|
Set this AND `services.hyperhive.agent.backendEnvironmentFile` together — this
|
|
option changes what the harness believes about its own login state,
|
|
the other actually supplies the credentials `claude` reads. Neither
|
|
is useful alone.
|
|
'';
|
|
};
|
|
|
|
options.services.hyperhive.agent.backendEnvironmentFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
example = "/agents/myagent/harness/openrouter.env";
|
|
description = ''
|
|
Path (outside the nix store) to a systemd `EnvironmentFile` loaded
|
|
by the harness service — the mechanism for supplying
|
|
`ANTHROPIC_API_KEY`/`ANTHROPIC_BASE_URL` (or any other backend
|
|
credential `claude`/the harness reads from the environment) without
|
|
baking a secret into the nix store.
|
|
|
|
The file must use systemd `EnvironmentFile` syntax: one `KEY=value`
|
|
pair per line, no `export`, no shell quoting needed for simple
|
|
values. Example contents:
|
|
|
|
```
|
|
ANTHROPIC_API_KEY=sk-or-v1-...
|
|
ANTHROPIC_BASE_URL=https://openrouter.ai/api/v1
|
|
```
|
|
|
|
Place the file inside the agent's bind-mounted **harness** dir (e.g.
|
|
`/agents/<name>/harness/openrouter.env`, `$HYPERHIVE_HARNESS_DIR`),
|
|
not `state/` — `harness/` survives container rebuilds exactly like
|
|
`state/` does, but is never bind-mounted into another agent's
|
|
container (unlike `state/`, which a `ManageRootAgent` holder gets
|
|
read-write for recovery — see `docs/agent-lifecycle/persistence.md`'s "Cross-agent access to
|
|
state"), so this credential is reachable by nothing but this agent
|
|
and the host. Permissions should be `0600`, owned by the agent's
|
|
unix user. Loaded with a leading `-` (optional `EnvironmentFile`),
|
|
so a path that doesn't exist yet — an operator setting this option
|
|
before creating the file, or a fresh host rebuild before state is
|
|
restored — makes systemd skip it rather than refuse to start the
|
|
harness. See `services.hyperhive.agent.useApiKey`'s doc for the option this one is
|
|
paired with.
|
|
'';
|
|
};
|
|
|
|
options.services.hyperhive.agent.runtime = lib.mkOption {
|
|
type = lib.types.enum [
|
|
"claude"
|
|
"acp"
|
|
];
|
|
default = "claude";
|
|
example = "acp";
|
|
description = ''
|
|
What drives this agent's turns. `"claude"` runs `claude --print`.
|
|
`"acp"` runs the Agent Client Protocol agent described by
|
|
`services.hyperhive.agent.acp`, as one long-lived child of the
|
|
harness, and turns `services.hyperhive.agent.useApiKey` on by default:
|
|
the agent authenticates to its own provider, so there is no Claude
|
|
login to wait for.
|
|
|
|
On `"acp"`, `model`, `effortLevel` and `autoCompact` have no effect
|
|
(the model is whatever the agent is configured with), and neither the
|
|
web UI's cancel button nor `/compact` works yet.
|
|
|
|
Subagents (`hive-subagent-daemon`) run on the same runtime. On
|
|
`"acp"`, each subagent run spawns its own copy of the ACP agent, which
|
|
also gets `services.hyperhive.agent.backendEnvironmentFile`.
|
|
'';
|
|
};
|
|
|
|
options.services.hyperhive.agent.acp = {
|
|
preset = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
example = "opencode";
|
|
description = ''
|
|
Name of an entry in `services.hyperhive.agent.acp.presets` whose
|
|
`command`, `args` and `env` become the defaults of the options of
|
|
the same name here.
|
|
'';
|
|
};
|
|
|
|
command = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "";
|
|
example = lib.literalExpression ''"''${pkgs.opencode}/bin/opencode"'';
|
|
description = "Program the harness spawns as its ACP agent (`HIVE_ACP_COMMAND`).";
|
|
};
|
|
|
|
args = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
example = [ "acp" ];
|
|
description = "Arguments for `command` (`HIVE_ACP_ARGS`, as JSON).";
|
|
};
|
|
|
|
env = lib.mkOption {
|
|
type = lib.types.attrsOf lib.types.str;
|
|
default = { };
|
|
description = ''
|
|
Environment for the ACP agent only, on top of the harness's own
|
|
(`HIVE_ACP_ENV`, as JSON). Rendered into the nix store: never put a
|
|
credential here. Put it in `services.hyperhive.agent.backendEnvironmentFile`,
|
|
which the agent inherits through the harness.
|
|
'';
|
|
};
|
|
|
|
presets = lib.mkOption {
|
|
type = lib.types.attrsOf (
|
|
lib.types.submodule {
|
|
options = {
|
|
command = lib.mkOption { type = lib.types.str; };
|
|
args = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
};
|
|
env = lib.mkOption {
|
|
type = lib.types.attrsOf lib.types.str;
|
|
default = { };
|
|
};
|
|
};
|
|
}
|
|
);
|
|
description = ''
|
|
Named ACP agent setups `services.hyperhive.agent.acp.preset` can
|
|
select. `opencode` runs `opencode acp` against the OpenAI-compatible
|
|
provider set in `services.hyperhive.agent.acp.opencode`.
|
|
'';
|
|
};
|
|
|
|
opencode = {
|
|
provider = {
|
|
id = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "provider";
|
|
example = "hetzner";
|
|
description = "Provider id in opencode's config; the model is addressed as `<id>/<model>`.";
|
|
};
|
|
name = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = oc.provider.id;
|
|
defaultText = lib.literalExpression "config.services.hyperhive.agent.acp.opencode.provider.id";
|
|
description = "Display name of the provider.";
|
|
};
|
|
baseUrl = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
example = "https://inference.hetzner.com/api/v1";
|
|
description = "Base URL of the OpenAI-compatible API.";
|
|
};
|
|
apiKeyEnv = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "ACP_PROVIDER_API_KEY";
|
|
description = ''
|
|
Environment variable opencode reads the provider's API key from.
|
|
Set it in `services.hyperhive.agent.backendEnvironmentFile`.
|
|
'';
|
|
};
|
|
};
|
|
model = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
example = "Qwen/Qwen3.6-35B-A3B-FP8";
|
|
description = "Model id, as the provider's API names it.";
|
|
};
|
|
contextWindow = lib.mkOption {
|
|
type = lib.types.ints.positive;
|
|
default = 131072;
|
|
example = 262144;
|
|
description = "The model's context window in tokens; the harness's ctx % is measured against it.";
|
|
};
|
|
outputLimit = lib.mkOption {
|
|
type = lib.types.ints.positive;
|
|
default = 32768;
|
|
description = "Maximum output tokens per model response.";
|
|
};
|
|
};
|
|
};
|
|
|
|
options.services.hyperhive.agent.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 = [
|
|
# services.hyperhive.agent.model must be a non-empty string — an empty value causes
|
|
# the harness to pass an invalid model flag to claude.
|
|
{
|
|
assertion = config.services.hyperhive.agent.model != "";
|
|
message = "services.hyperhive.agent.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.services.hyperhive.agent.availableModels == [ ]
|
|
|| builtins.elem config.services.hyperhive.agent.model config.services.hyperhive.agent.availableModels;
|
|
message =
|
|
"services.hyperhive.agent.model (\"${config.services.hyperhive.agent.model}\") must be one of "
|
|
+ "services.hyperhive.agent.availableModels ([ ${lib.concatStringsSep " " config.services.hyperhive.agent.availableModels} ]) "
|
|
+ "— add it to the list or change the model.";
|
|
}
|
|
{
|
|
assertion = acp.preset == null || preset != null;
|
|
message =
|
|
"services.hyperhive.agent.acp.preset (\"${toString acp.preset}\") names no entry in "
|
|
+ "services.hyperhive.agent.acp.presets ([ ${lib.concatStringsSep " " (lib.attrNames acp.presets)} ]).";
|
|
}
|
|
{
|
|
assertion = !isAcp || acp.command != "";
|
|
message = "services.hyperhive.agent.runtime = \"acp\" needs services.hyperhive.agent.acp.command (or acp.preset).";
|
|
}
|
|
{
|
|
assertion = acp.preset != "opencode" || (oc.provider.baseUrl != null && oc.model != null);
|
|
message = "services.hyperhive.agent.acp.preset = \"opencode\" needs acp.opencode.provider.baseUrl and acp.opencode.model.";
|
|
}
|
|
];
|
|
|
|
services.hyperhive.agent.useApiKey = lib.mkIf isAcp (lib.mkDefault true);
|
|
|
|
services.hyperhive.agent.acp = {
|
|
presets.opencode = {
|
|
command = "${pkgs.opencode}/bin/opencode";
|
|
args = [ "acp" ];
|
|
env = {
|
|
OPENCODE_CONFIG = "${opencodeConfig}";
|
|
OPENCODE_PERMISSION = builtins.toJSON opencodePermission;
|
|
# Keeps a config file in the agent's working directory from
|
|
# overriding the one above.
|
|
OPENCODE_DISABLE_PROJECT_CONFIG = "1";
|
|
};
|
|
};
|
|
command = lib.mkIf (preset != null) (lib.mkDefault preset.command);
|
|
args = lib.mkIf (preset != null) (lib.mkDefault preset.args);
|
|
env = lib.mkIf (preset != null) (lib.mkDefault preset.env);
|
|
};
|
|
|
|
# HIVE_DEFAULT_MODEL seeds the initial model selection when no
|
|
# persisted model choice exists in the state dir.
|
|
environment.variables = {
|
|
HIVE_DEFAULT_MODEL = config.services.hyperhive.agent.model;
|
|
# Comma-separated menu for the per-agent UI model quick-picker
|
|
# (see services.hyperhive.agent.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.services.hyperhive.agent.availableModels;
|
|
# Per-agent baseline effort (see services.hyperhive.agent.effortLevel). The
|
|
# harness resolves operator-override-file → this env → "medium"
|
|
# and passes it to claude --effort at turn launch.
|
|
HIVE_DEFAULT_EFFORT = config.services.hyperhive.agent.effortLevel;
|
|
}
|
|
// lib.optionalAttrs (!config.services.hyperhive.agent.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-lifecycle/agent-hierarchy.md::Harness systemd unit shape. PATH /bin
|
|
# auto-append behaviour: docs/process/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.services.hyperhive.agent.frontend.mergedDist}";
|
|
HIVE_ASSETS_DIR = "${config.services.hyperhive.agent.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.services.hyperhive.agent.mcp.httpPort;
|
|
}
|
|
// lib.optionalAttrs config.services.hyperhive.agent.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::services.hyperhive.agent.gui.vncPort.
|
|
HIVE_GUI_VNC_PORT = toString config.services.hyperhive.agent.gui.vncPort;
|
|
}
|
|
// lib.optionalAttrs (config.services.hyperhive.agent.extraWebProxies != { }) {
|
|
# JSON object {"<path>": "<upstream>"} for the transparent
|
|
# reverse-proxies. See `services.hyperhive.agent.extraWebProxies` option
|
|
# and `web_ui/proxy.rs::extra_proxy_service`.
|
|
HIVE_EXTRA_WEB_PROXIES = builtins.toJSON config.services.hyperhive.agent.extraWebProxies;
|
|
}
|
|
// lib.optionalAttrs config.services.hyperhive.agent.useApiKey {
|
|
# Tells the harness not to wait for a Claude OAuth session — see
|
|
# `services.hyperhive.agent.useApiKey`'s own description for the full mechanism.
|
|
HIVE_USE_API_KEY = "1";
|
|
}
|
|
// lib.optionalAttrs isAcp {
|
|
# Read by `hive_runtime::RuntimeSpec`; unset means claude.
|
|
HIVE_RUNTIME = "acp";
|
|
HIVE_ACP_COMMAND = acp.command;
|
|
HIVE_ACP_ARGS = builtins.toJSON acp.args;
|
|
HIVE_ACP_ENV = builtins.toJSON acp.env;
|
|
};
|
|
serviceConfig = {
|
|
ExecStart = "${config.services.hyperhive.agent.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;
|
|
# A LOWER OOMScoreAdjust means LESS likely to be killed, so this
|
|
# negative value puts the harness — and the agent's own `claude`,
|
|
# which it spawns as a child and which therefore inherits the
|
|
# value — last in line under memory pressure, behind
|
|
# `hive-subagent-daemon`'s `+500` (see ./mcp.nix). Losing a
|
|
# subagent costs one restartable task; losing this costs the
|
|
# session that was supervising it. Not `-1000`, which would
|
|
# exempt the harness entirely and leave the kernel with nothing
|
|
# to kill in a container whose only large process is this one.
|
|
OOMScoreAdjust = -500;
|
|
# 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;
|
|
}
|
|
// lib.optionalAttrs (config.services.hyperhive.agent.backendEnvironmentFile != null) {
|
|
# See `services.hyperhive.agent.backendEnvironmentFile`'s own description for
|
|
# the file shape and the leading-`-` rationale.
|
|
EnvironmentFile = "-${config.services.hyperhive.agent.backendEnvironmentFile}";
|
|
};
|
|
};
|
|
};
|
|
}
|