events: HIVE_DEFAULT_MODEL takes priority over persisted model (closes #319)

This commit is contained in:
damocles 2026-05-23 00:38:17 +02:00
parent cd9831b39e
commit 77b249076f
2 changed files with 68 additions and 17 deletions

View file

@ -310,22 +310,27 @@ pub enum TurnState {
Compacting, Compacting,
} }
/// Default claude model when nothing's been set at runtime. Overridable /// Compiled-in fallback model used when neither `HIVE_DEFAULT_MODEL` nor a
/// via the `HIVE_DEFAULT_MODEL` env var (set from `hyperhive.model` in /// persisted runtime override is present.
/// the container's `agent.nix`). The operator can also switch at runtime
/// via `/model <name>` in the web terminal; the chosen model is persisted
/// to the state dir so it survives restarts.
pub const DEFAULT_MODEL: &str = "haiku"; pub const DEFAULT_MODEL: &str = "haiku";
/// Return the initial default model name: `HIVE_DEFAULT_MODEL` env var if /// Return the model declared in `HIVE_DEFAULT_MODEL` (set from
/// set to a non-empty string, otherwise `DEFAULT_MODEL`. /// `hyperhive.model` in `agent.nix`), or `None` if the env var is absent /
/// empty. When `Some`, this takes precedence over any persisted runtime
/// override so that nix config changes always take effect on rebuild.
#[must_use] #[must_use]
pub fn default_model() -> &'static str { pub fn configured_model() -> Option<&'static str> {
// Leak once at startup — acceptable for a single config value. // Leak once at startup — acceptable for a single config value.
std::env::var("HIVE_DEFAULT_MODEL") std::env::var("HIVE_DEFAULT_MODEL")
.ok() .ok()
.filter(|s| !s.trim().is_empty()) .filter(|s| !s.trim().is_empty())
.map_or(DEFAULT_MODEL, |s| Box::leak(s.into_boxed_str())) .map(|s| &*Box::leak(s.into_boxed_str()))
}
/// Return the model to use when no config and no persisted override exist.
#[must_use]
pub fn default_model() -> &'static str {
configured_model().unwrap_or(DEFAULT_MODEL)
} }
/// Context-window size in tokens for a given model name. /// Context-window size in tokens for a given model name.
@ -441,7 +446,13 @@ impl Bus {
} }
}; };
let (tx, _) = broadcast::channel(CHANNEL_CAPACITY); let (tx, _) = broadcast::channel(CHANNEL_CAPACITY);
let initial_model = load_model().unwrap_or_else(|| default_model().to_owned()); // Priority: HIVE_DEFAULT_MODEL (from hyperhive.model in agent.nix) >
// persisted runtime override > compiled-in DEFAULT_MODEL.
// The nix config always wins on rebuild; the persisted file is kept
// for within-session tracking only (see persist_model / set_model).
let initial_model = configured_model()
.map(str::to_owned)
.unwrap_or_else(|| load_model().unwrap_or_else(|| DEFAULT_MODEL.to_owned()));
// Restore rate_limited from the sentinel file — if the harness // Restore rate_limited from the sentinel file — if the harness
// crashed while parked, we should still show the right status on // crashed while parked, we should still show the right status on
// cold load until the next turn clears it. // cold load until the next turn clears it.

View file

@ -2,6 +2,11 @@
pkgs, pkgs,
lib, lib,
config, config,
# Flake inputs routed through _module.args by the agent flake.nix.
# Default to {} so the module evaluates cleanly even when the agent
# flake doesn't set up the routing pattern (e.g. during standalone
# nixos-rebuild without a flake wrapper).
flakeInputs ? { },
... ...
}: }:
{ {
@ -20,13 +25,12 @@
default = "haiku"; default = "haiku";
example = "sonnet"; example = "sonnet";
description = '' description = ''
Default claude model for this agent. Sets the `HIVE_DEFAULT_MODEL` Claude model for this agent. Sets the `HIVE_DEFAULT_MODEL`
environment variable consumed by the harness at boot; if no environment variable; the harness applies it at boot and it takes
persisted model choice exists in the agent's state dir the harness priority over any persisted runtime override. The operator can still
falls back to this value. The operator can still switch the model at switch the model at runtime via the per-agent web UI that choice
runtime via the per-agent web UI that choice is persisted to the is tracked in the state dir for the current session but is reset by
state dir and takes precedence over this default until the agent is any rebuild that changes this option.
purged.
Valid values are the short model names that `claude --model` accepts: Valid values are the short model names that `claude --model` accepts:
`"haiku"`, `"sonnet"`, `"opus"` (or any future identifier). Context `"haiku"`, `"sonnet"`, `"opus"` (or any future identifier). Context
@ -320,6 +324,42 @@
}; };
config = { config = {
assertions = [
# Guard the inputs-routed-as-output pattern: the agent flake.nix is
# expected to set `_module.args.flakeInputs = builtins.removeAttrs inputs ["self"]`.
# If `self` leaks into flakeInputs the agent gets a spurious attrset
# entry that can shadow real inputs and is almost certainly a bug.
{
assertion = !(builtins.hasAttr "self" flakeInputs);
message = ''
hyperhive: `flakeInputs` must not contain "self".
In your agent flake.nix, use:
_module.args.flakeInputs = builtins.removeAttrs inputs [ "self" ];
'';
}
# 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\")";
}
# hyperhive.forge.url must look like an HTTP URL when non-default.
{
assertion =
config.hyperhive.forge.url == ""
|| lib.hasPrefix "http://" config.hyperhive.forge.url
|| lib.hasPrefix "https://" config.hyperhive.forge.url;
message = "hyperhive.forge.url must be an http:// or https:// URL (got: \"${config.hyperhive.forge.url}\")";
}
# hyperhive.icon must reference an SVG file when set.
{
assertion =
config.hyperhive.icon == null
|| lib.hasSuffix ".svg" (toString config.hyperhive.icon);
message = "hyperhive.icon must point to an .svg file";
}
];
environment.etc."hyperhive/extra-mcp.json".text = builtins.toJSON config.hyperhive.extraMcpServers; environment.etc."hyperhive/extra-mcp.json".text = builtins.toJSON config.hyperhive.extraMcpServers;
# Operator-set per-agent icon (hyperhive.icon). When configured, the # Operator-set per-agent icon (hyperhive.icon). When configured, the