From 4b5707768c898f2c6de5f898502c9e6ce9ff7bed Mon Sep 17 00:00:00 2001 From: atlas Date: Fri, 16 Oct 2026 17:18:23 +0200 Subject: [PATCH] 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_ env vars are gone. --- README.md | 2 - docs/turn-loop/claude-invocation.md | 26 ++++----- hive-agent/prompts/system.md | 2 +- hive-agent/src/events.rs | 6 +- hive-agent/src/harness_state.rs | 56 +++++++++++-------- hive-agent/src/prompt.rs | 57 +++++-------------- hive-agent/src/turn.rs | 4 +- hive-agent/src/web_ui/state.rs | 2 +- hive-c0re/src/coordinator.rs | 35 +----------- hive-c0re/src/main.rs | 20 ------- hive-c0re/src/meta.rs | 67 +---------------------- nix/agent-modules/agent-service.nix | 8 +-- nix/agent-modules/default.nix | 3 - nix/host-modules/c0re-options-removed.nix | 46 ++++++++++++++++ nix/host-modules/default.nix | 1 + nix/host-modules/hive-c0re/default.nix | 2 - nix/host-modules/hive-c0re/options.nix | 40 -------------- nix/module-eval/core-toggle.nix | 32 ++++++++++- 18 files changed, 151 insertions(+), 258 deletions(-) create mode 100644 nix/host-modules/c0re-options-removed.nix diff --git a/README.md b/README.md index d8e5f3ce..3e420c60 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/turn-loop/claude-invocation.md b/docs/turn-loop/claude-invocation.md index bdbc0742..b1fe15f0 100644 --- a/docs/turn-loop/claude-invocation.md +++ b/docs/turn-loop/claude-invocation.md @@ -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_` 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 (`...`, 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. diff --git a/hive-agent/prompts/system.md b/hive-agent/prompts/system.md index 71042c8d..9e947970 100644 --- a/hive-agent/prompts/system.md +++ b/hive-agent/prompts/system.md @@ -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: diff --git a/hive-agent/src/events.rs b/hive-agent/src/events.rs index 0722a535..05da0568 100644 --- a/hive-agent/src/events.rs +++ b/hive-agent/src/events.rs @@ -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>>, /// Resolved model id from the most recent turn's `assistant` events /// (the API-echoed `message.model`, e.g. `claude-opus-4-8`), as diff --git a/hive-agent/src/harness_state.rs b/hive-agent/src/harness_state.rs index 23e447e0..d6dedf7e 100644 --- a/hive-agent/src/harness_state.rs +++ b/hive-agent/src/harness_state.rs @@ -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_` 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 (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::() - && 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() { diff --git a/hive-agent/src/prompt.rs b/hive-agent/src/prompt.rs index 0d7efb35..88933406 100644 --- a/hive-agent/src/prompt.rs +++ b/hive-agent/src/prompt.rs @@ -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 { 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 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("