From 4b5707768c898f2c6de5f898502c9e6ce9ff7bed Mon Sep 17 00:00:00 2001 From: atlas Date: Fri, 16 Oct 2026 17:18:23 +0200 Subject: [PATCH 1/4] 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(" -Cadence follows `metricIntervalMs` (default 60s). Transport is OTLP/HTTP +The export cadence is a fixed 60s. Transport is OTLP/HTTP (JSON) to the hive collector's bridge address, with no auth header — that first hop is unauthenticated for every producer on this host, and the upstream credential stays on the swarm tier. @@ -409,8 +409,8 @@ window used by agent** panel (health tab) divides one by the other. Resource attributes (`service.name`, `agent`, `swarm`) come from the same container-wide `OTEL_RESOURCE_ATTRIBUTES` as everything else in this section, and the swarm's collector adds `hive` — nothing extra to configure. -Cadence follows `HYPERHIVE_OTEL_METRIC_INTERVAL_MS` (default 60s, same variable + default as -`hive-c0re`'s container-resource export above) — that only controls how often +Cadence follows `HYPERHIVE_OTEL_METRIC_INTERVAL_MS` (default 60s, the same +cadence as `hive-c0re`'s container-resource export above). That only controls how often the harness flushes the batched points to the collector, not how often it records them (every turn, always). diff --git a/hive-c0re/src/meta.rs b/hive-c0re/src/meta.rs index 05e74ddc..46dab331 100644 --- a/hive-c0re/src/meta.rs +++ b/hive-c0re/src/meta.rs @@ -555,10 +555,6 @@ struct OtelConfig { endpoint: String, protocol: String, extra_resource_attributes: Option, - metric_interval_ms: Option, - /// `HYPERHIVE_OTEL_DEBUG=1` → `services.hyperhive.agent.otel.debug = true` → - /// `CLAUDE_CODE_OTEL_DIAG_STDERR=1` in every agent's env. - debug: bool, } /// Read the hive-wide OTEL config from env, or `None` when OTEL is off. @@ -578,19 +574,10 @@ fn otel_config() -> Option { let extra_resource_attributes = std::env::var("HYPERHIVE_OTEL_EXTRA_RESOURCE_ATTRIBUTES") .ok() .filter(|v| !v.is_empty()); - let metric_interval_ms = std::env::var("HYPERHIVE_OTEL_METRIC_INTERVAL_MS") - .ok() - .and_then(|v| v.parse::().ok()) - .filter(|v| *v > 0); - let debug = std::env::var("HYPERHIVE_OTEL_DEBUG") - .ok() - .is_some_and(|v| v == "1"); Some(OtelConfig { endpoint, protocol, extra_resource_attributes, - metric_interval_ms, - debug, }) } @@ -902,17 +889,6 @@ where esc(attrs) ); } - if let Some(ms) = otel.metric_interval_ms { - // Int option — emit a bare numeric literal (no quotes). `ms` is a - // parsed u64, so it can't inject anything into the rendered nix. - let _ = writeln!( - out, - " services.hyperhive.agent.otel.metricIntervalMs = {ms};" - ); - } - if otel.debug { - out.push_str(" services.hyperhive.agent.otel.debug = true;\n"); - } } // Agent-facing service URLs (`services.hyperhive.agent.forge.url`, `services.hyperhive.agent.matrix.url`): // emit the host's real values as build-time agent config, the same diff --git a/hive-c0re/src/stats/otel_metrics.rs b/hive-c0re/src/stats/otel_metrics.rs index 9fd8ec90..36ae37bb 100644 --- a/hive-c0re/src/stats/otel_metrics.rs +++ b/hive-c0re/src/stats/otel_metrics.rs @@ -40,8 +40,8 @@ use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider}; use super::container_stats::{self, ContainerResource}; use super::warnings; -/// Default export cadence when `HYPERHIVE_OTEL_METRIC_INTERVAL_MS` is unset. -const DEFAULT_INTERVAL: Duration = Duration::from_mins(1); +/// Export cadence, and the snapshot refresher's. +const EXPORT_INTERVAL: Duration = Duration::from_mins(1); /// Latest container-stats snapshot: written by the async refresher, read by /// the sync observable-instrument callbacks. @@ -69,7 +69,7 @@ pub fn spawn_exporter(hyperhive_flake: &str) { let snapshot = SNAPSHOT .get_or_init(|| Arc::new(Mutex::new(Vec::new()))) .clone(); - let interval = interval(); + let interval = EXPORT_INTERVAL; // Async refresher: keep the shared snapshot current for the sync callbacks. let refresh = snapshot.clone(); @@ -124,10 +124,9 @@ fn build_provider( .with_protocol(Protocol::HttpJson) .build() .context("build OTLP metric exporter")?; - // Drive the reader's export at `interval` so `HYPERHIVE_OTEL_METRIC_INTERVAL_MS` - // is the real export cadence (not just the snapshot-refresh cadence). The - // refresher runs at the same interval, gather-first, so the snapshot is - // populated before the first export. + // Drive the reader's export at `interval`, the same cadence as the + // refresher. The refresher gathers first, so the snapshot is populated + // before the first export. let reader = PeriodicReader::builder(exporter) .with_interval(interval) .build(); @@ -400,15 +399,6 @@ fn endpoint() -> Option { .filter(|s| !s.is_empty()) } -/// Export cadence from `HYPERHIVE_OTEL_METRIC_INTERVAL_MS`, else the default. -fn interval() -> Duration { - std::env::var("HYPERHIVE_OTEL_METRIC_INTERVAL_MS") - .ok() - .and_then(|s| s.trim().parse::().ok()) - .filter(|ms| *ms > 0) - .map_or(DEFAULT_INTERVAL, Duration::from_millis) -} - /// Extra resource attributes from `HYPERHIVE_OTEL_EXTRA_RESOURCE_ATTRIBUTES` /// (`key=value,key=value`), same env the agent-side export reads. fn resource_attributes() -> Vec<(String, String)> { diff --git a/nix/agent-modules/claude-settings.nix b/nix/agent-modules/claude-settings.nix index 944ef489..c0dc3d6a 100644 --- a/nix/agent-modules/claude-settings.nix +++ b/nix/agent-modules/claude-settings.nix @@ -219,7 +219,7 @@ in baseClaudeEnv // lib.optionalAttrs otelCfg.enable otelSettingsEnv // lib.optionalAttrs (otelCfg.enable && otelCfg.debug) { - # SDK diagnostics — noisy; only on when services.hyperhive.otel.debug = true. + # SDK diagnostics — noisy; only on when services.hyperhive.agent.otel.debug = true. CLAUDE_CODE_OTEL_DIAG_STDERR = "1"; }; in diff --git a/nix/agent-modules/default.nix b/nix/agent-modules/default.nix index ffbfdb1c..03f889fb 100644 --- a/nix/agent-modules/default.nix +++ b/nix/agent-modules/default.nix @@ -51,6 +51,7 @@ in ./memory-cap.nix ./network.nix ./otel.nix + ./otel-removed.nix ./packages.nix ./queue.nix ./queue-identity.nix diff --git a/nix/agent-modules/otel-removed.nix b/nix/agent-modules/otel-removed.nix new file mode 100644 index 00000000..8c7802cc --- /dev/null +++ b/nix/agent-modules/otel-removed.nix @@ -0,0 +1,27 @@ +# Removal shim for `services.hyperhive.agent.otel.metricIntervalMs`. +# +# A definition is ignored and produces a warning, so an agent config, or a +# meta flake rendered before the option went, keeps evaluating. claude runs +# at the OTEL SDK's default export cadence. +# +# Self-contained and deletable: one file to remove when the deprecation +# window closes. +{ lib, config, ... }: +let + value = config.services.hyperhive.agent.otel.metricIntervalMs; +in +{ + options.services.hyperhive.agent.otel.metricIntervalMs = lib.mkOption { + type = lib.types.nullOr lib.types.ints.positive; + default = null; + visible = false; + internal = true; + description = "Removed and ignored. `null` is \"not set\"."; + }; + + config.warnings = lib.optional (value != null) '' + services.hyperhive.agent.otel.metricIntervalMs is removed and ignored: + claude exports metrics at the SDK default of 60 seconds. Remove this + setting. + ''; +} diff --git a/nix/agent-modules/otel.nix b/nix/agent-modules/otel.nix index f42618ab..f080c8ea 100644 --- a/nix/agent-modules/otel.nix +++ b/nix/agent-modules/otel.nix @@ -1,5 +1,6 @@ # OpenTelemetry wiring for an agent container: the per-agent options the -# meta flake injects (host-driven from `services.hyperhive.otel.*`), the +# meta flake injects (host-driven from `services.hyperhive.otel.*`) plus +# `debug`, which the agent's own config sets, the # environment every OTLP producer inside the container reads, and the # collector that forwards this container's journal to the hive. # @@ -62,9 +63,6 @@ let # silently drop without a deltatocumulative processor. OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE = "cumulative"; OTEL_RESOURCE_ATTRIBUTES = resourceAttributes; - } - // lib.optionalAttrs (cfg.metricIntervalMs != null) { - OTEL_METRIC_EXPORT_INTERVAL = toString cfg.metricIntervalMs; }; # The exporter's NAME picks the wire protocol, so `protocol` has to be @@ -131,25 +129,14 @@ in ''; }; - metricIntervalMs = lib.mkOption { - type = lib.types.nullOr lib.types.ints.positive; - default = null; - internal = true; - description = '' - Metric export interval in milliseconds, set as - `OTEL_METRIC_EXPORT_INTERVAL`. Null leaves the SDK default (60s - for Claude Code). Host-driven via - `services.hyperhive.otel.metricIntervalMs`. - ''; - }; - debug = lib.mkOption { type = lib.types.bool; default = false; - internal = true; description = '' - Emit OTEL SDK diagnostics to stderr (`CLAUDE_CODE_OTEL_DIAG_STDERR=1`). - Host-driven via `services.hyperhive.otel.debug`. + Emit claude's OTEL SDK diagnostics to stderr + (`CLAUDE_CODE_OTEL_DIAG_STDERR=1`). Set it in this agent's own + config while troubleshooting its collector connection; it is noisy, + so leave it off otherwise. Only takes effect when `enable` is true. ''; }; }; diff --git a/nix/host-modules/c0re-options-removed.nix b/nix/host-modules/c0re-options-removed.nix deleted file mode 100644 index 00e7cc87..00000000 --- a/nix/host-modules/c0re-options-removed.nix +++ /dev/null @@ -1,46 +0,0 @@ -# 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. - ''; -} diff --git a/nix/host-modules/default.nix b/nix/host-modules/default.nix index be88c0b0..ef1a1760 100644 --- a/nix/host-modules/default.nix +++ b/nix/host-modules/default.nix @@ -85,7 +85,7 @@ ./swarm-victoriametrics.nix ./swarm-wireguard.nix ./swarm.nix - ./c0re-options-removed.nix + ./hive-options-removed.nix ./host-ca-removed.nix ./nats-callout-removed.nix ./swarm-required-services.nix diff --git a/nix/host-modules/hive-c0re/environment.nix b/nix/host-modules/hive-c0re/environment.nix index b114a898..291ea52c 100644 --- a/nix/host-modules/hive-c0re/environment.nix +++ b/nix/host-modules/hive-c0re/environment.nix @@ -125,12 +125,6 @@ in # The rule: the collector holding the credential achieves nothing while # anything else hands out a copy, so there is exactly one holder and it # is on the host. - // lib.optionalAttrs (otel.metricIntervalMs != null) { - HYPERHIVE_OTEL_METRIC_INTERVAL_MS = toString otel.metricIntervalMs; - } - // lib.optionalAttrs otel.debug { - HYPERHIVE_OTEL_DEBUG = "1"; - } ) // { # In-cluster forge URL — the gateway vhost (`forge.`), which diff --git a/nix/host-modules/hive-options-removed.nix b/nix/host-modules/hive-options-removed.nix new file mode 100644 index 00000000..a3902803 --- /dev/null +++ b/nix/host-modules/hive-options-removed.nix @@ -0,0 +1,62 @@ +# Removal shim for four host options: `c0re.operatorPronouns`, +# `c0re.contextWindowTokens`, `otel.metricIntervalMs` and `otel.debug`. +# +# A definition of any of them 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, the +# harness (hive-agent/src/harness_state.rs) holds the per-model context +# windows it uses until a turn reports the model's own, every OTEL exporter +# on the hive runs at its SDK's default cadence, and an agent's own config +# sets `services.hyperhive.agent.otel.debug`. +# +# 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; + + # `null` is "not set". + ignored = + type: + lib.mkOption { + type = lib.types.nullOr type; + default = null; + visible = false; + internal = true; + description = "Removed and ignored."; + }; + + removed = + path: value: why: + lib.optional (value != null) '' + services.hyperhive.${path} is removed and ignored: ${why} Remove this + setting. + ''; +in +{ + options.services.hyperhive = { + c0re.operatorPronouns = ignored lib.types.str; + c0re.contextWindowTokens = ignored (lib.types.attrsOf lib.types.int); + otel.metricIntervalMs = ignored lib.types.ints.positive; + otel.debug = ignored lib.types.bool; + }; + + config.warnings = + removed "c0re.operatorPronouns" cfg.c0re.operatorPronouns '' + the operator's pronouns will be a per-operator profile setting. Until + then every agent's system prompt says she/her. + '' + ++ removed "c0re.contextWindowTokens" cfg.c0re.contextWindowTokens '' + 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). + '' + ++ removed "otel.metricIntervalMs" cfg.otel.metricIntervalMs '' + claude in every agent and hive-c0re's container-resource exporter both + export metrics at the SDK default of 60 seconds. + '' + ++ removed "otel.debug" cfg.otel.debug '' + to see one agent's OTEL SDK diagnostics, set + services.hyperhive.agent.otel.debug = true in that agent's own config. + ''; +} diff --git a/nix/host-modules/otel.nix b/nix/host-modules/otel.nix index dda48021..5ab1e6de 100644 --- a/nix/host-modules/otel.nix +++ b/nix/host-modules/otel.nix @@ -158,18 +158,6 @@ in ''; }; - debug = lib.mkOption { - type = lib.types.bool; - default = false; - description = '' - Emit OTEL SDK diagnostic messages to every agent's stderr by - setting `CLAUDE_CODE_OTEL_DIAG_STDERR=1`. Useful when - troubleshooting collector connectivity or endpoint config; - leave off in normal operation to avoid noise in agent logs. - Only meaningful when `enable` is true. - ''; - }; - collector.upstreamHeaderName = lib.mkOption { type = lib.types.str; default = "Authorization"; @@ -306,23 +294,6 @@ in copied into the store and never passed in argv. ''; }; - - metricIntervalMs = lib.mkOption { - type = lib.types.nullOr lib.types.ints.positive; - default = null; - example = 10000; - description = '' - Metric export interval in milliseconds, set as - `OTEL_METRIC_EXPORT_INTERVAL` for every agent. Claude Code's - default is 60000 (60s). Leave `null` to use that default. - - Each agent runs claude as a short-lived per-turn process; claude - force-flushes metrics on shutdown, so this is not required for - metrics to be exported, but a lower value gives more frequent - intermediate flushes within long turns. Cosmetic, not a - correctness knob. - ''; - }; }; config = lib.mkIf config.services.hyperhive.otel.enable ( diff --git a/nix/module-eval/agent-otel.nix b/nix/module-eval/agent-otel.nix index ad7df33b..5d75854b 100644 --- a/nix/module-eval/agent-otel.nix +++ b/nix/module-eval/agent-otel.nix @@ -38,11 +38,13 @@ let # The same agent over the other wire protocol. An exporter's NAME is what # selects it, so this is where a defined exporter and the pipeline's - # reference to it can drift apart. + # reference to it can drift apart. Also an agent config still setting the + # removed metric interval. agentOtelGrpc = agent { otel.enable = true; otel.endpoint = agentBridge; otel.protocol = "grpc"; + otel.metricIntervalMs = 10000; }; agentNoOtel = shared.agentBare; @@ -116,6 +118,22 @@ let in (s.exporters ? otlp) && s.service.pipelines.logs.exporters == [ "otlp" ]; } + { + # Control: `agentOtel` sets no interval. The endpoint lookup shows the + # environment it reads is the one the OTEL SDKs get. + name = "an agent config setting the removed metric interval evaluates, warns, and exports none"; + ok = + let + warned = lib.any ( + w: lib.hasInfix "services.hyperhive.agent.otel.metricIntervalMs is removed and ignored" w + ); + env = machine: machine.systemd.globalEnvironment; + in + warned agentOtelGrpc.warnings + && !(warned agentOtel.warnings) + && (env agentOtelGrpc) ? OTEL_EXPORTER_OTLP_ENDPOINT + && !((env agentOtelGrpc) ? OTEL_METRIC_EXPORT_INTERVAL); + } ]; in runGroup "agent-otel" cases diff --git a/nix/module-eval/core-toggle.nix b/nix/module-eval/core-toggle.nix index c32fc617..00b48a83 100644 --- a/nix/module-eval/core-toggle.nix +++ b/nix/module-eval/core-toggle.nix @@ -49,11 +49,13 @@ let # 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. Also the host still - # setting the two removed `c0re` options; no case on its queue reads them. + # setting the four removed host options; no case on its queue reads them. noAgentQueue = hiveWithController { deploy.hive-controller.queue.agentNatsUrl = null; c0re.operatorPronouns = "they/them"; c0re.contextWindowTokens.sonnet = 900000; + otel.metricIntervalMs = 10000; + otel.debug = true; }; # On the store's host, which mints the runner's store identity. @@ -243,17 +245,19 @@ let !(e ? HIVE_AGENT_NATS_URL) && !(e ? HIVE_AGENT_OIDC_TOKEN_ENDPOINT); } { - # Control: `bare` sets neither option, and its serve.json carries the + # Control: `bare` sets none of them, 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"; + name = "a definition of a removed host option evaluates and warns, and the c0re ones reach no serve.json"; ok = let warned = m: option: - lib.any (w: lib.hasInfix "services.hyperhive.c0re.${option} is removed and ignored" w) m.warnings; + lib.any (w: lib.hasInfix "services.hyperhive.${option} is removed and ignored" w) m.warnings; options = [ - "operatorPronouns" - "contextWindowTokens" + "c0re.operatorPronouns" + "c0re.contextWindowTokens" + "otel.metricIntervalMs" + "otel.debug" ]; hasKey = m: key: lib.hasInfix "\"${key}\":" m.environment.etc."hyperhive/serve.json".text; noRemovedKeys = From f8960dd501ad78257b1772c1c5dee4792426b482 Mon Sep 17 00:00:00 2001 From: atlas Date: Fri, 16 Oct 2026 17:48:24 +0200 Subject: [PATCH 3/4] drop the remaining HYPERHIVE_OTEL_METRIC_INTERVAL_MS readers Nothing sets the variable once otel.metricIntervalMs is gone, so the turn-metrics, jobq-metrics and vcs-metrics exporters export at a fixed 60s, as hive-c0re's container-resource exporter already does. The observability doc and the removal warning now say so. --- docs/scheduler/observability.md | 8 ++++---- hive-agent/src/otel_turn_metrics.rs | 18 ++++-------------- hive-jobq-metrics/src/lib.rs | 23 +++++------------------ nix/host-modules/hive-options-removed.nix | 9 +++++---- swarm-controller/src/vcs_metrics.rs | 17 ++++------------- 5 files changed, 22 insertions(+), 53 deletions(-) diff --git a/docs/scheduler/observability.md b/docs/scheduler/observability.md index e586b7ff..91788e96 100644 --- a/docs/scheduler/observability.md +++ b/docs/scheduler/observability.md @@ -409,8 +409,8 @@ window used by agent** panel (health tab) divides one by the other. Resource attributes (`service.name`, `agent`, `swarm`) come from the same container-wide `OTEL_RESOURCE_ATTRIBUTES` as everything else in this section, and the swarm's collector adds `hive` — nothing extra to configure. -Cadence follows `HYPERHIVE_OTEL_METRIC_INTERVAL_MS` (default 60s, the same -cadence as `hive-c0re`'s container-resource export above). That only controls how often +The export cadence is a fixed 60s, the same +as `hive-c0re`'s container-resource export above. That only controls how often the harness flushes the batched points to the collector, not how often it records them (every turn, always). @@ -486,8 +486,8 @@ store either a commit or a push anywhere as a row to count. A push with zero commits (a branch delete, or a force-push that doesn't add new commits) still increments `push.count`; `commit.count` only advances -when the delivery actually carries commits. Same enable signal (`OTEL_EXPORTER_OTLP_ENDPOINT`), cadence variable -(`HYPERHIVE_OTEL_METRIC_INTERVAL_MS`) and `HYPERHIVE_OTEL_EXTRA_RESOURCE_ATTRIBUTES` +when the delivery actually carries commits. Same enable signal (`OTEL_EXPORTER_OTLP_ENDPOINT`), fixed 60s cadence +and `HYPERHIVE_OTEL_EXTRA_RESOURCE_ATTRIBUTES` resource-attribute channel as `swarm-controller`'s other OTEL exporter (its `hive-jobq-metrics`-backed job-graph rollup, undocumented here — see that crate's own doc comment) — `swarm-controller` sets `service.name = diff --git a/hive-agent/src/otel_turn_metrics.rs b/hive-agent/src/otel_turn_metrics.rs index 6cf96ff6..18ef9246 100644 --- a/hive-agent/src/otel_turn_metrics.rs +++ b/hive-agent/src/otel_turn_metrics.rs @@ -39,10 +39,9 @@ use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider}; use crate::turn_stats::TurnStatRow; -/// Default export cadence when `HYPERHIVE_OTEL_METRIC_INTERVAL_MS` is unset. -/// Matches `hive-c0re`'s container-metrics default so the two exporters -/// batch on the same rhythm by default. -const DEFAULT_INTERVAL: Duration = Duration::from_mins(1); +/// Export cadence. Matches `hive-c0re`'s container-metrics exporter so the +/// two batch on the same rhythm. +const EXPORT_INTERVAL: Duration = Duration::from_mins(1); struct Instruments { /// Kept alive for the process lifetime — the `PeriodicReader` only @@ -177,7 +176,7 @@ fn build() -> Option { tracing::debug!("otel turn-metrics: no endpoint configured, exporter disabled"); return None; } - let interval = interval(); + let interval = EXPORT_INTERVAL; match build_provider(interval) { Ok(provider) => { tracing::info!(?interval, "otel turn-metrics: exporter enabled"); @@ -254,12 +253,3 @@ fn build_provider(interval: Duration) -> anyhow::Result { pub fn enabled() -> bool { std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").is_ok_and(|s| !s.trim().is_empty()) } - -/// Export cadence from `HYPERHIVE_OTEL_METRIC_INTERVAL_MS`, else the default. -fn interval() -> Duration { - std::env::var("HYPERHIVE_OTEL_METRIC_INTERVAL_MS") - .ok() - .and_then(|s| s.trim().parse::().ok()) - .filter(|ms| *ms > 0) - .map_or(DEFAULT_INTERVAL, Duration::from_millis) -} diff --git a/hive-jobq-metrics/src/lib.rs b/hive-jobq-metrics/src/lib.rs index 9c45d453..b12e7e75 100644 --- a/hive-jobq-metrics/src/lib.rs +++ b/hive-jobq-metrics/src/lib.rs @@ -55,11 +55,10 @@ impl HttpClient for BoxedHttpClient { } } -/// Default export cadence when `HYPERHIVE_OTEL_METRIC_INTERVAL_MS` is unset. -/// Matches `hive-c0re::stats::otel_metrics`'s default — no reason for the -/// exporters across this tree to disagree on how fresh "current" means by -/// default. -const DEFAULT_INTERVAL: Duration = Duration::from_mins(1); +/// Export cadence, and the snapshot refresher's. Matches +/// `hive-c0re::stats::otel_metrics` — no reason for the exporters across +/// this tree to disagree on how fresh "current" means. +const EXPORT_INTERVAL: Duration = Duration::from_mins(1); /// Spawn a jobq-rollup OTEL exporter for `jobq` if OTEL is configured /// (`OTEL_EXPORTER_OTLP_ENDPOINT` non-empty) — `None` otherwise, same @@ -97,7 +96,7 @@ where { let endpoint = endpoint()?; let snapshot: Arc>> = Arc::new(Mutex::new(Vec::new())); - let interval = interval(); + let interval = EXPORT_INTERVAL; let refresh = snapshot.clone(); tokio::spawn(async move { @@ -225,18 +224,6 @@ fn endpoint() -> Option { .filter(|s| !s.is_empty()) } -/// Export cadence from `HYPERHIVE_OTEL_METRIC_INTERVAL_MS`, else the -/// default. Same variable `hive-c0re::stats::otel_metrics` reads — one -/// cadence knob for every OTEL exporter in the tree, not a new one per -/// module. -fn interval() -> Duration { - std::env::var("HYPERHIVE_OTEL_METRIC_INTERVAL_MS") - .ok() - .and_then(|s| s.trim().parse::().ok()) - .filter(|ms| *ms > 0) - .map_or(DEFAULT_INTERVAL, Duration::from_millis) -} - /// Extra resource attributes from `HYPERHIVE_OTEL_EXTRA_RESOURCE_ATTRIBUTES` /// (`key=value,key=value`), same env `hive-c0re::stats::otel_metrics` reads. fn resource_attributes() -> Vec<(String, String)> { diff --git a/nix/host-modules/hive-options-removed.nix b/nix/host-modules/hive-options-removed.nix index a3902803..70e45140 100644 --- a/nix/host-modules/hive-options-removed.nix +++ b/nix/host-modules/hive-options-removed.nix @@ -6,8 +6,9 @@ # (hive-agent/prompts/system.md) names the operator's pronouns itself, the # harness (hive-agent/src/harness_state.rs) holds the per-model context # windows it uses until a turn reports the model's own, every OTEL exporter -# on the hive runs at its SDK's default cadence, and an agent's own config -# sets `services.hyperhive.agent.otel.debug`. +# exports every 60 seconds (claude at its SDK default, the rest by a fixed +# constant), and an agent's own config sets +# `services.hyperhive.agent.otel.debug`. # # Self-contained and deletable, like ./swarm-peers-removed.nix: one file to # remove when the deprecation window closes. @@ -52,8 +53,8 @@ in 1000000, opus 1000000 tokens). '' ++ removed "otel.metricIntervalMs" cfg.otel.metricIntervalMs '' - claude in every agent and hive-c0re's container-resource exporter both - export metrics at the SDK default of 60 seconds. + claude exports at its SDK default of 60 seconds, and every hyperhive + exporter at a fixed 60 seconds. '' ++ removed "otel.debug" cfg.otel.debug '' to see one agent's OTEL SDK diagnostics, set diff --git a/swarm-controller/src/vcs_metrics.rs b/swarm-controller/src/vcs_metrics.rs index a0c5d801..e927b9c2 100644 --- a/swarm-controller/src/vcs_metrics.rs +++ b/swarm-controller/src/vcs_metrics.rs @@ -13,7 +13,7 @@ //! exporter): blocking OTLP client on the `PeriodicReader`'s own background //! thread, `service.name` + `HYPERHIVE_OTEL_EXTRA_RESOURCE_ATTRIBUTES` for //! the resource, `OTEL_EXPORTER_OTLP_ENDPOINT` as the sole enable signal, -//! `HYPERHIVE_OTEL_METRIC_INTERVAL_MS` for cadence. Recording is +//! a fixed 60 s export cadence. Recording is //! synchronous (`counter.add`, called at the event site) rather than //! observable/polled — see `hive-agent::otel_turn_metrics`'s doc comment //! for why an occurrence, unlike a live gauge, has nothing to poll between @@ -29,9 +29,8 @@ use opentelemetry_otlp::{MetricExporter, Protocol, WithExportConfig, WithHttpCon use opentelemetry_sdk::Resource; use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider}; -/// Default export cadence when `HYPERHIVE_OTEL_METRIC_INTERVAL_MS` is -/// unset. Matches every other OTEL exporter in this tree. -const DEFAULT_INTERVAL: Duration = Duration::from_mins(1); +/// Export cadence. Matches every other OTEL exporter in this tree. +const EXPORT_INTERVAL: Duration = Duration::from_mins(1); /// One push delivery, as parsed off the webhook body — just the two fields /// this module needs, not a full mirror of Forgejo's push-event schema. @@ -86,7 +85,7 @@ pub(super) fn record_push(activity: &PushActivity) { fn build() -> Option { let endpoint = endpoint()?; - let interval = interval(); + let interval = EXPORT_INTERVAL; match build_provider(interval) { Ok(provider) => { tracing::info!(%endpoint, ?interval, "otel vcs-metrics: exporter enabled"); @@ -198,14 +197,6 @@ fn endpoint() -> Option { .filter(|s| !s.is_empty()) } -fn interval() -> Duration { - std::env::var("HYPERHIVE_OTEL_METRIC_INTERVAL_MS") - .ok() - .and_then(|s| s.trim().parse::().ok()) - .filter(|ms| *ms > 0) - .map_or(DEFAULT_INTERVAL, Duration::from_millis) -} - fn resource_attributes() -> Vec<(String, String)> { std::env::var("HYPERHIVE_OTEL_EXTRA_RESOURCE_ATTRIBUTES") .ok() From e9f313e75d00561cf442bfc56f0d12378ab08784 Mon Sep 17 00:00:00 2001 From: atlas Date: Fri, 16 Oct 2026 19:15:15 +0200 Subject: [PATCH 4/4] drop the operator pronoun sentence from the agent system prompt Not every swarm's operator is she/her. The prompt now names no pronouns and tells agents to say "the operator"; pronouns will come from a per-operator profile setting. The operatorPronouns removal warning and claude-invocation.md say the same. --- docs/turn-loop/claude-invocation.md | 4 ++-- hive-agent/prompts/system.md | 2 +- hive-agent/src/prompt.rs | 5 +++-- nix/host-modules/hive-options-removed.nix | 7 ++++--- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/turn-loop/claude-invocation.md b/docs/turn-loop/claude-invocation.md index b1fe15f0..992b6314 100644 --- a/docs/turn-loop/claude-invocation.md +++ b/docs/turn-loop/claude-invocation.md @@ -260,8 +260,8 @@ needs to `chown` a bind mount), once at startup: (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). The template itself names the operator's - pronouns (`she/her`); no option sets them. + shape for the swarm). The template names no pronouns for the + operator, and tells agents to call them the operator. 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 9e947970..1fce9027 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 **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. +You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity}{swarm_identity} in a multi-agent system. The operator is the human at the dashboard; say "the operator" when you mention the operator 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/prompt.rs b/hive-agent/src/prompt.rs index 88933406..7ca58e82 100644 --- a/hive-agent/src/prompt.rs +++ b/hive-agent/src/prompt.rs @@ -232,11 +232,12 @@ shared closer } #[test] - fn render_substitutes_label_and_keeps_the_pronoun_line() { + fn render_substitutes_label_and_names_no_operator_pronouns() { // Real template's first agent line. let rendered = render(&PRODUCTION_TEMPLATE, "alice", None, None, None); assert!(rendered.contains("hyperhive agent `alice`")); - assert!(rendered.contains("uses **she/her** pronouns")); + assert!(rendered.contains("say \"the operator\"")); + assert!(!rendered.contains("pronoun")); assert!(!rendered.contains("{label}")); assert!(!rendered.contains("{operator_pronouns}")); } diff --git a/nix/host-modules/hive-options-removed.nix b/nix/host-modules/hive-options-removed.nix index 70e45140..d33315ca 100644 --- a/nix/host-modules/hive-options-removed.nix +++ b/nix/host-modules/hive-options-removed.nix @@ -3,7 +3,7 @@ # # A definition of any of them 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, the +# (hive-agent/prompts/system.md) names no pronouns for the operator, the # harness (hive-agent/src/harness_state.rs) holds the per-model context # windows it uses until a turn reports the model's own, every OTEL exporter # exports every 60 seconds (claude at its SDK default, the rest by a fixed @@ -44,8 +44,9 @@ in config.warnings = removed "c0re.operatorPronouns" cfg.c0re.operatorPronouns '' - the operator's pronouns will be a per-operator profile setting. Until - then every agent's system prompt says she/her. + agents' system prompts name no pronouns for the operator and say "the + operator". Pronouns will come from a per-operator profile setting, which + is an open hyperhive forge issue. '' ++ removed "c0re.contextWindowTokens" cfg.c0re.contextWindowTokens '' the agent harness uses the context window the model reports after each