Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9f313e75d | ||
|
|
f8960dd501 | ||
|
|
0a5f35bda3 | ||
|
|
4b5707768c |
29 changed files with 252 additions and 404 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ labels (`hive`, `swarm`, …) ride on the resource via
|
|||
|
||||
<!-- vale write-good.Passive = NO -->
|
||||
|
||||
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
|
||||
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 =
|
||||
|
|
|
|||
|
|
@ -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 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.
|
||||
|
|
|
|||
|
|
@ -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 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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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<Instruments> {
|
|||
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<SdkMeterProvider> {
|
|||
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::<u64>().ok())
|
||||
.filter(|ms| *ms > 0)
|
||||
.map_or(DEFAULT_INTERVAL, Duration::from_millis)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,12 @@ 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_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("**they/them** pronouns"));
|
||||
assert!(rendered.contains("say \"the operator\""));
|
||||
assert!(!rendered.contains("pronoun"));
|
||||
assert!(!rendered.contains("{label}"));
|
||||
assert!(!rendered.contains("{operator_pronouns}"));
|
||||
}
|
||||
|
|
@ -250,7 +245,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 +255,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 +275,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 +291,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 +301,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 +314,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 +327,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 +337,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 +352,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}"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -566,10 +555,6 @@ struct OtelConfig {
|
|||
endpoint: String,
|
||||
protocol: String,
|
||||
extra_resource_attributes: Option<String>,
|
||||
metric_interval_ms: Option<u64>,
|
||||
/// `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.
|
||||
|
|
@ -589,19 +574,10 @@ fn otel_config() -> Option<OtelConfig> {
|
|||
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::<u64>().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,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -742,10 +718,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 +727,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 +802,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
|
||||
|
|
@ -921,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
|
||||
|
|
@ -1025,20 +982,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 +1259,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 +1303,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 +1319,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 +1357,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 +1383,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 +1401,6 @@ mod tests {
|
|||
"",
|
||||
None,
|
||||
8000,
|
||||
"she/her",
|
||||
&std::collections::HashMap::new(),
|
||||
&[sample_spec("alice", false, 9001)],
|
||||
);
|
||||
assert!(
|
||||
|
|
@ -1496,8 +1429,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 +1460,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 +1496,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 +1539,6 @@ mod tests {
|
|||
"path:/nix/store/aaaa-nixpkgs-source",
|
||||
None,
|
||||
8000,
|
||||
"she/her",
|
||||
&std::collections::HashMap::new(),
|
||||
&[sample_spec("alice", false, 9001)],
|
||||
)
|
||||
};
|
||||
|
|
@ -1792,8 +1717,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 +1751,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 +1793,6 @@ mod tests {
|
|||
"path:/nix/store/aaaa-nixpkgs-source",
|
||||
None,
|
||||
8000,
|
||||
"she/her",
|
||||
&std::collections::HashMap::new(),
|
||||
&[sample_spec("alice", false, 9001)],
|
||||
)
|
||||
};
|
||||
|
|
@ -1964,8 +1883,6 @@ mod tests {
|
|||
"path:/nix/store/aaaa-nixpkgs-source",
|
||||
None,
|
||||
8000,
|
||||
"she/her",
|
||||
&std::collections::HashMap::new(),
|
||||
&[sample_spec("alice", false, 9001)],
|
||||
)
|
||||
};
|
||||
|
|
@ -2038,8 +1955,6 @@ mod tests {
|
|||
"path:/nix/store/aaaa-nixpkgs-source",
|
||||
None,
|
||||
8000,
|
||||
"she/her",
|
||||
&std::collections::HashMap::new(),
|
||||
&[sample_spec("alice", false, 9001)],
|
||||
)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<String> {
|
|||
.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::<u64>().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)> {
|
||||
|
|
|
|||
|
|
@ -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<Mutex<Vec<StateCount>>> = 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<String> {
|
|||
.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::<u64>().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)> {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ in
|
|||
./memory-cap.nix
|
||||
./network.nix
|
||||
./otel.nix
|
||||
./otel-removed.nix
|
||||
./packages.nix
|
||||
./queue.nix
|
||||
./queue-identity.nix
|
||||
|
|
@ -280,9 +281,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";
|
||||
|
|
|
|||
27
nix/agent-modules/otel-removed.nix
Normal file
27
nix/agent-modules/otel-removed.nix
Normal file
|
|
@ -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.
|
||||
'';
|
||||
}
|
||||
|
|
@ -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.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@
|
|||
./swarm-victoriametrics.nix
|
||||
./swarm-wireguard.nix
|
||||
./swarm.nix
|
||||
./hive-options-removed.nix
|
||||
./host-ca-removed.nix
|
||||
./nats-callout-removed.nix
|
||||
./swarm-required-services.nix
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.<domain>`), which
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
64
nix/host-modules/hive-options-removed.nix
Normal file
64
nix/host-modules/hive-options-removed.nix
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# 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 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
|
||||
# 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.
|
||||
{ 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 ''
|
||||
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
|
||||
turn, and before the first one its own defaults (haiku 200000, sonnet
|
||||
1000000, opus 1000000 tokens).
|
||||
''
|
||||
++ removed "otel.metricIntervalMs" cfg.otel.metricIntervalMs ''
|
||||
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
|
||||
services.hyperhive.agent.otel.debug = true in that agent's own config.
|
||||
'';
|
||||
}
|
||||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -48,8 +48,15 @@ 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 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.
|
||||
withCi = hive {
|
||||
|
|
@ -237,6 +244,31 @@ let
|
|||
in
|
||||
!(e ? HIVE_AGENT_NATS_URL) && !(e ? HIVE_AGENT_OIDC_TOKEN_ENDPOINT);
|
||||
}
|
||||
{
|
||||
# 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 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.${option} is removed and ignored" w) m.warnings;
|
||||
options = [
|
||||
"c0re.operatorPronouns"
|
||||
"c0re.contextWindowTokens"
|
||||
"otel.metricIntervalMs"
|
||||
"otel.debug"
|
||||
];
|
||||
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`
|
||||
|
|
|
|||
|
|
@ -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<Instruments> {
|
||||
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<String> {
|
|||
.filter(|s| !s.is_empty())
|
||||
}
|
||||
|
||||
fn interval() -> Duration {
|
||||
std::env::var("HYPERHIVE_OTEL_METRIC_INTERVAL_MS")
|
||||
.ok()
|
||||
.and_then(|s| s.trim().parse::<u64>().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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue