Watch
0
0
Fork
You've already forked hyperhive
0

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:
atlas 2026-10-16 17:18:23 +02:00 • committed by mara
commit 4b5707768c
18 changed files with 151 additions and 258 deletions

View file

@ -136,8 +136,6 @@ covers spreading a swarm across hosts.
swarm.domain = "hive.example"; # the swarm's DNS domain
hiveName = "pr1ma"; # this hive's label in it
swarm.hives.pr1ma = { }; # the directory: just us
# c0re.operatorPronouns = "they/them"; # default "she/her"
};
# ... the rest of your host config

View file

@ -61,16 +61,15 @@ clearing it goes back to the local value →
context-window size per-model. Resolution order (first
match wins):
1. `HIVE_CONTEXT_WINDOW_TOKENS_<KEY>` env var, where `KEY`
(lowercased) is a substring of the active model name. Injected
by the meta flake from `services.hyperhive.c0re.contextWindowTokens`
(host-level NixOS option, defaults: haiku=200k, sonnet=1M,
opus=1M). Override these for all agents at once without a
per-agent config change.
1. The harness's per-family defaults, matched when the family name
is a case-insensitive substring of the active model name:
haiku=200k, sonnet=1M, opus=1M.
2. `HIVE_CONTEXT_WINDOW_TOKENS` — single global override for any
model (useful in dev / test).
3. Hard fallback: `200_000` (conservative; only reached outside
NixOS where the env vars aren't set).
other model (useful in dev / test).
3. Hard fallback: `200_000`.
Once a turn completes, the window the model reports
(`modelUsage.*.contextWindow`) replaces this value.
The effective window drives watermarks; the harness exposes it at
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`:
HTML-comment markers (`<!-- role:agent -->...<!-- /role:agent -->`,
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}`
(hive-qualified `name@domain` form), `{operator_pronouns}`,
(hive-qualified `name@domain` form),
`{hive_identity}` (for example `` on hive `pr1ma` ``; empty when
`HYPERHIVE_HIVE_NAME` is unset), and `{swarm_identity}` (same
shape for the swarm). Pronouns come from `HIVE_OPERATOR_PRONOUNS`
env (set by the meta flake from
`services.hyperhive.c0re.operatorPronouns`, default `she/her`).
shape for the swarm). The template itself names the operator's
pronouns (`she/her`); no option sets them.
When you enable `services.hyperhive.agent.docs.enable`, `HIVE_DOCS_DIR` is present
in the environment and `render()` appends a one-sentence pointer
telling the agent where the docs live.

View file

@ -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:

View file

@ -367,10 +367,10 @@ pub struct Bus {
/// Per-inference context-window size as reported by the Anthropic API
/// in the stream-json `result` event (`modelUsage.*.contextWindow`).
/// 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
/// limit the model enforces, which may differ from what the operator
/// configured (e.g. 200 k active window on a 1 M cache-enabled model).
/// limit the model enforces, which may differ from the default
/// (e.g. 200 k active window on a 1 M cache-enabled model).
api_context_window: Arc<Mutex<Option<u64>>>,
/// Resolved model id from the most recent turn's `assistant` events
/// (the API-echoed `message.model`, e.g. `claude-opus-4-8`), as

View file

@ -237,33 +237,31 @@ pub fn configured_effort() -> Option<&'static str> {
.map(|s| &*Box::leak(s.into_boxed_str()))
}
/// Context-window size in tokens for a given model name.
///
/// Canonical per-model sizes are declared in the harness nix modules as
/// `hyperhive.contextWindowTokens` and injected as
/// `HIVE_CONTEXT_WINDOW_TOKENS_<KEY_UPPER>` env vars — so this function
/// normally just reads them. The Rust code carries no model knowledge;
/// updating model families only requires a Nix change.
/// Context-window size in tokens per model family, keyed by a substring
/// of the model name (`"sonnet"` matches `"claude-sonnet-4-5"`).
const CONTEXT_WINDOW_DEFAULTS: [(&str, u64); 3] = [
("haiku", 200_000),
("sonnet", 1_000_000),
("opus", 1_000_000),
];
/// 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):
/// 1. `HIVE_CONTEXT_WINDOW_TOKENS_<KEY>` — key (lowercased) is a
/// substring of the active model name. Populated by the Nix default
/// map for all known families; add/override in `agent.nix`.
/// 1. [`CONTEXT_WINDOW_DEFAULTS`] — the family key, case-insensitively,
/// is a substring of the model name.
/// 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]
pub fn context_window_tokens(model: &str) -> u64 {
let m = model.to_ascii_lowercase();
// Per-model env vars set by `hyperhive.contextWindowTokens` in Nix.
for (key, val) in std::env::vars() {
if let Some(suffix) = key.strip_prefix("HIVE_CONTEXT_WINDOW_TOKENS_")
&& !suffix.is_empty()
&& m.contains(&suffix.to_ascii_lowercase())
&& let Ok(v) = val.trim().parse::<u64>()
&& v > 0
{
return v;
}
if let Some((_, tokens)) = CONTEXT_WINDOW_DEFAULTS
.iter()
.find(|(family, _)| m.contains(family))
{
return *tokens;
}
// Global override (single value, any model).
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;
}
// Hard fallback for dev/test outside NixOS where env vars aren't set.
200_000
}
#[cfg(test)]
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]
fn compiled_default_effort_matches_claude_settings() {

View file

@ -8,7 +8,7 @@ use std::path::{Path, PathBuf};
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
/// [`write_system_prompt`] so the marker logic + substitution is unit-testable
/// in isolation. The caller supplies the template body so tests can pass an
@ -21,7 +21,6 @@ use anyhow::{Context, Result};
pub fn render(
template: &str,
label: &str,
operator_pronouns: &str,
hive_name: Option<&str>,
swarm_name: Option<&str>,
docs_dir: Option<&str>,
@ -37,7 +36,6 @@ pub fn render(
let rendered = body
.replace("{label}", label)
.replace("{qualified_label}", &qualified)
.replace("{operator_pronouns}", operator_pronouns)
.replace("{hive_identity}", &hive_identity)
.replace("{swarm_identity}", &swarm_identity);
// 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> {
let parent = crate::paths::config_dir();
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 = tokio::fs::read_to_string(&template_path)
.await
@ -137,7 +134,6 @@ pub async fn write_system_prompt(_socket: &Path, label: &str) -> Result<PathBuf>
let body = render(
&template,
label,
&pronouns,
hive_name.as_deref(),
swarm_name.as_deref(),
docs_dir.as_deref(),
@ -236,13 +232,11 @@ shared closer
}
#[test]
fn render_substitutes_label_and_pronouns() {
// Real template's first agent line — keeps the renderer
// honest about the {label} / {operator_pronouns} pair the
// harness already relied on.
let rendered = render(&PRODUCTION_TEMPLATE, "alice", "they/them", None, None, None);
fn render_substitutes_label_and_keeps_the_pronoun_line() {
// Real template's first agent line.
let rendered = render(&PRODUCTION_TEMPLATE, "alice", None, None, None);
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("{operator_pronouns}"));
}
@ -250,7 +244,7 @@ shared closer
#[test]
fn render_no_role_markers_in_output() {
// 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:"));
// Shared tools appear.
@ -260,13 +254,13 @@ shared closer
#[test]
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"));
}
#[test]
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}");
}
@ -280,19 +274,12 @@ shared closer
// substitution logic; nothing here depends on the production
// template's flavor markers.
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]
fn render_substitutes_hive_identity_when_set() {
let rendered = render(
IDENTITY_FIXTURE,
"alice",
"she/her",
Some("pr1ma"),
None,
None,
);
let rendered = render(IDENTITY_FIXTURE, "alice", Some("pr1ma"), None, None);
assert!(rendered.contains("on hive `pr1ma`"), "{rendered}");
// swarm clause stays absent when only hive is set.
assert!(!rendered.contains("in swarm"));
@ -303,14 +290,7 @@ You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity
#[test]
fn render_substitutes_swarm_identity_when_set() {
let rendered = render(
IDENTITY_FIXTURE,
"ruth",
"she/her",
None,
Some("constellat1on"),
None,
);
let rendered = render(IDENTITY_FIXTURE, "ruth", None, Some("constellat1on"), None);
assert!(rendered.contains("in swarm `constellat1on`"));
assert!(!rendered.contains("on hive"));
}
@ -320,7 +300,6 @@ You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity
let rendered = render(
IDENTITY_FIXTURE,
"iris",
"she/her",
Some("pr1ma"),
Some("constellat1on"),
None,
@ -334,7 +313,7 @@ You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity
fn render_omits_identity_when_unset() {
// None / None must round-trip the non-identity opener verbatim
// — 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("in swarm"));
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
// filters empty), but `render` should still no-op on a
// direct `Some("")` from a test fixture or a future caller.
let rendered = render(
IDENTITY_FIXTURE,
"alice",
"she/her",
Some(""),
Some(""),
None,
);
let rendered = render(IDENTITY_FIXTURE, "alice", Some(""), Some(""), None);
assert!(!rendered.contains("on hive"));
assert!(!rendered.contains("in swarm"));
}
@ -364,7 +336,6 @@ You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity
let rendered = render(
&PRODUCTION_TEMPLATE,
"alice",
"she/her",
None,
None,
Some("/run/hive-docs"),
@ -380,7 +351,7 @@ You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity
#[test]
fn render_no_docs_pointer_when_docs_dir_absent_or_empty() {
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!(
!rendered.contains("mounted read-only at"),
"docs pointer must not appear for {docs:?}:\n{rendered}"

View file

@ -264,8 +264,8 @@ pub fn stall_sleep_secs() -> u64 {
/// Resolve the effective context-window size for watermark calculations.
/// Priority order (first wins):
/// 1. API-reported window from the last `result` event's `modelUsage.*.contextWindow`.
/// 2. `HIVE_CONTEXT_WINDOW_TOKENS_*` env vars (Nix-configured per-model defaults).
/// 3. Hard fallback: 200 000.
/// 2. `harness_state::context_window_tokens` (per-model defaults, then the
/// `HIVE_CONTEXT_WINDOW_TOKENS` override, then 200 000).
///
/// The API-reported window is the authoritative per-inference active
/// context limit. It reflects what the model actually enforces — which

View file

@ -151,7 +151,7 @@ pub(super) struct StateSnapshot {
/// Effective context-window token budget for the current model.
/// Primary source: API-reported `modelUsage.*.contextWindow` from
/// 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 %.
context_window_tokens: u64,
/// Last-inference token usage from the most recent completed

View file

@ -67,20 +67,6 @@ pub struct Coordinator {
/// each per-agent flake so the agent's web UI can build the right
/// rebuild-button URL pointing back at the dashboard.
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
/// the `container@h-<name>.service.d/` drop-in on every spawn/rebuild.
pub agent_cpu_quota: String,
@ -100,9 +86,8 @@ pub struct Coordinator {
/// estimate on the ST4TS tab. Set via `services.hyperhive.modelPrices`
/// and passed to `hive-c0re serve --model-prices <json>`. Models not
/// 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 —
/// unlike `context_window_tokens` — it is *not* part of `HiveEnv` and
/// is never injected into containers.
/// hive-c0re-local (read only by the `/api/stats-hive` handler), so it
/// is *not* part of `HiveEnv` and is never injected into containers.
pub model_prices: crate::hive_stats::PriceTable,
agents: Mutex<HashMap<String, AgentSocket>>,
/// 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.
pub claude_code_path: Option<String>,
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%"`).
pub agent_cpu_quota: String,
/// Per-agent systemd `MemoryMax=` value (e.g. `"4G"`).
@ -247,12 +230,6 @@ impl Default for HiveEnv {
nixpkgs_flake: String::new(),
claude_code_path: None,
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_memory_max: "4G".to_string(),
// 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.dashboard_port, 1234);
// omitted → HiveEnv::default()
assert_eq!(sc.env.operator_pronouns, "she/her");
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"));
}
@ -432,8 +407,6 @@ impl Coordinator {
nixpkgs_flake,
claude_code_path,
dashboard_port,
operator_pronouns,
context_window_tokens,
agent_cpu_quota,
agent_memory_max,
agent_cpu_weight,
@ -473,8 +446,6 @@ impl Coordinator {
nixpkgs_flake,
claude_code_path,
dashboard_port,
operator_pronouns,
context_window_tokens,
agent_cpu_quota,
agent_memory_max,
agent_cpu_weight,
@ -506,8 +477,6 @@ impl Coordinator {
nixpkgs_flake: self.nixpkgs_flake.clone(),
claude_code_path: self.claude_code_path.clone(),
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_memory_max: self.agent_memory_max.clone(),
agent_cpu_weight: self.agent_cpu_weight,

View file

@ -90,17 +90,6 @@ enum Cmd {
/// Override: dashboard HTTP port.
#[arg(long)]
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
/// via a drop-in (e.g. `"200%"` = 2 cores). Set via
/// `services.hyperhive.agentCpuQuota`.
@ -150,8 +139,6 @@ async fn main() -> Result<()> {
hyperhive_flake,
nixpkgs_flake,
dashboard_port,
operator_pronouns,
context_window_tokens,
agent_cpu_quota,
agent_memory_max,
agent_cpu_weight,
@ -180,13 +167,6 @@ async fn main() -> Result<()> {
if let Some(v) = dashboard_port {
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 {
sc.env.agent_cpu_quota = v;
}

View file

@ -334,25 +334,16 @@ fn render_hive_flake(hive: &HiveEnv, agents: &[AgentSpec]) -> String {
&hive.nixpkgs_flake,
hive.claude_code_path.as_deref(),
hive.dashboard_port,
&hive.operator_pronouns,
&hive.context_window_tokens,
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(
hyperhive_flake: &str,
docs_flake: &str,
nixpkgs_flake: &str,
claude_code_path: Option<&str>,
dashboard_port: u16,
operator_pronouns: &str,
context_window_tokens: &std::collections::HashMap<String, u64>,
agents: &[AgentSpec],
) -> String {
render_flake_with_lookup(
@ -361,8 +352,6 @@ fn render_flake(
nixpkgs_flake,
claude_code_path,
dashboard_port,
operator_pronouns,
context_window_tokens,
agents,
agent_canonical_inputs,
)
@ -742,10 +731,8 @@ fn agent_canonical_inputs(name: &str) -> Vec<&'static str> {
/// agent flake-lock introspection.
#[allow(
clippy::too_many_lines,
clippy::too_many_arguments,
reason = "templated string-builder for the meta flake — the length is one \
contiguous fmt block, splitting it would just hide the shape; the \
args mirror the host-level config the flake is rendered from"
contiguous fmt block, splitting it would just hide the shape"
)]
fn render_flake_with_lookup<F>(
hyperhive_flake: &str,
@ -753,8 +740,6 @@ fn render_flake_with_lookup<F>(
nixpkgs_flake: &str,
claude_code_path: Option<&str>,
dashboard_port: u16,
operator_pronouns: &str,
context_window_tokens: &std::collections::HashMap<String, u64>,
agents: &[AgentSpec],
lookup: F,
) -> String
@ -830,13 +815,9 @@ where
}
}
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!(
out,
" dashboardPort = {dashboard_port};\n operatorPronouns = \"{pronouns_escaped}\";\n mkAgent = {{ name, isManager, port }}:"
" dashboardPort = {dashboard_port};\n mkAgent = {{ name, isManager, port }}:"
);
out.push_str(
r#" let
@ -1025,20 +1006,8 @@ where
HIVE_PORT = toString port;
HIVE_LABEL = name;
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
// env (`services.hyperhive.*` options flow through the hyperhive
// NixOS module into the host process). We copy whatever's set into
@ -1314,8 +1283,6 @@ mod tests {
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
"she/her",
&std::collections::HashMap::new(),
&[sample_spec("alice", false, 9001)],
);
// nixpkgs is a top-level input with an explicit URL; hyperhive
@ -1360,8 +1327,6 @@ mod tests {
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
"she/her",
&std::collections::HashMap::new(),
&[sample_spec("alice", false, 9001)],
);
assert!(
@ -1378,8 +1343,6 @@ mod tests {
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
"she/her",
&std::collections::HashMap::new(),
&[sample_spec("alice", false, 9001)],
);
assert!(
@ -1418,8 +1381,6 @@ mod tests {
"path:/nix/store/aaaa-nixpkgs-source",
Some("/nix/store/cccc-claude-code-2.1.220"),
8000,
"she/her",
&std::collections::HashMap::new(),
&[sample_spec("alice", false, 9001)],
);
assert!(
@ -1446,8 +1407,6 @@ mod tests {
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
"she/her",
&std::collections::HashMap::new(),
&[sample_spec("alice", false, 9001)],
);
assert!(
@ -1466,8 +1425,6 @@ mod tests {
"",
None,
8000,
"she/her",
&std::collections::HashMap::new(),
&[sample_spec("alice", false, 9001)],
);
assert!(
@ -1496,8 +1453,6 @@ mod tests {
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
"she/her",
&std::collections::HashMap::new(),
&[
sample_spec("argus", false, 9001),
sample_spec("bitburner", false, 9002),
@ -1529,8 +1484,6 @@ mod tests {
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
"she/her",
&std::collections::HashMap::new(),
&[sample_spec("alice", false, 9001)],
|_| Vec::new(),
);
@ -1567,8 +1520,6 @@ mod tests {
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
"she/her",
&std::collections::HashMap::new(),
&[sample_spec("alice", false, 9001)],
);
unsafe {
@ -1612,8 +1563,6 @@ mod tests {
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
"she/her",
&std::collections::HashMap::new(),
&[sample_spec("alice", false, 9001)],
)
};
@ -1792,8 +1741,6 @@ mod tests {
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
"she/her",
&std::collections::HashMap::new(),
&[sample_spec("alice", false, 9001)],
);
unsafe {
@ -1828,8 +1775,6 @@ mod tests {
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
"she/her",
&std::collections::HashMap::new(),
&[sample_spec("alice", false, 9001)],
);
let want = format!(
@ -1872,8 +1817,6 @@ mod tests {
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
"she/her",
&std::collections::HashMap::new(),
&[sample_spec("alice", false, 9001)],
)
};
@ -1964,8 +1907,6 @@ mod tests {
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
"she/her",
&std::collections::HashMap::new(),
&[sample_spec("alice", false, 9001)],
)
};
@ -2038,8 +1979,6 @@ mod tests {
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
"she/her",
&std::collections::HashMap::new(),
&[sample_spec("alice", false, 9001)],
)
};

View file

@ -70,11 +70,9 @@ in
`swarmctl agent inference set`) wins over 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.
`"haiku"`, `"sonnet"`, `"opus"` (or any future identifier). The
harness takes the context window from the model's own report after
each turn, and from a per-family default before the first.
'';
};

View file

@ -280,9 +280,6 @@ in
# here so both the harness binary and any user-shell `cargo run`
# inside the container resolve them from the same path.
# 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 = {
HIVE_ASSETS_DIR = "${config.services.hyperhive.agent.packages.assets}/share/hyperhive";
SHELL = "${pkgs.bashInteractive}/bin/bash";

View 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.
'';
}

View file

@ -85,6 +85,7 @@
./swarm-victoriametrics.nix
./swarm-wireguard.nix
./swarm.nix
./c0re-options-removed.nix
./host-ca-removed.nix
./nats-callout-removed.nix
./swarm-required-services.nix

View file

@ -112,8 +112,6 @@ let
# that caused it.
claude_code_path = cfg.claudeCodePackage;
dashboard_port = cfg.dashboardPort;
operator_pronouns = cfg.operatorPronouns;
context_window_tokens = cfg.contextWindowTokens;
agent_cpu_quota = cfg.agentCpuQuota;
agent_memory_max = cfg.agentMemoryMax;
agent_cpu_weight = cfg.agentCpuWeight;

View file

@ -171,21 +171,6 @@
default = 7000;
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 {
type = lib.types.bool;
default = false;
@ -212,31 +197,6 @@
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 {
type = lib.types.attrsOf (

View file

@ -48,8 +48,13 @@ let
inherit (shared) forgeHere;
# 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.
noAgentQueue = hiveWithController { deploy.hive-controller.queue.agentNatsUrl = null; };
# only hive without one is a hive told to have none. Also the host still
# 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.
withCi = hive {
@ -237,6 +242,29 @@ let
in
!(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
# spanning two modules. Asserted against the hive's own `BAO_ADDR`