hive-agent: export ACP-reported cost and context fill over OTLP
An ACP agent's `usage_update` carries `cost.{amount,currency}`, the
session's running total (opencode sums every assistant message in the
session). hive-runtime now reads it and turns the running total into
what each report added: a new session counts from zero, a session loaded
into a freshly started agent only baselines on its first report, and a
falling total adds nothing. The spend is held on the runtime until
`Runtime::take_reported_cost` drains it; claude's runtime reports none,
since the claude binary already exports `claude_code.cost.usage`.
hive-agent's existing turn-metrics meter records three new instruments:
- `hyperhive.agent.cost.usage` (counter, `model` + `currency`), ACP only;
- `hyperhive.agent.context.used` / `.size` (gauges, no attributes), for
every backend: the two numbers the web UI's ctx% divides.
The `hyperhive · agents` dashboard gets ACP cost panels on its cost tab
and a context-fill panel on its health tab.
Refs #4845
This commit is contained in:
parent
5d7042e655
commit
c2bdf30e05
8 changed files with 605 additions and 26 deletions
|
|
@ -6,8 +6,8 @@
|
|||
//! only the harness-only concepts: wall-clock turn duration as *this harness*
|
||||
//! measures it (not Claude's own per-request latency), what woke the turn,
|
||||
//! the harness's own outcome classification, the loose-ends backlog at turn
|
||||
//! end, and session boundaries (fresh vs. `--continue`'d). Scoped this way
|
||||
//! per the tracker discussion on the "emit agent stats as OTEL metrics" issue.
|
||||
//! end, session boundaries (fresh vs. `--continue`'d), context-window fill,
|
||||
//! and the cost an ACP agent reports — claude's runtime reports none.
|
||||
//!
|
||||
//! Synchronous instruments, not observable ones: unlike hive-c0re's
|
||||
//! container-resource gauges (`hive-c0re/src/stats/otel_metrics.rs`), which
|
||||
|
|
@ -62,6 +62,11 @@ struct Instruments {
|
|||
/// `window`.
|
||||
claude_usage_percent: Gauge<f64>,
|
||||
claude_usage_resets_at: Gauge<u64>,
|
||||
context_used: Gauge<u64>,
|
||||
context_size: Gauge<u64>,
|
||||
/// Keyed by `model` and `currency`: an ACP agent reports cost in a
|
||||
/// currency of its choosing.
|
||||
reported_cost: Counter<f64>,
|
||||
}
|
||||
|
||||
/// Lazily built on the first call to [`record`]. `None` when OTEL isn't
|
||||
|
|
@ -129,6 +134,30 @@ pub fn record_claude_usage(window: &str, percent: f64, resets_at: Option<u64>) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Record the context window's fill at turn end: `used` tokens of `size`,
|
||||
/// the two numbers the web UI's ctx% divides. No-op when OTEL isn't
|
||||
/// configured, same as [`record`].
|
||||
pub fn record_context(used: u64, size: u64) {
|
||||
let Some(inst) = INSTRUMENTS.get_or_init(build).as_ref() else {
|
||||
return;
|
||||
};
|
||||
inst.context_used.record(used, &[]);
|
||||
inst.context_size.record(size, &[]);
|
||||
}
|
||||
|
||||
/// Add what an ACP agent reported spending since the last call. No-op when
|
||||
/// OTEL isn't configured, same as [`record`].
|
||||
pub fn record_reported_cost(model: &str, cost: &hive_runtime::ReportedCost) {
|
||||
let Some(inst) = INSTRUMENTS.get_or_init(build).as_ref() else {
|
||||
return;
|
||||
};
|
||||
let attrs = [
|
||||
KeyValue::new("model", model.to_owned()),
|
||||
KeyValue::new("currency", cost.currency.clone()),
|
||||
];
|
||||
inst.reported_cost.add(cost.amount, &attrs);
|
||||
}
|
||||
|
||||
fn build() -> Option<Instruments> {
|
||||
if !enabled() {
|
||||
tracing::debug!("otel turn-metrics: no endpoint configured, exporter disabled");
|
||||
|
|
@ -161,6 +190,15 @@ fn build() -> Option<Instruments> {
|
|||
.u64_gauge("hyperhive.agent.claude_usage.resets_at")
|
||||
.with_unit("s")
|
||||
.build(),
|
||||
context_used: meter
|
||||
.u64_gauge("hyperhive.agent.context.used")
|
||||
.with_unit("{token}")
|
||||
.build(),
|
||||
context_size: meter
|
||||
.u64_gauge("hyperhive.agent.context.size")
|
||||
.with_unit("{token}")
|
||||
.build(),
|
||||
reported_cost: meter.f64_counter("hyperhive.agent.cost.usage").build(),
|
||||
_provider: provider,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue