remove c0re.operatorPronouns and c0re.contextWindowTokens
Both options are now ignored with a warning (the swarm-peers-removed.nix shape), so a host that still sets one keeps evaluating. - operatorPronouns: the agents' system prompt names the pronouns as she/her, the value the harness fell back to and the option's default. The --operator-pronouns flag, HiveEnv field, meta-flake let binding and HIVE_OPERATOR_PRONOUNS env var are gone. - contextWindowTokens: the per-family defaults (haiku 200000, sonnet 1000000, opus 1000000) move into hive-agent's harness_state as a table; the window the API reports after a turn still wins. The --context-window-tokens flag, HiveEnv field and the rendered HIVE_CONTEXT_WINDOW_TOKENS_<KEY> env vars are gone.
This commit is contained in:
parent
5cd1f16405
commit
4b5707768c
18 changed files with 151 additions and 258 deletions
|
|
@ -136,8 +136,6 @@ covers spreading a swarm across hosts.
|
||||||
swarm.domain = "hive.example"; # the swarm's DNS domain
|
swarm.domain = "hive.example"; # the swarm's DNS domain
|
||||||
hiveName = "pr1ma"; # this hive's label in it
|
hiveName = "pr1ma"; # this hive's label in it
|
||||||
swarm.hives.pr1ma = { }; # the directory: just us
|
swarm.hives.pr1ma = { }; # the directory: just us
|
||||||
|
|
||||||
# c0re.operatorPronouns = "they/them"; # default "she/her"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# ... the rest of your host config
|
# ... the rest of your host config
|
||||||
|
|
|
||||||
|
|
@ -61,16 +61,15 @@ clearing it goes back to the local value →
|
||||||
context-window size per-model. Resolution order (first
|
context-window size per-model. Resolution order (first
|
||||||
match wins):
|
match wins):
|
||||||
|
|
||||||
1. `HIVE_CONTEXT_WINDOW_TOKENS_<KEY>` env var, where `KEY`
|
1. The harness's per-family defaults, matched when the family name
|
||||||
(lowercased) is a substring of the active model name. Injected
|
is a case-insensitive substring of the active model name:
|
||||||
by the meta flake from `services.hyperhive.c0re.contextWindowTokens`
|
haiku=200k, sonnet=1M, opus=1M.
|
||||||
(host-level NixOS option, defaults: haiku=200k, sonnet=1M,
|
|
||||||
opus=1M). Override these for all agents at once without a
|
|
||||||
per-agent config change.
|
|
||||||
2. `HIVE_CONTEXT_WINDOW_TOKENS` — single global override for any
|
2. `HIVE_CONTEXT_WINDOW_TOKENS` — single global override for any
|
||||||
model (useful in dev / test).
|
other model (useful in dev / test).
|
||||||
3. Hard fallback: `200_000` (conservative; only reached outside
|
3. Hard fallback: `200_000`.
|
||||||
NixOS where the env vars aren't set).
|
|
||||||
|
Once a turn completes, the window the model reports
|
||||||
|
(`modelUsage.*.contextWindow`) replaces this value.
|
||||||
|
|
||||||
The effective window drives watermarks; the harness exposes it at
|
The effective window drives watermarks; the harness exposes it at
|
||||||
runtime via `/api/state.context_window_tokens` so the UI can show a
|
runtime via `/api/state.context_window_tokens` so the UI can show a
|
||||||
|
|
@ -256,14 +255,13 @@ needs to `chown` a bind mount), once at startup:
|
||||||
`hive-agent/prompts/system.md` by `hive-agent`'s `prompt::render`:
|
`hive-agent/prompts/system.md` by `hive-agent`'s `prompt::render`:
|
||||||
HTML-comment markers (`<!-- role:agent -->...<!-- /role:agent -->`,
|
HTML-comment markers (`<!-- role:agent -->...<!-- /role:agent -->`,
|
||||||
same for `role:manager`) gate the role-specific blocks; both flavors
|
same for `role:manager`) gate the role-specific blocks; both flavors
|
||||||
share everything else. Five placeholders are then
|
share everything else. Four placeholders are then
|
||||||
substituted: `{label}` (short agent name), `{qualified_label}`
|
substituted: `{label}` (short agent name), `{qualified_label}`
|
||||||
(hive-qualified `name@domain` form), `{operator_pronouns}`,
|
(hive-qualified `name@domain` form),
|
||||||
`{hive_identity}` (for example `` on hive `pr1ma` ``; empty when
|
`{hive_identity}` (for example `` on hive `pr1ma` ``; empty when
|
||||||
`HYPERHIVE_HIVE_NAME` is unset), and `{swarm_identity}` (same
|
`HYPERHIVE_HIVE_NAME` is unset), and `{swarm_identity}` (same
|
||||||
shape for the swarm). Pronouns come from `HIVE_OPERATOR_PRONOUNS`
|
shape for the swarm). The template itself names the operator's
|
||||||
env (set by the meta flake from
|
pronouns (`she/her`); no option sets them.
|
||||||
`services.hyperhive.c0re.operatorPronouns`, default `she/her`).
|
|
||||||
When you enable `services.hyperhive.agent.docs.enable`, `HIVE_DOCS_DIR` is present
|
When you enable `services.hyperhive.agent.docs.enable`, `HIVE_DOCS_DIR` is present
|
||||||
in the environment and `render()` appends a one-sentence pointer
|
in the environment and `render()` appends a one-sentence pointer
|
||||||
telling the agent where the docs live.
|
telling the agent where the docs live.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity}{swarm_identity} in a multi-agent system. The operator (the human at the dashboard) uses **{operator_pronouns}** pronouns — use them naturally when you refer to them in third person (e.g. when relaying to a peer or the manager). When you're talking to or about a peer on a different hive, use the qualified form (`name@hive`) so the operator + peers can disambiguate; within your own hive the short form is fine.
|
You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity}{swarm_identity} in a multi-agent system. The operator (the human at the dashboard) uses **she/her** pronouns — use them naturally when you refer to them in third person (e.g. when relaying to a peer or the manager). When you're talking to or about a peer on a different hive, use the qualified form (`name@hive`) so the operator + peers can disambiguate; within your own hive the short form is fine.
|
||||||
|
|
||||||
Tools (hyperhive surface). Full signature + behavior for each comes from the tool's own MCP description (you already received it via the MCP tool schema) — this is just the map of what exists and which ones are gated, so you know where to look:
|
Tools (hyperhive surface). Full signature + behavior for each comes from the tool's own MCP description (you already received it via the MCP tool schema) — this is just the map of what exists and which ones are gated, so you know where to look:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -367,10 +367,10 @@ pub struct Bus {
|
||||||
/// Per-inference context-window size as reported by the Anthropic API
|
/// Per-inference context-window size as reported by the Anthropic API
|
||||||
/// in the stream-json `result` event (`modelUsage.*.contextWindow`).
|
/// in the stream-json `result` event (`modelUsage.*.contextWindow`).
|
||||||
/// Set by the stdout pump on every completed turn. Takes precedence
|
/// Set by the stdout pump on every completed turn. Takes precedence
|
||||||
/// over the Nix-configured `HIVE_CONTEXT_WINDOW_TOKENS_*` env vars
|
/// over the per-model defaults in `harness_state::context_window_tokens`
|
||||||
/// for compaction watermark calculations — it reflects the actual
|
/// for compaction watermark calculations — it reflects the actual
|
||||||
/// limit the model enforces, which may differ from what the operator
|
/// limit the model enforces, which may differ from the default
|
||||||
/// configured (e.g. 200 k active window on a 1 M cache-enabled model).
|
/// (e.g. 200 k active window on a 1 M cache-enabled model).
|
||||||
api_context_window: Arc<Mutex<Option<u64>>>,
|
api_context_window: Arc<Mutex<Option<u64>>>,
|
||||||
/// Resolved model id from the most recent turn's `assistant` events
|
/// Resolved model id from the most recent turn's `assistant` events
|
||||||
/// (the API-echoed `message.model`, e.g. `claude-opus-4-8`), as
|
/// (the API-echoed `message.model`, e.g. `claude-opus-4-8`), as
|
||||||
|
|
|
||||||
|
|
@ -237,33 +237,31 @@ pub fn configured_effort() -> Option<&'static str> {
|
||||||
.map(|s| &*Box::leak(s.into_boxed_str()))
|
.map(|s| &*Box::leak(s.into_boxed_str()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Context-window size in tokens for a given model name.
|
/// Context-window size in tokens per model family, keyed by a substring
|
||||||
///
|
/// of the model name (`"sonnet"` matches `"claude-sonnet-4-5"`).
|
||||||
/// Canonical per-model sizes are declared in the harness nix modules as
|
const CONTEXT_WINDOW_DEFAULTS: [(&str, u64); 3] = [
|
||||||
/// `hyperhive.contextWindowTokens` and injected as
|
("haiku", 200_000),
|
||||||
/// `HIVE_CONTEXT_WINDOW_TOKENS_<KEY_UPPER>` env vars — so this function
|
("sonnet", 1_000_000),
|
||||||
/// normally just reads them. The Rust code carries no model knowledge;
|
("opus", 1_000_000),
|
||||||
/// updating model families only requires a Nix change.
|
];
|
||||||
|
|
||||||
|
/// Context-window size in tokens for a given model name, used until a
|
||||||
|
/// completed turn reports the real window (`Bus::effective_context_window`
|
||||||
|
/// prefers that one).
|
||||||
///
|
///
|
||||||
/// Resolution order (first match wins):
|
/// Resolution order (first match wins):
|
||||||
/// 1. `HIVE_CONTEXT_WINDOW_TOKENS_<KEY>` — key (lowercased) is a
|
/// 1. [`CONTEXT_WINDOW_DEFAULTS`] — the family key, case-insensitively,
|
||||||
/// substring of the active model name. Populated by the Nix default
|
/// is a substring of the model name.
|
||||||
/// map for all known families; add/override in `agent.nix`.
|
|
||||||
/// 2. `HIVE_CONTEXT_WINDOW_TOKENS` — single global override (any model).
|
/// 2. `HIVE_CONTEXT_WINDOW_TOKENS` — single global override (any model).
|
||||||
/// 3. Hard fallback: `200_000` (conservative; only hit outside NixOS).
|
/// 3. Hard fallback: `200_000`.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn context_window_tokens(model: &str) -> u64 {
|
pub fn context_window_tokens(model: &str) -> u64 {
|
||||||
let m = model.to_ascii_lowercase();
|
let m = model.to_ascii_lowercase();
|
||||||
// Per-model env vars set by `hyperhive.contextWindowTokens` in Nix.
|
if let Some((_, tokens)) = CONTEXT_WINDOW_DEFAULTS
|
||||||
for (key, val) in std::env::vars() {
|
.iter()
|
||||||
if let Some(suffix) = key.strip_prefix("HIVE_CONTEXT_WINDOW_TOKENS_")
|
.find(|(family, _)| m.contains(family))
|
||||||
&& !suffix.is_empty()
|
{
|
||||||
&& m.contains(&suffix.to_ascii_lowercase())
|
return *tokens;
|
||||||
&& let Ok(v) = val.trim().parse::<u64>()
|
|
||||||
&& v > 0
|
|
||||||
{
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Global override (single value, any model).
|
// Global override (single value, any model).
|
||||||
if let Ok(s) = std::env::var("HIVE_CONTEXT_WINDOW_TOKENS")
|
if let Ok(s) = std::env::var("HIVE_CONTEXT_WINDOW_TOKENS")
|
||||||
|
|
@ -272,13 +270,25 @@ pub fn context_window_tokens(model: &str) -> u64 {
|
||||||
{
|
{
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
// Hard fallback for dev/test outside NixOS where env vars aren't set.
|
|
||||||
200_000
|
200_000
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{DEFAULT_EFFORT, DEFAULT_MODEL, Source, effort_from, model_from};
|
use super::{
|
||||||
|
DEFAULT_EFFORT, DEFAULT_MODEL, Source, context_window_tokens, effort_from, model_from,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn context_window_defaults_per_model_family() {
|
||||||
|
assert_eq!(context_window_tokens("claude-haiku-4-5"), 200_000);
|
||||||
|
assert_eq!(context_window_tokens("haiku"), 200_000);
|
||||||
|
assert_eq!(context_window_tokens("claude-sonnet-4-5"), 1_000_000);
|
||||||
|
assert_eq!(context_window_tokens("Claude-Sonnet-4-6"), 1_000_000);
|
||||||
|
assert_eq!(context_window_tokens("sonnet"), 1_000_000);
|
||||||
|
assert_eq!(context_window_tokens("claude-opus-4-1"), 1_000_000);
|
||||||
|
assert_eq!(context_window_tokens("opus"), 1_000_000);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn compiled_default_effort_matches_claude_settings() {
|
fn compiled_default_effort_matches_claude_settings() {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
/// Assemble the system prompt for a given label + pronouns + optional hive /
|
/// Assemble the system prompt for a given label + optional hive /
|
||||||
/// swarm display names. Pure function — no I/O. Splits out from
|
/// swarm display names. Pure function — no I/O. Splits out from
|
||||||
/// [`write_system_prompt`] so the marker logic + substitution is unit-testable
|
/// [`write_system_prompt`] so the marker logic + substitution is unit-testable
|
||||||
/// in isolation. The caller supplies the template body so tests can pass an
|
/// in isolation. The caller supplies the template body so tests can pass an
|
||||||
|
|
@ -21,7 +21,6 @@ use anyhow::{Context, Result};
|
||||||
pub fn render(
|
pub fn render(
|
||||||
template: &str,
|
template: &str,
|
||||||
label: &str,
|
label: &str,
|
||||||
operator_pronouns: &str,
|
|
||||||
hive_name: Option<&str>,
|
hive_name: Option<&str>,
|
||||||
swarm_name: Option<&str>,
|
swarm_name: Option<&str>,
|
||||||
docs_dir: Option<&str>,
|
docs_dir: Option<&str>,
|
||||||
|
|
@ -37,7 +36,6 @@ pub fn render(
|
||||||
let rendered = body
|
let rendered = body
|
||||||
.replace("{label}", label)
|
.replace("{label}", label)
|
||||||
.replace("{qualified_label}", &qualified)
|
.replace("{qualified_label}", &qualified)
|
||||||
.replace("{operator_pronouns}", operator_pronouns)
|
|
||||||
.replace("{hive_identity}", &hive_identity)
|
.replace("{hive_identity}", &hive_identity)
|
||||||
.replace("{swarm_identity}", &swarm_identity);
|
.replace("{swarm_identity}", &swarm_identity);
|
||||||
// When the reference docs are mounted in-container (`services.hyperhive.agent.docs.enable`
|
// When the reference docs are mounted in-container (`services.hyperhive.agent.docs.enable`
|
||||||
|
|
@ -114,7 +112,6 @@ fn parse_close_marker(line: &str) -> Option<&str> {
|
||||||
pub async fn write_system_prompt(_socket: &Path, label: &str) -> Result<PathBuf> {
|
pub async fn write_system_prompt(_socket: &Path, label: &str) -> Result<PathBuf> {
|
||||||
let parent = crate::paths::config_dir();
|
let parent = crate::paths::config_dir();
|
||||||
tokio::fs::create_dir_all(&parent).await.ok();
|
tokio::fs::create_dir_all(&parent).await.ok();
|
||||||
let pronouns = std::env::var("HIVE_OPERATOR_PRONOUNS").unwrap_or_else(|_| "she/her".to_owned());
|
|
||||||
let template_path = hive_sh4re::assets::prompt_template();
|
let template_path = hive_sh4re::assets::prompt_template();
|
||||||
let template = tokio::fs::read_to_string(&template_path)
|
let template = tokio::fs::read_to_string(&template_path)
|
||||||
.await
|
.await
|
||||||
|
|
@ -137,7 +134,6 @@ pub async fn write_system_prompt(_socket: &Path, label: &str) -> Result<PathBuf>
|
||||||
let body = render(
|
let body = render(
|
||||||
&template,
|
&template,
|
||||||
label,
|
label,
|
||||||
&pronouns,
|
|
||||||
hive_name.as_deref(),
|
hive_name.as_deref(),
|
||||||
swarm_name.as_deref(),
|
swarm_name.as_deref(),
|
||||||
docs_dir.as_deref(),
|
docs_dir.as_deref(),
|
||||||
|
|
@ -236,13 +232,11 @@ shared closer
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn render_substitutes_label_and_pronouns() {
|
fn render_substitutes_label_and_keeps_the_pronoun_line() {
|
||||||
// Real template's first agent line — keeps the renderer
|
// Real template's first agent line.
|
||||||
// honest about the {label} / {operator_pronouns} pair the
|
let rendered = render(&PRODUCTION_TEMPLATE, "alice", None, None, None);
|
||||||
// harness already relied on.
|
|
||||||
let rendered = render(&PRODUCTION_TEMPLATE, "alice", "they/them", None, None, None);
|
|
||||||
assert!(rendered.contains("hyperhive agent `alice`"));
|
assert!(rendered.contains("hyperhive agent `alice`"));
|
||||||
assert!(rendered.contains("**they/them** pronouns"));
|
assert!(rendered.contains("uses **she/her** pronouns"));
|
||||||
assert!(!rendered.contains("{label}"));
|
assert!(!rendered.contains("{label}"));
|
||||||
assert!(!rendered.contains("{operator_pronouns}"));
|
assert!(!rendered.contains("{operator_pronouns}"));
|
||||||
}
|
}
|
||||||
|
|
@ -250,7 +244,7 @@ shared closer
|
||||||
#[test]
|
#[test]
|
||||||
fn render_no_role_markers_in_output() {
|
fn render_no_role_markers_in_output() {
|
||||||
// No raw role markers should survive into the rendered prompt.
|
// No raw role markers should survive into the rendered prompt.
|
||||||
let rendered = render(&PRODUCTION_TEMPLATE, "alice", "she/her", None, None, None);
|
let rendered = render(&PRODUCTION_TEMPLATE, "alice", None, None, None);
|
||||||
assert!(!rendered.contains("<!-- role:"));
|
assert!(!rendered.contains("<!-- role:"));
|
||||||
assert!(!rendered.contains("<!-- /role:"));
|
assert!(!rendered.contains("<!-- /role:"));
|
||||||
// Shared tools appear.
|
// Shared tools appear.
|
||||||
|
|
@ -260,13 +254,13 @@ shared closer
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn render_uses_agent_opener() {
|
fn render_uses_agent_opener() {
|
||||||
let rendered = render(&PRODUCTION_TEMPLATE, "alice", "she/her", None, None, None);
|
let rendered = render(&PRODUCTION_TEMPLATE, "alice", None, None, None);
|
||||||
assert!(rendered.starts_with("You are hyperhive agent"));
|
assert!(rendered.starts_with("You are hyperhive agent"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn render_points_agents_at_the_work_dir() {
|
fn render_points_agents_at_the_work_dir() {
|
||||||
let rendered = render(&PRODUCTION_TEMPLATE, "alice", "she/her", None, None, None);
|
let rendered = render(&PRODUCTION_TEMPLATE, "alice", None, None, None);
|
||||||
assert!(rendered.contains("`/agents/alice/work/`"), "{rendered}");
|
assert!(rendered.contains("`/agents/alice/work/`"), "{rendered}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -280,19 +274,12 @@ shared closer
|
||||||
// substitution logic; nothing here depends on the production
|
// substitution logic; nothing here depends on the production
|
||||||
// template's flavor markers.
|
// template's flavor markers.
|
||||||
const IDENTITY_FIXTURE: &str = "\
|
const IDENTITY_FIXTURE: &str = "\
|
||||||
You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity}{swarm_identity} in a multi-agent system. Pronouns: **{operator_pronouns}**.
|
You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity}{swarm_identity} in a multi-agent system.
|
||||||
";
|
";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn render_substitutes_hive_identity_when_set() {
|
fn render_substitutes_hive_identity_when_set() {
|
||||||
let rendered = render(
|
let rendered = render(IDENTITY_FIXTURE, "alice", Some("pr1ma"), None, None);
|
||||||
IDENTITY_FIXTURE,
|
|
||||||
"alice",
|
|
||||||
"she/her",
|
|
||||||
Some("pr1ma"),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
assert!(rendered.contains("on hive `pr1ma`"), "{rendered}");
|
assert!(rendered.contains("on hive `pr1ma`"), "{rendered}");
|
||||||
// swarm clause stays absent when only hive is set.
|
// swarm clause stays absent when only hive is set.
|
||||||
assert!(!rendered.contains("in swarm"));
|
assert!(!rendered.contains("in swarm"));
|
||||||
|
|
@ -303,14 +290,7 @@ You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn render_substitutes_swarm_identity_when_set() {
|
fn render_substitutes_swarm_identity_when_set() {
|
||||||
let rendered = render(
|
let rendered = render(IDENTITY_FIXTURE, "ruth", None, Some("constellat1on"), None);
|
||||||
IDENTITY_FIXTURE,
|
|
||||||
"ruth",
|
|
||||||
"she/her",
|
|
||||||
None,
|
|
||||||
Some("constellat1on"),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
assert!(rendered.contains("in swarm `constellat1on`"));
|
assert!(rendered.contains("in swarm `constellat1on`"));
|
||||||
assert!(!rendered.contains("on hive"));
|
assert!(!rendered.contains("on hive"));
|
||||||
}
|
}
|
||||||
|
|
@ -320,7 +300,6 @@ You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity
|
||||||
let rendered = render(
|
let rendered = render(
|
||||||
IDENTITY_FIXTURE,
|
IDENTITY_FIXTURE,
|
||||||
"iris",
|
"iris",
|
||||||
"she/her",
|
|
||||||
Some("pr1ma"),
|
Some("pr1ma"),
|
||||||
Some("constellat1on"),
|
Some("constellat1on"),
|
||||||
None,
|
None,
|
||||||
|
|
@ -334,7 +313,7 @@ You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity
|
||||||
fn render_omits_identity_when_unset() {
|
fn render_omits_identity_when_unset() {
|
||||||
// None / None must round-trip the non-identity opener verbatim
|
// None / None must round-trip the non-identity opener verbatim
|
||||||
// — single-hive deployments see zero diff.
|
// — single-hive deployments see zero diff.
|
||||||
let rendered = render(IDENTITY_FIXTURE, "alice", "she/her", None, None, None);
|
let rendered = render(IDENTITY_FIXTURE, "alice", None, None, None);
|
||||||
assert!(!rendered.contains("on hive"));
|
assert!(!rendered.contains("on hive"));
|
||||||
assert!(!rendered.contains("in swarm"));
|
assert!(!rendered.contains("in swarm"));
|
||||||
assert!(!rendered.contains("{hive_identity}"));
|
assert!(!rendered.contains("{hive_identity}"));
|
||||||
|
|
@ -347,14 +326,7 @@ You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity
|
||||||
// through `identity::hive_name()` as None (the accessor
|
// through `identity::hive_name()` as None (the accessor
|
||||||
// filters empty), but `render` should still no-op on a
|
// filters empty), but `render` should still no-op on a
|
||||||
// direct `Some("")` from a test fixture or a future caller.
|
// direct `Some("")` from a test fixture or a future caller.
|
||||||
let rendered = render(
|
let rendered = render(IDENTITY_FIXTURE, "alice", Some(""), Some(""), None);
|
||||||
IDENTITY_FIXTURE,
|
|
||||||
"alice",
|
|
||||||
"she/her",
|
|
||||||
Some(""),
|
|
||||||
Some(""),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
assert!(!rendered.contains("on hive"));
|
assert!(!rendered.contains("on hive"));
|
||||||
assert!(!rendered.contains("in swarm"));
|
assert!(!rendered.contains("in swarm"));
|
||||||
}
|
}
|
||||||
|
|
@ -364,7 +336,6 @@ You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity
|
||||||
let rendered = render(
|
let rendered = render(
|
||||||
&PRODUCTION_TEMPLATE,
|
&PRODUCTION_TEMPLATE,
|
||||||
"alice",
|
"alice",
|
||||||
"she/her",
|
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
Some("/run/hive-docs"),
|
Some("/run/hive-docs"),
|
||||||
|
|
@ -380,7 +351,7 @@ You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity
|
||||||
#[test]
|
#[test]
|
||||||
fn render_no_docs_pointer_when_docs_dir_absent_or_empty() {
|
fn render_no_docs_pointer_when_docs_dir_absent_or_empty() {
|
||||||
for docs in [None, Some("")] {
|
for docs in [None, Some("")] {
|
||||||
let rendered = render(&PRODUCTION_TEMPLATE, "alice", "she/her", None, None, docs);
|
let rendered = render(&PRODUCTION_TEMPLATE, "alice", None, None, docs);
|
||||||
assert!(
|
assert!(
|
||||||
!rendered.contains("mounted read-only at"),
|
!rendered.contains("mounted read-only at"),
|
||||||
"docs pointer must not appear for {docs:?}:\n{rendered}"
|
"docs pointer must not appear for {docs:?}:\n{rendered}"
|
||||||
|
|
|
||||||
|
|
@ -264,8 +264,8 @@ pub fn stall_sleep_secs() -> u64 {
|
||||||
/// Resolve the effective context-window size for watermark calculations.
|
/// Resolve the effective context-window size for watermark calculations.
|
||||||
/// Priority order (first wins):
|
/// Priority order (first wins):
|
||||||
/// 1. API-reported window from the last `result` event's `modelUsage.*.contextWindow`.
|
/// 1. API-reported window from the last `result` event's `modelUsage.*.contextWindow`.
|
||||||
/// 2. `HIVE_CONTEXT_WINDOW_TOKENS_*` env vars (Nix-configured per-model defaults).
|
/// 2. `harness_state::context_window_tokens` (per-model defaults, then the
|
||||||
/// 3. Hard fallback: 200 000.
|
/// `HIVE_CONTEXT_WINDOW_TOKENS` override, then 200 000).
|
||||||
///
|
///
|
||||||
/// The API-reported window is the authoritative per-inference active
|
/// The API-reported window is the authoritative per-inference active
|
||||||
/// context limit. It reflects what the model actually enforces — which
|
/// context limit. It reflects what the model actually enforces — which
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ pub(super) struct StateSnapshot {
|
||||||
/// Effective context-window token budget for the current model.
|
/// Effective context-window token budget for the current model.
|
||||||
/// Primary source: API-reported `modelUsage.*.contextWindow` from
|
/// Primary source: API-reported `modelUsage.*.contextWindow` from
|
||||||
/// the last result event (authoritative per-inference active window).
|
/// the last result event (authoritative per-inference active window).
|
||||||
/// Falls back to `HIVE_CONTEXT_WINDOW_TOKENS_*` env vars, then 200 000.
|
/// Falls back to `harness_state::context_window_tokens(model)`.
|
||||||
/// Consumers (e.g. dashboard badge) use this to render ctx-usage %.
|
/// Consumers (e.g. dashboard badge) use this to render ctx-usage %.
|
||||||
context_window_tokens: u64,
|
context_window_tokens: u64,
|
||||||
/// Last-inference token usage from the most recent completed
|
/// Last-inference token usage from the most recent completed
|
||||||
|
|
|
||||||
|
|
@ -67,20 +67,6 @@ pub struct Coordinator {
|
||||||
/// each per-agent flake so the agent's web UI can build the right
|
/// each per-agent flake so the agent's web UI can build the right
|
||||||
/// rebuild-button URL pointing back at the dashboard.
|
/// rebuild-button URL pointing back at the dashboard.
|
||||||
pub dashboard_port: u16,
|
pub dashboard_port: u16,
|
||||||
/// Operator pronouns (free text) — `she/her` by default, set via
|
|
||||||
/// the NixOS module option `services.hive-c0re.operatorPronouns`.
|
|
||||||
/// Reaches each container as the `HIVE_OPERATOR_PRONOUNS` env var
|
|
||||||
/// (injected into `systemd.services.<harness>.environment` by the
|
|
||||||
/// meta flake); the harness substitutes it into the agent /
|
|
||||||
/// manager system prompt at boot.
|
|
||||||
pub operator_pronouns: String,
|
|
||||||
/// Per-model context-window sizes in tokens. Set via the host-level
|
|
||||||
/// `services.hive-c0re.contextWindowTokens` NixOS option; injected
|
|
||||||
/// into each container as `HIVE_CONTEXT_WINDOW_TOKENS_<KEY_UPPER>`
|
|
||||||
/// by the meta flake renderer. The harness uses these to derive
|
|
||||||
/// compaction / auto-reset watermarks and exposes the active value
|
|
||||||
/// on `/api/state` as `context_window_tokens`.
|
|
||||||
pub context_window_tokens: std::collections::HashMap<String, u64>,
|
|
||||||
/// Per-agent systemd `CPUQuota=` value (e.g. `"200%"`). Written into
|
/// Per-agent systemd `CPUQuota=` value (e.g. `"200%"`). Written into
|
||||||
/// the `container@h-<name>.service.d/` drop-in on every spawn/rebuild.
|
/// the `container@h-<name>.service.d/` drop-in on every spawn/rebuild.
|
||||||
pub agent_cpu_quota: String,
|
pub agent_cpu_quota: String,
|
||||||
|
|
@ -100,9 +86,8 @@ pub struct Coordinator {
|
||||||
/// estimate on the ST4TS tab. Set via `services.hyperhive.modelPrices`
|
/// estimate on the ST4TS tab. Set via `services.hyperhive.modelPrices`
|
||||||
/// and passed to `hive-c0re serve --model-prices <json>`. Models not
|
/// and passed to `hive-c0re serve --model-prices <json>`. Models not
|
||||||
/// in the table fall back to the built-in estimate in `hive_stats`.
|
/// in the table fall back to the built-in estimate in `hive_stats`.
|
||||||
/// hive-c0re-local (read only by the `/api/stats-hive` handler), so —
|
/// hive-c0re-local (read only by the `/api/stats-hive` handler), so it
|
||||||
/// unlike `context_window_tokens` — it is *not* part of `HiveEnv` and
|
/// is *not* part of `HiveEnv` and is never injected into containers.
|
||||||
/// is never injected into containers.
|
|
||||||
pub model_prices: crate::hive_stats::PriceTable,
|
pub model_prices: crate::hive_stats::PriceTable,
|
||||||
agents: Mutex<HashMap<String, AgentSocket>>,
|
agents: Mutex<HashMap<String, AgentSocket>>,
|
||||||
/// Tombstone for transients that have JUST been cleared. The
|
/// Tombstone for transients that have JUST been cleared. The
|
||||||
|
|
@ -224,8 +209,6 @@ pub struct HiveEnv {
|
||||||
/// alive host-side by the module that resolved it.
|
/// alive host-side by the module that resolved it.
|
||||||
pub claude_code_path: Option<String>,
|
pub claude_code_path: Option<String>,
|
||||||
pub dashboard_port: u16,
|
pub dashboard_port: u16,
|
||||||
pub operator_pronouns: String,
|
|
||||||
pub context_window_tokens: std::collections::HashMap<String, u64>,
|
|
||||||
/// Per-agent systemd `CPUQuota=` value (e.g. `"200%"`).
|
/// Per-agent systemd `CPUQuota=` value (e.g. `"200%"`).
|
||||||
pub agent_cpu_quota: String,
|
pub agent_cpu_quota: String,
|
||||||
/// Per-agent systemd `MemoryMax=` value (e.g. `"4G"`).
|
/// Per-agent systemd `MemoryMax=` value (e.g. `"4G"`).
|
||||||
|
|
@ -247,12 +230,6 @@ impl Default for HiveEnv {
|
||||||
nixpkgs_flake: String::new(),
|
nixpkgs_flake: String::new(),
|
||||||
claude_code_path: None,
|
claude_code_path: None,
|
||||||
dashboard_port: 7000,
|
dashboard_port: 7000,
|
||||||
operator_pronouns: "she/her".to_string(),
|
|
||||||
context_window_tokens: std::collections::HashMap::from([
|
|
||||||
("haiku".to_string(), 200_000),
|
|
||||||
("sonnet".to_string(), 1_000_000),
|
|
||||||
("opus".to_string(), 1_000_000),
|
|
||||||
]),
|
|
||||||
agent_cpu_quota: "200%".to_string(),
|
agent_cpu_quota: "200%".to_string(),
|
||||||
agent_memory_max: "4G".to_string(),
|
agent_memory_max: "4G".to_string(),
|
||||||
// Slightly below the kernel default of 100, so agent
|
// Slightly below the kernel default of 100, so agent
|
||||||
|
|
@ -317,9 +294,7 @@ mod serve_config_tests {
|
||||||
assert_eq!(sc.env.hyperhive_flake, "/x");
|
assert_eq!(sc.env.hyperhive_flake, "/x");
|
||||||
assert_eq!(sc.env.dashboard_port, 1234);
|
assert_eq!(sc.env.dashboard_port, 1234);
|
||||||
// omitted → HiveEnv::default()
|
// omitted → HiveEnv::default()
|
||||||
assert_eq!(sc.env.operator_pronouns, "she/her");
|
|
||||||
assert_eq!(sc.env.agent_cpu_quota, "200%");
|
assert_eq!(sc.env.agent_cpu_quota, "200%");
|
||||||
assert_eq!(sc.env.context_window_tokens.get("sonnet"), Some(&1_000_000));
|
|
||||||
assert!(sc.model_prices.contains_key("opus"));
|
assert!(sc.model_prices.contains_key("opus"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -432,8 +407,6 @@ impl Coordinator {
|
||||||
nixpkgs_flake,
|
nixpkgs_flake,
|
||||||
claude_code_path,
|
claude_code_path,
|
||||||
dashboard_port,
|
dashboard_port,
|
||||||
operator_pronouns,
|
|
||||||
context_window_tokens,
|
|
||||||
agent_cpu_quota,
|
agent_cpu_quota,
|
||||||
agent_memory_max,
|
agent_memory_max,
|
||||||
agent_cpu_weight,
|
agent_cpu_weight,
|
||||||
|
|
@ -473,8 +446,6 @@ impl Coordinator {
|
||||||
nixpkgs_flake,
|
nixpkgs_flake,
|
||||||
claude_code_path,
|
claude_code_path,
|
||||||
dashboard_port,
|
dashboard_port,
|
||||||
operator_pronouns,
|
|
||||||
context_window_tokens,
|
|
||||||
agent_cpu_quota,
|
agent_cpu_quota,
|
||||||
agent_memory_max,
|
agent_memory_max,
|
||||||
agent_cpu_weight,
|
agent_cpu_weight,
|
||||||
|
|
@ -506,8 +477,6 @@ impl Coordinator {
|
||||||
nixpkgs_flake: self.nixpkgs_flake.clone(),
|
nixpkgs_flake: self.nixpkgs_flake.clone(),
|
||||||
claude_code_path: self.claude_code_path.clone(),
|
claude_code_path: self.claude_code_path.clone(),
|
||||||
dashboard_port: self.dashboard_port,
|
dashboard_port: self.dashboard_port,
|
||||||
operator_pronouns: self.operator_pronouns.clone(),
|
|
||||||
context_window_tokens: self.context_window_tokens.clone(),
|
|
||||||
agent_cpu_quota: self.agent_cpu_quota.clone(),
|
agent_cpu_quota: self.agent_cpu_quota.clone(),
|
||||||
agent_memory_max: self.agent_memory_max.clone(),
|
agent_memory_max: self.agent_memory_max.clone(),
|
||||||
agent_cpu_weight: self.agent_cpu_weight,
|
agent_cpu_weight: self.agent_cpu_weight,
|
||||||
|
|
|
||||||
|
|
@ -90,17 +90,6 @@ enum Cmd {
|
||||||
/// Override: dashboard HTTP port.
|
/// Override: dashboard HTTP port.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
dashboard_port: Option<u16>,
|
dashboard_port: Option<u16>,
|
||||||
/// Override: operator pronouns (free text). Threaded into each
|
|
||||||
/// container's harness via `HIVE_OPERATOR_PRONOUNS`.
|
|
||||||
#[arg(long)]
|
|
||||||
operator_pronouns: Option<String>,
|
|
||||||
/// Override: per-model context-window sizes, as a JSON object
|
|
||||||
/// mapping model-family short name to token count. Threaded into
|
|
||||||
/// each container as `HIVE_CONTEXT_WINDOW_TOKENS_<KEY_UPPER>` env
|
|
||||||
/// vars. Set via the `services.hive-c0re.contextWindowTokens`
|
|
||||||
/// NixOS option.
|
|
||||||
#[arg(long)]
|
|
||||||
context_window_tokens: Option<String>,
|
|
||||||
/// Override: systemd `CPUQuota=` applied to every agent container
|
/// Override: systemd `CPUQuota=` applied to every agent container
|
||||||
/// via a drop-in (e.g. `"200%"` = 2 cores). Set via
|
/// via a drop-in (e.g. `"200%"` = 2 cores). Set via
|
||||||
/// `services.hyperhive.agentCpuQuota`.
|
/// `services.hyperhive.agentCpuQuota`.
|
||||||
|
|
@ -150,8 +139,6 @@ async fn main() -> Result<()> {
|
||||||
hyperhive_flake,
|
hyperhive_flake,
|
||||||
nixpkgs_flake,
|
nixpkgs_flake,
|
||||||
dashboard_port,
|
dashboard_port,
|
||||||
operator_pronouns,
|
|
||||||
context_window_tokens,
|
|
||||||
agent_cpu_quota,
|
agent_cpu_quota,
|
||||||
agent_memory_max,
|
agent_memory_max,
|
||||||
agent_cpu_weight,
|
agent_cpu_weight,
|
||||||
|
|
@ -180,13 +167,6 @@ async fn main() -> Result<()> {
|
||||||
if let Some(v) = dashboard_port {
|
if let Some(v) = dashboard_port {
|
||||||
sc.env.dashboard_port = v;
|
sc.env.dashboard_port = v;
|
||||||
}
|
}
|
||||||
if let Some(v) = operator_pronouns {
|
|
||||||
sc.env.operator_pronouns = v;
|
|
||||||
}
|
|
||||||
if let Some(v) = context_window_tokens {
|
|
||||||
sc.env.context_window_tokens =
|
|
||||||
serde_json::from_str(&v).context("--context-window-tokens: invalid JSON")?;
|
|
||||||
}
|
|
||||||
if let Some(v) = agent_cpu_quota {
|
if let Some(v) = agent_cpu_quota {
|
||||||
sc.env.agent_cpu_quota = v;
|
sc.env.agent_cpu_quota = v;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -334,25 +334,16 @@ fn render_hive_flake(hive: &HiveEnv, agents: &[AgentSpec]) -> String {
|
||||||
&hive.nixpkgs_flake,
|
&hive.nixpkgs_flake,
|
||||||
hive.claude_code_path.as_deref(),
|
hive.claude_code_path.as_deref(),
|
||||||
hive.dashboard_port,
|
hive.dashboard_port,
|
||||||
&hive.operator_pronouns,
|
|
||||||
&hive.context_window_tokens,
|
|
||||||
agents,
|
agents,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(
|
|
||||||
clippy::too_many_arguments,
|
|
||||||
reason = "many genuine flake inputs (source flakes, port, pronouns, tokens, \
|
|
||||||
agents, forge base); a params struct would just move the same fields"
|
|
||||||
)]
|
|
||||||
fn render_flake(
|
fn render_flake(
|
||||||
hyperhive_flake: &str,
|
hyperhive_flake: &str,
|
||||||
docs_flake: &str,
|
docs_flake: &str,
|
||||||
nixpkgs_flake: &str,
|
nixpkgs_flake: &str,
|
||||||
claude_code_path: Option<&str>,
|
claude_code_path: Option<&str>,
|
||||||
dashboard_port: u16,
|
dashboard_port: u16,
|
||||||
operator_pronouns: &str,
|
|
||||||
context_window_tokens: &std::collections::HashMap<String, u64>,
|
|
||||||
agents: &[AgentSpec],
|
agents: &[AgentSpec],
|
||||||
) -> String {
|
) -> String {
|
||||||
render_flake_with_lookup(
|
render_flake_with_lookup(
|
||||||
|
|
@ -361,8 +352,6 @@ fn render_flake(
|
||||||
nixpkgs_flake,
|
nixpkgs_flake,
|
||||||
claude_code_path,
|
claude_code_path,
|
||||||
dashboard_port,
|
dashboard_port,
|
||||||
operator_pronouns,
|
|
||||||
context_window_tokens,
|
|
||||||
agents,
|
agents,
|
||||||
agent_canonical_inputs,
|
agent_canonical_inputs,
|
||||||
)
|
)
|
||||||
|
|
@ -742,10 +731,8 @@ fn agent_canonical_inputs(name: &str) -> Vec<&'static str> {
|
||||||
/// agent flake-lock introspection.
|
/// agent flake-lock introspection.
|
||||||
#[allow(
|
#[allow(
|
||||||
clippy::too_many_lines,
|
clippy::too_many_lines,
|
||||||
clippy::too_many_arguments,
|
|
||||||
reason = "templated string-builder for the meta flake — the length is one \
|
reason = "templated string-builder for the meta flake — the length is one \
|
||||||
contiguous fmt block, splitting it would just hide the shape; the \
|
contiguous fmt block, splitting it would just hide the shape"
|
||||||
args mirror the host-level config the flake is rendered from"
|
|
||||||
)]
|
)]
|
||||||
fn render_flake_with_lookup<F>(
|
fn render_flake_with_lookup<F>(
|
||||||
hyperhive_flake: &str,
|
hyperhive_flake: &str,
|
||||||
|
|
@ -753,8 +740,6 @@ fn render_flake_with_lookup<F>(
|
||||||
nixpkgs_flake: &str,
|
nixpkgs_flake: &str,
|
||||||
claude_code_path: Option<&str>,
|
claude_code_path: Option<&str>,
|
||||||
dashboard_port: u16,
|
dashboard_port: u16,
|
||||||
operator_pronouns: &str,
|
|
||||||
context_window_tokens: &std::collections::HashMap<String, u64>,
|
|
||||||
agents: &[AgentSpec],
|
agents: &[AgentSpec],
|
||||||
lookup: F,
|
lookup: F,
|
||||||
) -> String
|
) -> String
|
||||||
|
|
@ -830,13 +815,9 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out.push_str(" };\n outputs =\n { self, hyperhive, ... }@inputs:\n let\n");
|
out.push_str(" };\n outputs =\n { self, hyperhive, ... }@inputs:\n let\n");
|
||||||
// Free-text operator string — escape backslash + double-quote so a
|
|
||||||
// pronouns value like `he/him \ "rare"` round-trips into a valid
|
|
||||||
// nix string literal without breaking the flake.
|
|
||||||
let pronouns_escaped = operator_pronouns.replace('\\', "\\\\").replace('"', "\\\"");
|
|
||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
out,
|
out,
|
||||||
" dashboardPort = {dashboard_port};\n operatorPronouns = \"{pronouns_escaped}\";\n mkAgent = {{ name, isManager, port }}:"
|
" dashboardPort = {dashboard_port};\n mkAgent = {{ name, isManager, port }}:"
|
||||||
);
|
);
|
||||||
out.push_str(
|
out.push_str(
|
||||||
r#" let
|
r#" let
|
||||||
|
|
@ -1025,20 +1006,8 @@ where
|
||||||
HIVE_PORT = toString port;
|
HIVE_PORT = toString port;
|
||||||
HIVE_LABEL = name;
|
HIVE_LABEL = name;
|
||||||
HIVE_DASHBOARD_PORT = toString dashboardPort;
|
HIVE_DASHBOARD_PORT = toString dashboardPort;
|
||||||
HIVE_OPERATOR_PRONOUNS = operatorPronouns;",
|
",
|
||||||
);
|
);
|
||||||
// Per-model context-window env vars declared in the host-level
|
|
||||||
// `services.hive-c0re.contextWindowTokens` option. Use a sorted
|
|
||||||
// iterator for deterministic flake output (no spurious git diffs).
|
|
||||||
let mut sorted_tokens: Vec<(&String, &u64)> = context_window_tokens.iter().collect();
|
|
||||||
sorted_tokens.sort_by_key(|(k, _)| k.as_str());
|
|
||||||
for (key, val) in &sorted_tokens {
|
|
||||||
let upper_key = key.to_ascii_uppercase();
|
|
||||||
let _ = writeln!(
|
|
||||||
out,
|
|
||||||
" HIVE_CONTEXT_WINDOW_TOKENS_{upper_key} = \"{val}\";"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Forwarded env vars — picked up from hive-c0re's own systemd unit
|
// Forwarded env vars — picked up from hive-c0re's own systemd unit
|
||||||
// env (`services.hyperhive.*` options flow through the hyperhive
|
// env (`services.hyperhive.*` options flow through the hyperhive
|
||||||
// NixOS module into the host process). We copy whatever's set into
|
// NixOS module into the host process). We copy whatever's set into
|
||||||
|
|
@ -1314,8 +1283,6 @@ mod tests {
|
||||||
"path:/nix/store/aaaa-nixpkgs-source",
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
"she/her",
|
|
||||||
&std::collections::HashMap::new(),
|
|
||||||
&[sample_spec("alice", false, 9001)],
|
&[sample_spec("alice", false, 9001)],
|
||||||
);
|
);
|
||||||
// nixpkgs is a top-level input with an explicit URL; hyperhive
|
// nixpkgs is a top-level input with an explicit URL; hyperhive
|
||||||
|
|
@ -1360,8 +1327,6 @@ mod tests {
|
||||||
"path:/nix/store/aaaa-nixpkgs-source",
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
"she/her",
|
|
||||||
&std::collections::HashMap::new(),
|
|
||||||
&[sample_spec("alice", false, 9001)],
|
&[sample_spec("alice", false, 9001)],
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -1378,8 +1343,6 @@ mod tests {
|
||||||
"path:/nix/store/aaaa-nixpkgs-source",
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
"she/her",
|
|
||||||
&std::collections::HashMap::new(),
|
|
||||||
&[sample_spec("alice", false, 9001)],
|
&[sample_spec("alice", false, 9001)],
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -1418,8 +1381,6 @@ mod tests {
|
||||||
"path:/nix/store/aaaa-nixpkgs-source",
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
Some("/nix/store/cccc-claude-code-2.1.220"),
|
Some("/nix/store/cccc-claude-code-2.1.220"),
|
||||||
8000,
|
8000,
|
||||||
"she/her",
|
|
||||||
&std::collections::HashMap::new(),
|
|
||||||
&[sample_spec("alice", false, 9001)],
|
&[sample_spec("alice", false, 9001)],
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -1446,8 +1407,6 @@ mod tests {
|
||||||
"path:/nix/store/aaaa-nixpkgs-source",
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
"she/her",
|
|
||||||
&std::collections::HashMap::new(),
|
|
||||||
&[sample_spec("alice", false, 9001)],
|
&[sample_spec("alice", false, 9001)],
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -1466,8 +1425,6 @@ mod tests {
|
||||||
"",
|
"",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
"she/her",
|
|
||||||
&std::collections::HashMap::new(),
|
|
||||||
&[sample_spec("alice", false, 9001)],
|
&[sample_spec("alice", false, 9001)],
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -1496,8 +1453,6 @@ mod tests {
|
||||||
"path:/nix/store/aaaa-nixpkgs-source",
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
"she/her",
|
|
||||||
&std::collections::HashMap::new(),
|
|
||||||
&[
|
&[
|
||||||
sample_spec("argus", false, 9001),
|
sample_spec("argus", false, 9001),
|
||||||
sample_spec("bitburner", false, 9002),
|
sample_spec("bitburner", false, 9002),
|
||||||
|
|
@ -1529,8 +1484,6 @@ mod tests {
|
||||||
"path:/nix/store/aaaa-nixpkgs-source",
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
"she/her",
|
|
||||||
&std::collections::HashMap::new(),
|
|
||||||
&[sample_spec("alice", false, 9001)],
|
&[sample_spec("alice", false, 9001)],
|
||||||
|_| Vec::new(),
|
|_| Vec::new(),
|
||||||
);
|
);
|
||||||
|
|
@ -1567,8 +1520,6 @@ mod tests {
|
||||||
"path:/nix/store/aaaa-nixpkgs-source",
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
"she/her",
|
|
||||||
&std::collections::HashMap::new(),
|
|
||||||
&[sample_spec("alice", false, 9001)],
|
&[sample_spec("alice", false, 9001)],
|
||||||
);
|
);
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
@ -1612,8 +1563,6 @@ mod tests {
|
||||||
"path:/nix/store/aaaa-nixpkgs-source",
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
"she/her",
|
|
||||||
&std::collections::HashMap::new(),
|
|
||||||
&[sample_spec("alice", false, 9001)],
|
&[sample_spec("alice", false, 9001)],
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
@ -1792,8 +1741,6 @@ mod tests {
|
||||||
"path:/nix/store/aaaa-nixpkgs-source",
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
"she/her",
|
|
||||||
&std::collections::HashMap::new(),
|
|
||||||
&[sample_spec("alice", false, 9001)],
|
&[sample_spec("alice", false, 9001)],
|
||||||
);
|
);
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
@ -1828,8 +1775,6 @@ mod tests {
|
||||||
"path:/nix/store/aaaa-nixpkgs-source",
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
"she/her",
|
|
||||||
&std::collections::HashMap::new(),
|
|
||||||
&[sample_spec("alice", false, 9001)],
|
&[sample_spec("alice", false, 9001)],
|
||||||
);
|
);
|
||||||
let want = format!(
|
let want = format!(
|
||||||
|
|
@ -1872,8 +1817,6 @@ mod tests {
|
||||||
"path:/nix/store/aaaa-nixpkgs-source",
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
"she/her",
|
|
||||||
&std::collections::HashMap::new(),
|
|
||||||
&[sample_spec("alice", false, 9001)],
|
&[sample_spec("alice", false, 9001)],
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
@ -1964,8 +1907,6 @@ mod tests {
|
||||||
"path:/nix/store/aaaa-nixpkgs-source",
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
"she/her",
|
|
||||||
&std::collections::HashMap::new(),
|
|
||||||
&[sample_spec("alice", false, 9001)],
|
&[sample_spec("alice", false, 9001)],
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
@ -2038,8 +1979,6 @@ mod tests {
|
||||||
"path:/nix/store/aaaa-nixpkgs-source",
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
"she/her",
|
|
||||||
&std::collections::HashMap::new(),
|
|
||||||
&[sample_spec("alice", false, 9001)],
|
&[sample_spec("alice", false, 9001)],
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -70,11 +70,9 @@ in
|
||||||
`swarmctl agent inference set`) wins over this option.
|
`swarmctl agent inference set`) wins over this option.
|
||||||
|
|
||||||
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). The
|
||||||
window sizes are looked up at runtime from the
|
harness takes the context window from the model's own report after
|
||||||
`HIVE_CONTEXT_WINDOW_TOKENS_<KEY_UPPER>` env vars injected by the
|
each turn, and from a per-family default before the first.
|
||||||
meta flake; override sizes via `services.hyperhive.c0re.contextWindowTokens`
|
|
||||||
on the host.
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -280,9 +280,6 @@ in
|
||||||
# here so both the harness binary and any user-shell `cargo run`
|
# here so both the harness binary and any user-shell `cargo run`
|
||||||
# inside the container resolve them from the same path.
|
# inside the container resolve them from the same path.
|
||||||
# SHELL must be set so claude's Bash tool finds a POSIX shell.
|
# SHELL must be set so claude's Bash tool finds a POSIX shell.
|
||||||
# HIVE_CONTEXT_WINDOW_TOKENS_* are injected by the meta flake from the
|
|
||||||
# host-level `services.hyperhive.c0re.contextWindowTokens` option — not
|
|
||||||
# set here.
|
|
||||||
environment.variables = {
|
environment.variables = {
|
||||||
HIVE_ASSETS_DIR = "${config.services.hyperhive.agent.packages.assets}/share/hyperhive";
|
HIVE_ASSETS_DIR = "${config.services.hyperhive.agent.packages.assets}/share/hyperhive";
|
||||||
SHELL = "${pkgs.bashInteractive}/bin/bash";
|
SHELL = "${pkgs.bashInteractive}/bin/bash";
|
||||||
|
|
|
||||||
46
nix/host-modules/c0re-options-removed.nix
Normal file
46
nix/host-modules/c0re-options-removed.nix
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Removal shim for `services.hyperhive.c0re.operatorPronouns` and
|
||||||
|
# `services.hyperhive.c0re.contextWindowTokens`.
|
||||||
|
#
|
||||||
|
# A definition of either is ignored and produces a warning, so a host that
|
||||||
|
# still sets one keeps evaluating. The agents' system prompt
|
||||||
|
# (hive-agent/prompts/system.md) names the operator's pronouns itself, and
|
||||||
|
# the harness (hive-agent/src/harness_state.rs) holds the per-model context
|
||||||
|
# windows it uses until a turn reports the model's own.
|
||||||
|
#
|
||||||
|
# Self-contained and deletable, like ./swarm-peers-removed.nix: one file to
|
||||||
|
# remove when the deprecation window closes.
|
||||||
|
{ lib, config, ... }:
|
||||||
|
let
|
||||||
|
cfg = config.services.hyperhive.c0re;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.hyperhive.c0re = {
|
||||||
|
operatorPronouns = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
visible = false;
|
||||||
|
internal = true;
|
||||||
|
description = "Removed and ignored. `null` is \"not set\".";
|
||||||
|
};
|
||||||
|
contextWindowTokens = lib.mkOption {
|
||||||
|
type = lib.types.nullOr (lib.types.attrsOf lib.types.int);
|
||||||
|
default = null;
|
||||||
|
visible = false;
|
||||||
|
internal = true;
|
||||||
|
description = "Removed and ignored. `null` is \"not set\".";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config.warnings =
|
||||||
|
lib.optional (cfg.operatorPronouns != null) ''
|
||||||
|
services.hyperhive.c0re.operatorPronouns is removed and ignored: the
|
||||||
|
operator's pronouns will be a per-operator profile setting. Until then
|
||||||
|
every agent's system prompt says she/her. Remove this setting.
|
||||||
|
''
|
||||||
|
++ lib.optional (cfg.contextWindowTokens != null) ''
|
||||||
|
services.hyperhive.c0re.contextWindowTokens is removed and ignored: the
|
||||||
|
agent harness uses the context window the model reports after each
|
||||||
|
turn, and before the first one its own defaults (haiku 200000, sonnet
|
||||||
|
1000000, opus 1000000 tokens). Remove this setting.
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
|
@ -85,6 +85,7 @@
|
||||||
./swarm-victoriametrics.nix
|
./swarm-victoriametrics.nix
|
||||||
./swarm-wireguard.nix
|
./swarm-wireguard.nix
|
||||||
./swarm.nix
|
./swarm.nix
|
||||||
|
./c0re-options-removed.nix
|
||||||
./host-ca-removed.nix
|
./host-ca-removed.nix
|
||||||
./nats-callout-removed.nix
|
./nats-callout-removed.nix
|
||||||
./swarm-required-services.nix
|
./swarm-required-services.nix
|
||||||
|
|
|
||||||
|
|
@ -112,8 +112,6 @@ let
|
||||||
# that caused it.
|
# that caused it.
|
||||||
claude_code_path = cfg.claudeCodePackage;
|
claude_code_path = cfg.claudeCodePackage;
|
||||||
dashboard_port = cfg.dashboardPort;
|
dashboard_port = cfg.dashboardPort;
|
||||||
operator_pronouns = cfg.operatorPronouns;
|
|
||||||
context_window_tokens = cfg.contextWindowTokens;
|
|
||||||
agent_cpu_quota = cfg.agentCpuQuota;
|
agent_cpu_quota = cfg.agentCpuQuota;
|
||||||
agent_memory_max = cfg.agentMemoryMax;
|
agent_memory_max = cfg.agentMemoryMax;
|
||||||
agent_cpu_weight = cfg.agentCpuWeight;
|
agent_cpu_weight = cfg.agentCpuWeight;
|
||||||
|
|
|
||||||
|
|
@ -171,21 +171,6 @@
|
||||||
default = 7000;
|
default = 7000;
|
||||||
description = "TCP port the hive-c0re dashboard listens on.";
|
description = "TCP port the hive-c0re dashboard listens on.";
|
||||||
};
|
};
|
||||||
operatorPronouns = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "she/her";
|
|
||||||
example = "they/them";
|
|
||||||
description = ''
|
|
||||||
Operator pronouns, free text. Threaded into every agent
|
|
||||||
container as the `HIVE_OPERATOR_PRONOUNS` env var; the
|
|
||||||
harness substitutes it into the agent / manager system
|
|
||||||
prompt at boot so claude refers to the operator naturally
|
|
||||||
in third person ("ask her", "tell them", etc.). Changes
|
|
||||||
propagate to running agents on the next `↻ R3BU1LD` —
|
|
||||||
forwards as a meta flake env-var bump, no per-agent
|
|
||||||
approval needed.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
preBuildAgentTemplates = lib.mkOption {
|
preBuildAgentTemplates = lib.mkOption {
|
||||||
type = lib.types.bool;
|
type = lib.types.bool;
|
||||||
default = false;
|
default = false;
|
||||||
|
|
@ -212,31 +197,6 @@
|
||||||
store.
|
store.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
contextWindowTokens = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf lib.types.int;
|
|
||||||
default = {
|
|
||||||
haiku = 200000;
|
|
||||||
sonnet = 1000000;
|
|
||||||
opus = 1000000;
|
|
||||||
};
|
|
||||||
example = {
|
|
||||||
haiku = 150000;
|
|
||||||
sonnet = 900000;
|
|
||||||
};
|
|
||||||
description = ''
|
|
||||||
Per-model context-window sizes in tokens. Each key is a
|
|
||||||
model-family short name matched case-insensitively as a
|
|
||||||
substring of the active model name at runtime (e.g. `"sonnet"`
|
|
||||||
matches `"claude-sonnet-4-5"`). The defaults cover the known
|
|
||||||
Anthropic families; add entries for new models or override
|
|
||||||
existing ones here to change the window for all agents at once.
|
|
||||||
|
|
||||||
Passed to `hive-c0re serve` as JSON and injected into every
|
|
||||||
container's harness service environment as
|
|
||||||
`HIVE_CONTEXT_WINDOW_TOKENS_<KEY_UPPER>`. Changes propagate
|
|
||||||
on the next `↻ R3BU1LD` — no per-agent approval needed.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
modelPrices = lib.mkOption {
|
modelPrices = lib.mkOption {
|
||||||
type = lib.types.attrsOf (
|
type = lib.types.attrsOf (
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,13 @@ let
|
||||||
inherit (shared) forgeHere;
|
inherit (shared) forgeHere;
|
||||||
|
|
||||||
# Every hive defaults the agents' queue address to the queue's name, so the
|
# Every hive defaults the agents' queue address to the queue's name, so the
|
||||||
# only hive without one is a hive told to have none.
|
# only hive without one is a hive told to have none. Also the host still
|
||||||
noAgentQueue = hiveWithController { deploy.hive-controller.queue.agentNatsUrl = null; };
|
# setting the two removed `c0re` options; no case on its queue reads them.
|
||||||
|
noAgentQueue = hiveWithController {
|
||||||
|
deploy.hive-controller.queue.agentNatsUrl = null;
|
||||||
|
c0re.operatorPronouns = "they/them";
|
||||||
|
c0re.contextWindowTokens.sonnet = 900000;
|
||||||
|
};
|
||||||
|
|
||||||
# On the store's host, which mints the runner's store identity.
|
# On the store's host, which mints the runner's store identity.
|
||||||
withCi = hive {
|
withCi = hive {
|
||||||
|
|
@ -237,6 +242,29 @@ let
|
||||||
in
|
in
|
||||||
!(e ? HIVE_AGENT_NATS_URL) && !(e ? HIVE_AGENT_OIDC_TOKEN_ENDPOINT);
|
!(e ? HIVE_AGENT_NATS_URL) && !(e ? HIVE_AGENT_OIDC_TOKEN_ENDPOINT);
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
# Control: `bare` sets neither option, and its serve.json carries the
|
||||||
|
# dashboard port, so the key lookup reads a real document.
|
||||||
|
name = "a definition of a removed c0re option evaluates, warns, and reaches no serve.json";
|
||||||
|
ok =
|
||||||
|
let
|
||||||
|
warned =
|
||||||
|
m: option:
|
||||||
|
lib.any (w: lib.hasInfix "services.hyperhive.c0re.${option} is removed and ignored" w) m.warnings;
|
||||||
|
options = [
|
||||||
|
"operatorPronouns"
|
||||||
|
"contextWindowTokens"
|
||||||
|
];
|
||||||
|
hasKey = m: key: lib.hasInfix "\"${key}\":" m.environment.etc."hyperhive/serve.json".text;
|
||||||
|
noRemovedKeys =
|
||||||
|
m:
|
||||||
|
hasKey m "dashboard_port" && !(hasKey m "operator_pronouns") && !(hasKey m "context_window_tokens");
|
||||||
|
in
|
||||||
|
lib.all (warned noAgentQueue) options
|
||||||
|
&& !(lib.any (warned bare) options)
|
||||||
|
&& noRemovedKeys noAgentQueue
|
||||||
|
&& noRemovedKeys bare;
|
||||||
|
}
|
||||||
{
|
{
|
||||||
# Where the store is and where the agent is told it is, one agreement
|
# Where the store is and where the agent is told it is, one agreement
|
||||||
# spanning two modules. Asserted against the hive's own `BAO_ADDR`
|
# spanning two modules. Asserted against the hive's own `BAO_ADDR`
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue