From cc8f58fb2485cce9883b1592a2ca773e8bfdd011 Mon Sep 17 00:00:00 2001 From: iris Date: Fri, 5 Jun 2026 23:52:16 +0200 Subject: [PATCH 1/4] feat(stats): make ST4TS model price table operator-tunable The hive-wide cost estimate on the dashboard's ST4TS tab used a hard-coded model->price table in hive_stats.rs. Anthropic list pricing drifts, so move the table to a nix option operators can keep current without a code change. - New `services.hyperhive.modelPrices` option: attrset of model-family short name -> { input, output, cache_read, cache_write } USD per million tokens. Passed to `hive-c0re serve --model-prices `. - hive_stats: `Prices` is now public + Deserialize; add `PriceTable` type and `resolve_prices` (longest case-insensitive substring key wins) with the old hard-coded table preserved as `builtin_prices` fallback for any model not covered. - Coordinator holds the parsed table (hive-c0re-local, not injected into containers, so not part of HiveEnv); `/api/stats-hive` reads it. - Docs: dashboard.md ST4TS cost note updated; option self-documents via nixosOptionsDoc. Closes #1434 --- docs/web-ui/dashboard.md | 12 ++++-- hive-c0re/src/coordinator.rs | 10 +++++ hive-c0re/src/dashboard.rs | 11 +++++- hive-c0re/src/hive_stats.rs | 73 ++++++++++++++++++++++++++++-------- hive-c0re/src/main.rs | 18 +++++++++ nix/modules/hive-c0re.nix | 71 ++++++++++++++++++++++++++++++++++- 6 files changed, 172 insertions(+), 23 deletions(-) diff --git a/docs/web-ui/dashboard.md b/docs/web-ui/dashboard.md index 05a28bc3..5c630084 100644 --- a/docs/web-ui/dashboard.md +++ b/docs/web-ui/dashboard.md @@ -333,10 +333,14 @@ the data is fetched on tab activation and on window change. Rendered with plain tables + CSS bars — the dashboard bundle ships no chart library. -The cost figure is a deliberately rough estimate from an approximate -per-model price table (`est_cost_usd`); it drifts with list pricing and -is labelled accordingly. (A follow-up can move the table to a nix -option so it's operator-tunable.) +The cost figure is a deliberately rough estimate from a per-model +price table (`est_cost_usd`); it drifts with list pricing and is +labelled accordingly. The table is operator-tunable via the +`services.hyperhive.modelPrices` nix option — each key is a +model-family short name (matched case-insensitively as a substring of +the model id, longest match wins) mapping to +`{ input, output, cache_read, cache_write }` USD-per-million-token +prices. Models not covered fall back to hive-c0re's built-in estimate. ## P33RS tab diff --git a/hive-c0re/src/coordinator.rs b/hive-c0re/src/coordinator.rs index c9846c9a..690bc2d1 100644 --- a/hive-c0re/src/coordinator.rs +++ b/hive-c0re/src/coordinator.rs @@ -86,6 +86,14 @@ pub struct Coordinator { pub agent_cpu_quota: String, /// Per-agent systemd `MemoryMax=` value (e.g. `"4G"`). Same drop-in. pub agent_memory_max: String, + /// Operator-tunable model→price table backing the hive-wide cost + /// estimate on the ST4TS tab. Set via `services.hyperhive.modelPrices` + /// and passed to `hive-c0re serve --model-prices `. 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. + pub model_prices: crate::hive_stats::PriceTable, agents: Mutex>, /// Agents whose lifecycle action (currently just spawn) is in flight. /// Read by the dashboard to render a spinner; cleared when the action @@ -279,6 +287,7 @@ impl Coordinator { context_window_tokens: std::collections::HashMap, agent_cpu_quota: String, agent_memory_max: String, + model_prices: crate::hive_stats::PriceTable, ) -> Result { let broker = Broker::open(db_path).context("open broker")?; let approvals = Approvals::open(db_path).context("open approvals")?; @@ -314,6 +323,7 @@ impl Coordinator { context_window_tokens, agent_cpu_quota, agent_memory_max, + model_prices, agents: Mutex::new(HashMap::new()), transient: Mutex::new(HashMap::new()), recent_transient: Mutex::new(HashMap::new()), diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs index d452217e..9d9e3b6f 100644 --- a/hive-c0re/src/dashboard.rs +++ b/hive-c0re/src/dashboard.rs @@ -1791,9 +1791,16 @@ struct StatsHiveQuery { /// Hive-wide turn-stats rollup for the dashboard swarm-stats view. /// Aggregates every agent's `hyperhive-turn-stats.sqlite` read-only /// (skips missing/unreadable ones). Window defaults to `24h`. -async fn api_stats_hive(axum::extract::Query(q): axum::extract::Query) -> Response { +async fn api_stats_hive( + State(state): State, + axum::extract::Query(q): axum::extract::Query, +) -> Response { let window = crate::hive_stats::Window::parse(q.window.as_deref().unwrap_or("24h")); - axum::Json(crate::hive_stats::hive_snapshot(window)).into_response() + axum::Json(crate::hive_stats::hive_snapshot( + window, + &state.coord.model_prices, + )) + .into_response() } /// Live per-agent-container CPU + memory load from cgroup v2. Samples diff --git a/hive-c0re/src/hive_stats.rs b/hive-c0re/src/hive_stats.rs index 7f23b7df..470fb457 100644 --- a/hive-c0re/src/hive_stats.rs +++ b/hive-c0re/src/hive_stats.rs @@ -20,7 +20,7 @@ use std::path::Path; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use rusqlite::{Connection, OpenFlags}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use crate::coordinator::Coordinator; @@ -73,19 +73,34 @@ impl Window { } } -/// Approximate USD price per **million** tokens, per model class. -/// Matched by substring against the model id. This is a deliberately -/// rough estimate — Anthropic list pricing drifts, so the dashboard -/// labels the figure as an estimate. A follow-up can wire it from a -/// nix option (like `contextWindowTokens`) instead of hard-coding. -struct Prices { - input: f64, - output: f64, - cache_read: f64, - cache_write: f64, +/// Approximate USD price per **million** tokens for one model class. +/// The four fields map to the four token kinds the turn-stats schema +/// records. This is a deliberately rough estimate — Anthropic list +/// pricing drifts, so the dashboard labels the figure as an estimate. +/// +/// Operators keep the table current without a code change via the +/// `services.hyperhive.modelPrices` nix option (passed to +/// `hive-c0re serve --model-prices `, held on the `Coordinator` +/// as a [`PriceTable`]). Any model not covered by the operator table +/// falls back to [`builtin_prices`]. +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct Prices { + pub input: f64, + pub output: f64, + pub cache_read: f64, + pub cache_write: f64, } -fn model_prices(model: &str) -> Prices { +/// Operator-tunable model→price map. Keys are matched +/// case-insensitively as a substring of the model id (e.g. `"sonnet"` +/// matches `"claude-sonnet-4-5"`); the longest matching key wins so a +/// specific entry beats a generic family name. An empty map means +/// every model uses [`builtin_prices`]. +pub type PriceTable = HashMap; + +/// Built-in fallback pricing — the historical hard-coded table. Used +/// when the operator's [`PriceTable`] has no key matching the model. +fn builtin_prices(model: &str) -> Prices { let m = model.to_ascii_lowercase(); if m.contains("opus") { Prices { @@ -112,6 +127,32 @@ fn model_prices(model: &str) -> Prices { } } +/// Resolve the price for `model`: prefer the operator-provided +/// `table` (longest matching key wins, so a specific +/// `claude-3-5-sonnet` entry beats a generic `sonnet`), else fall back +/// to [`builtin_prices`]. +fn resolve_prices(model: &str, table: &PriceTable) -> Prices { + let m = model.to_ascii_lowercase(); + let mut best: Option<(usize, Prices)> = None; + for (k, v) in table { + let key = k.to_ascii_lowercase(); + if key.is_empty() || !m.contains(&key) { + continue; + } + let better = match best { + Some((blen, _)) => key.len() > blen, + None => true, + }; + if better { + best = Some((key.len(), *v)); + } + } + match best { + Some((_, p)) => p, + None => builtin_prices(&m), + } +} + #[derive(Debug, Serialize)] pub struct KeyCount { pub key: String, @@ -179,7 +220,7 @@ fn u64_from_i64(v: i64) -> u64 { /// Aggregate one agent's turn-stats over `[from, now]`. Errors bubble /// up so the caller can skip a single bad/locked db without failing /// the whole endpoint. -fn read_agent(path: &Path, from: i64) -> rusqlite::Result { +fn read_agent(path: &Path, from: i64, prices: &PriceTable) -> rusqlite::Result { let conn = Connection::open_with_flags(path, OpenFlags::SQLITE_OPEN_READ_ONLY)?; // turn_stats is rollback-journal (not WAL): a read landing while the // harness is mid-INSERT would get SQLITE_BUSY and drop that active @@ -208,7 +249,7 @@ fn read_agent(path: &Path, from: i64) -> rusqlite::Result { agg.output = agg.output.saturating_add(output); agg.cache_read = agg.cache_read.saturating_add(cache_read); agg.cache_creation = agg.cache_creation.saturating_add(cache_creation); - let p = model_prices(&model); + let p = resolve_prices(&model, prices); #[allow( clippy::cast_precision_loss, reason = "token counts stay well under f64's 2^53 exact-integer range, so this cost computation loses no precision" @@ -228,7 +269,7 @@ fn read_agent(path: &Path, from: i64) -> rusqlite::Result { /// Build the swarm-wide rollup. Best-effort: a missing or unreadable /// per-agent db is skipped (logged), never fatal. #[must_use] -pub fn hive_snapshot(window: Window) -> HiveStats { +pub fn hive_snapshot(window: Window, prices: &PriceTable) -> HiveStats { let now = now_secs(); let from = now - window.span_secs(); @@ -247,7 +288,7 @@ pub fn hive_snapshot(window: Window) -> HiveStats { if !path.exists() { continue; } - let agg = match read_agent(&path, from) { + let agg = match read_agent(&path, from, prices) { Ok(a) => a, Err(e) => { tracing::warn!(agent = %name, error = ?e, "hive-stats: read failed; skipping"); diff --git a/hive-c0re/src/main.rs b/hive-c0re/src/main.rs index 53a3f452..4c506284 100644 --- a/hive-c0re/src/main.rs +++ b/hive-c0re/src/main.rs @@ -82,6 +82,18 @@ enum Cmd { /// Set via `services.hyperhive.agentMemoryMax`. #[arg(long, default_value = "4G")] agent_memory_max: String, + /// Per-model USD prices (per million tokens) for the hive-wide + /// ST4TS cost estimate, as a JSON object mapping a model-family + /// short name to `{input, output, cache_read, cache_write}`. + /// Keys are matched case-insensitively as a substring of the + /// model id; models not covered fall back to the built-in + /// estimate. Set via the `services.hyperhive.modelPrices` NixOS + /// option. + #[arg( + long, + default_value = r#"{"opus":{"input":15.0,"output":75.0,"cache_read":1.5,"cache_write":18.75},"sonnet":{"input":3.0,"output":15.0,"cache_read":0.3,"cache_write":3.75},"haiku":{"input":0.8,"output":4.0,"cache_read":0.08,"cache_write":1.0}}"# + )] + model_prices: String, }, /// Spawn a new agent container directly (`hive-agent-`). Bypasses /// the approval queue — use only as an operator on the host. For @@ -152,6 +164,7 @@ async fn main() -> Result<()> { context_window_tokens, agent_cpu_quota, agent_memory_max, + model_prices, } => { cmd_serve( hyperhive_flake, @@ -163,6 +176,7 @@ async fn main() -> Result<()> { context_window_tokens, agent_cpu_quota, agent_memory_max, + model_prices, &cli.socket, ) .await @@ -221,6 +235,7 @@ async fn cmd_serve( context_window_tokens: String, agent_cpu_quota: String, agent_memory_max: String, + model_prices: String, socket: &std::path::Path, ) -> Result<()> { // Move any host-side state still at the legacy flat layout into its @@ -230,6 +245,8 @@ async fn cmd_serve( hive_c0re::paths::relocate_legacy_state(); let cwt: std::collections::HashMap = serde_json::from_str(&context_window_tokens) .context("--context-window-tokens: invalid JSON")?; + let prices: hive_c0re::hive_stats::PriceTable = + serde_json::from_str(&model_prices).context("--model-prices: invalid JSON")?; let coord = Arc::new(Coordinator::open( &db, hyperhive_flake, @@ -240,6 +257,7 @@ async fn cmd_serve( cwt, agent_cpu_quota, agent_memory_max, + prices, )?); manager_server::start(coord.clone())?; // Idempotent pre-flight: rewrite pre-meta-layout applied diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index d66bc99b..4ef9e2dd 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -423,6 +423,75 @@ in ''; }; + modelPrices = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule { + options = { + input = lib.mkOption { + type = lib.types.float; + description = "USD per million input tokens."; + }; + output = lib.mkOption { + type = lib.types.float; + description = "USD per million output tokens."; + }; + cache_read = lib.mkOption { + type = lib.types.float; + description = "USD per million cache-read tokens."; + }; + cache_write = lib.mkOption { + type = lib.types.float; + description = "USD per million cache-creation (write) tokens."; + }; + }; + } + ); + default = { + opus = { + input = 15.0; + output = 75.0; + cache_read = 1.5; + cache_write = 18.75; + }; + sonnet = { + input = 3.0; + output = 15.0; + cache_read = 0.3; + cache_write = 3.75; + }; + haiku = { + input = 0.8; + output = 4.0; + cache_read = 0.08; + cache_write = 1.0; + }; + }; + example = { + sonnet = { + input = 3.0; + output = 15.0; + cache_read = 0.3; + cache_write = 3.75; + }; + }; + description = '' + Per-model USD prices (per **million** tokens) used for the + hive-wide cost *estimate* on the dashboard's ST4TS tab. Each key + is a model-family short name matched case-insensitively as a + substring of the active model id at runtime (e.g. `"sonnet"` + matches `"claude-sonnet-4-5"`); the longest matching key wins, so + a specific entry beats a generic family name. Any model not + covered by this table falls back to hive-c0re's built-in + estimate. + + The defaults track Anthropic list pricing at the time of + writing — override them here to keep the estimate current + without a code change. Passed to `hive-c0re serve` as JSON via + `--model-prices`; read only by hive-c0re itself (not injected + into containers). Changes apply on the next host rebuild. + ''; + }; + agentCpuQuota = lib.mkOption { type = lib.types.str; default = "200%"; @@ -662,7 +731,7 @@ in ); }; serviceConfig = { - ExecStart = "${cfg.package}/bin/hive-c0re --socket /run/hyperhive/host.sock serve --hyperhive-flake ${cfg.hyperhiveFlake} --nixpkgs-flake ${cfg.nixpkgsFlake} --nixpkgs-unstable-flake ${cfg.nixpkgsUnstableFlake} --dashboard-port ${toString cfg.dashboardPort} --operator-pronouns ${lib.escapeShellArg cfg.operatorPronouns} --context-window-tokens ${lib.escapeShellArg (builtins.toJSON cfg.contextWindowTokens)} --agent-cpu-quota ${lib.escapeShellArg cfg.agentCpuQuota} --agent-memory-max ${lib.escapeShellArg cfg.agentMemoryMax}"; + ExecStart = "${cfg.package}/bin/hive-c0re --socket /run/hyperhive/host.sock serve --hyperhive-flake ${cfg.hyperhiveFlake} --nixpkgs-flake ${cfg.nixpkgsFlake} --nixpkgs-unstable-flake ${cfg.nixpkgsUnstableFlake} --dashboard-port ${toString cfg.dashboardPort} --operator-pronouns ${lib.escapeShellArg cfg.operatorPronouns} --context-window-tokens ${lib.escapeShellArg (builtins.toJSON cfg.contextWindowTokens)} --agent-cpu-quota ${lib.escapeShellArg cfg.agentCpuQuota} --agent-memory-max ${lib.escapeShellArg cfg.agentMemoryMax} --model-prices ${lib.escapeShellArg (builtins.toJSON cfg.modelPrices)}"; # Migrate hive-c0re's *own* state to the service user after an # upgrade from a root-run install (systemd's StateDirectory only # chowns the top-level dir, not pre-existing files inside it). The From 60caef73f96d5d9a009fd7892e91c772268aae54 Mon Sep 17 00:00:00 2001 From: iris Date: Fri, 5 Jun 2026 23:55:33 +0200 Subject: [PATCH 2/4] review: address damocles nits on modelPrices - modelPrices submodule fields use lib.types.numbers.nonnegative instead of lib.types.float: accepts bare ints (15) as well as floats (15.0) and rejects negative prices for free. - Collapse the triple-sourced default: hive-c0re serve --model-prices now defaults to "{}" so builtin_prices() is the single in-code fallback. The nix option default still carries the full opus/sonnet/haiku table to self-document prices for operators. --- hive-c0re/src/main.rs | 10 +++++----- nix/modules/hive-c0re.nix | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/hive-c0re/src/main.rs b/hive-c0re/src/main.rs index 4c506284..81c1bdf9 100644 --- a/hive-c0re/src/main.rs +++ b/hive-c0re/src/main.rs @@ -88,11 +88,11 @@ enum Cmd { /// Keys are matched case-insensitively as a substring of the /// model id; models not covered fall back to the built-in /// estimate. Set via the `services.hyperhive.modelPrices` NixOS - /// option. - #[arg( - long, - default_value = r#"{"opus":{"input":15.0,"output":75.0,"cache_read":1.5,"cache_write":18.75},"sonnet":{"input":3.0,"output":15.0,"cache_read":0.3,"cache_write":3.75},"haiku":{"input":0.8,"output":4.0,"cache_read":0.08,"cache_write":1.0}}"# - )] + /// option (whose default carries the full opus/sonnet/haiku + /// table). Defaults to `{}` here so a bare `hive-c0re serve` + /// leans entirely on the in-code `builtin_prices` fallback — + /// keeping a single source of truth for the numbers. + #[arg(long, default_value = "{}")] model_prices: String, }, /// Spawn a new agent container directly (`hive-agent-`). Bypasses diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index 4ef9e2dd..9b0d7b1f 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -428,19 +428,19 @@ in lib.types.submodule { options = { input = lib.mkOption { - type = lib.types.float; + type = lib.types.numbers.nonnegative; description = "USD per million input tokens."; }; output = lib.mkOption { - type = lib.types.float; + type = lib.types.numbers.nonnegative; description = "USD per million output tokens."; }; cache_read = lib.mkOption { - type = lib.types.float; + type = lib.types.numbers.nonnegative; description = "USD per million cache-read tokens."; }; cache_write = lib.mkOption { - type = lib.types.float; + type = lib.types.numbers.nonnegative; description = "USD per million cache-creation (write) tokens."; }; }; From a9560ebb513243b829a3fd499d6a0839df8dad6c Mon Sep 17 00:00:00 2001 From: iris Date: Mon, 8 Jun 2026 19:34:56 +0200 Subject: [PATCH 3/4] review: update default model prices to current Anthropic list pricing Per operator request on the PR: the built-in/default prices were the old Claude 3 numbers (opus 15/75, etc.). Update opus + haiku to the current Claude 4.x family list pricing (cache_write = the default 5-minute cache TTL); sonnet was already correct: - opus: input 5, output 25, cache_read 0.5, cache_write 6.25 - sonnet: input 3, output 15, cache_read 0.3, cache_write 3.75 (unchanged) - haiku: input 1, output 5, cache_read 0.1, cache_write 1.25 Updated in both builtin_prices (hive_stats.rs) and the nix modelPrices default (hive-c0re.nix), with cross-reference "keep in sync" notes on both sides. Also addresses the earlier reviewer note: dropped the over-strong "single source of truth" wording in the --model-prices arg doc (the nix default does mirror the numbers in production). --- hive-c0re/src/hive_stats.rs | 24 ++++++++++++++---------- hive-c0re/src/main.rs | 7 ++++--- nix/modules/hive-c0re.nix | 20 ++++++++++++-------- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/hive-c0re/src/hive_stats.rs b/hive-c0re/src/hive_stats.rs index 470fb457..413661d8 100644 --- a/hive-c0re/src/hive_stats.rs +++ b/hive-c0re/src/hive_stats.rs @@ -98,23 +98,27 @@ pub struct Prices { /// every model uses [`builtin_prices`]. pub type PriceTable = HashMap; -/// Built-in fallback pricing — the historical hard-coded table. Used -/// when the operator's [`PriceTable`] has no key matching the model. +/// Built-in fallback pricing, per **million** tokens. Used when the +/// operator's [`PriceTable`] has no key matching the model. Figures are +/// current Anthropic list prices for the Claude 4.x family (Opus 4.x, +/// Sonnet 4.x, Haiku 4.5); `cache_write` uses the default 5-minute +/// cache-TTL price. Keep in sync with the `services.hyperhive.modelPrices` +/// nix default (`nix/modules/hive-c0re.nix`). fn builtin_prices(model: &str) -> Prices { let m = model.to_ascii_lowercase(); if m.contains("opus") { Prices { - input: 15.0, - output: 75.0, - cache_read: 1.5, - cache_write: 18.75, + input: 5.0, + output: 25.0, + cache_read: 0.5, + cache_write: 6.25, } } else if m.contains("haiku") { Prices { - input: 0.8, - output: 4.0, - cache_read: 0.08, - cache_write: 1.0, + input: 1.0, + output: 5.0, + cache_read: 0.1, + cache_write: 1.25, } } else { // sonnet + unknown fallback diff --git a/hive-c0re/src/main.rs b/hive-c0re/src/main.rs index 81c1bdf9..e37fec13 100644 --- a/hive-c0re/src/main.rs +++ b/hive-c0re/src/main.rs @@ -89,9 +89,10 @@ enum Cmd { /// model id; models not covered fall back to the built-in /// estimate. Set via the `services.hyperhive.modelPrices` NixOS /// option (whose default carries the full opus/sonnet/haiku - /// table). Defaults to `{}` here so a bare `hive-c0re serve` - /// leans entirely on the in-code `builtin_prices` fallback — - /// keeping a single source of truth for the numbers. + /// table — kept in sync with the in-code `builtin_prices`). + /// Defaults to `{}` here so a bare `hive-c0re serve` leans + /// entirely on `builtin_prices`; the NixOS default (every real + /// deployment) shadows it with the same operator-tunable table. #[arg(long, default_value = "{}")] model_prices: String, }, diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index 9b0d7b1f..28013c70 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -446,12 +446,16 @@ in }; } ); + # Current Anthropic list prices for the Claude 4.x family (Opus + # 4.x, Sonnet 4.x, Haiku 4.5); cache_write is the default 5-minute + # cache-TTL price. Keep in sync with `builtin_prices` in + # hive-c0re/src/hive_stats.rs. default = { opus = { - input = 15.0; - output = 75.0; - cache_read = 1.5; - cache_write = 18.75; + input = 5.0; + output = 25.0; + cache_read = 0.5; + cache_write = 6.25; }; sonnet = { input = 3.0; @@ -460,10 +464,10 @@ in cache_write = 3.75; }; haiku = { - input = 0.8; - output = 4.0; - cache_read = 0.08; - cache_write = 1.0; + input = 1.0; + output = 5.0; + cache_read = 0.1; + cache_write = 1.25; }; }; example = { From b066af010b0c8ec87a0f661fa33e27b2edfb2519 Mon Sep 17 00:00:00 2001 From: iris Date: Mon, 8 Jun 2026 20:03:47 +0200 Subject: [PATCH 4/4] review: use 1-hour cache-TTL prices (the subscription default) --- hive-c0re/src/hive_stats.rs | 13 +++++++------ nix/modules/hive-c0re.nix | 11 ++++++----- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/hive-c0re/src/hive_stats.rs b/hive-c0re/src/hive_stats.rs index 413661d8..3951fb40 100644 --- a/hive-c0re/src/hive_stats.rs +++ b/hive-c0re/src/hive_stats.rs @@ -101,9 +101,10 @@ pub type PriceTable = HashMap; /// Built-in fallback pricing, per **million** tokens. Used when the /// operator's [`PriceTable`] has no key matching the model. Figures are /// current Anthropic list prices for the Claude 4.x family (Opus 4.x, -/// Sonnet 4.x, Haiku 4.5); `cache_write` uses the default 5-minute -/// cache-TTL price. Keep in sync with the `services.hyperhive.modelPrices` -/// nix default (`nix/modules/hive-c0re.nix`). +/// Sonnet 4.x, Haiku 4.5); `cache_write` uses the 1-hour cache-TTL price, +/// which is the default through the Claude subscription the agents run +/// on. Keep in sync with the `services.hyperhive.modelPrices` nix default +/// (`nix/modules/hive-c0re.nix`). fn builtin_prices(model: &str) -> Prices { let m = model.to_ascii_lowercase(); if m.contains("opus") { @@ -111,14 +112,14 @@ fn builtin_prices(model: &str) -> Prices { input: 5.0, output: 25.0, cache_read: 0.5, - cache_write: 6.25, + cache_write: 10.0, } } else if m.contains("haiku") { Prices { input: 1.0, output: 5.0, cache_read: 0.1, - cache_write: 1.25, + cache_write: 2.0, } } else { // sonnet + unknown fallback @@ -126,7 +127,7 @@ fn builtin_prices(model: &str) -> Prices { input: 3.0, output: 15.0, cache_read: 0.3, - cache_write: 3.75, + cache_write: 6.0, } } } diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index 28013c70..05291b6d 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -447,27 +447,28 @@ in } ); # Current Anthropic list prices for the Claude 4.x family (Opus - # 4.x, Sonnet 4.x, Haiku 4.5); cache_write is the default 5-minute - # cache-TTL price. Keep in sync with `builtin_prices` in + # 4.x, Sonnet 4.x, Haiku 4.5); cache_write is the 1-hour cache-TTL + # price (the default through the Claude subscription the agents run + # on). Keep in sync with `builtin_prices` in # hive-c0re/src/hive_stats.rs. default = { opus = { input = 5.0; output = 25.0; cache_read = 0.5; - cache_write = 6.25; + cache_write = 10.0; }; sonnet = { input = 3.0; output = 15.0; cache_read = 0.3; - cache_write = 3.75; + cache_write = 6.0; }; haiku = { input = 1.0; output = 5.0; cache_read = 0.1; - cache_write = 1.25; + cache_write = 2.0; }; }; example = {