hive-agent: emit harness-only per-turn otel metrics

This commit is contained in:
damocles 2026-08-24 18:35:44 +02:00 committed by mara
commit eb981e7f5d
4 changed files with 196 additions and 18 deletions

View file

@ -17,6 +17,7 @@ mod identity;
mod login;
mod login_session;
mod mcp_config;
mod otel_turn_metrics;
mod paths;
mod plugins;
mod prompt;
@ -1006,30 +1007,42 @@ async fn handle_turn<S: Surface>(
let tool_calls = bus.take_tool_calls();
let todo_wake_checked =
(from == "todo").then(|| tool_calls.contains_key("mcp__hyperhive__get_loose_ends"));
// Read-and-clear once per turn unconditionally (same rationale as
// `take_tool_calls` above — the harness gets exactly one chance to
// observe this flag) rather than only when the sqlite sink happens to be
// configured: the OTEL turn-metrics exporter's session-boundary counter
// needs it too, independent of `stats`.
let fresh_session = bus.take_fresh_session();
if let Some(stats) = stats {
// Fresh session this turn → mint a `sessions` row and set its id on
// the bus so this turn (and subsequent ones until the next fresh
// start) stamp `turn_stats.session_id`. Takes the one-shot flag
// `run_claude` set when it suppressed `--continue`.
if bus.take_fresh_session() {
// start) stamp `turn_stats.session_id`.
if fresh_session {
let sid = stats.start_session(started_at, &model_at_start);
bus.set_session_id(sid);
}
let ended_at = chrono::Utc::now().timestamp();
let duration_ms = i64::try_from(started_instant.elapsed().as_millis()).unwrap_or(i64::MAX);
let (open_threads, open_reminders) = S::post_turn_counts(socket).await;
let row = serve_common::build_row(serve_common::TurnRowArgs {
started_at,
ended_at,
duration_ms,
model: model_at_start,
wake_from: from.clone(),
outcome: &outcome,
bus,
tool_calls,
open_threads_count: open_threads,
open_reminders_count: open_reminders,
});
}
let ended_at = chrono::Utc::now().timestamp();
let duration_ms = i64::try_from(started_instant.elapsed().as_millis()).unwrap_or(i64::MAX);
let (open_threads, open_reminders) = S::post_turn_counts(socket).await;
let row = serve_common::build_row(serve_common::TurnRowArgs {
started_at,
ended_at,
duration_ms,
model: model_at_start,
wake_from: from.clone(),
outcome: &outcome,
bus,
tool_calls,
open_threads_count: open_threads,
open_reminders_count: open_reminders,
});
// Harness-only OTEL metrics (duration/wake_from/result_kind/loose-ends/
// session boundaries) — independent of the sqlite sink below, and a
// cheap no-op when OTEL isn't configured. See `otel_turn_metrics`'s
// module doc for why token/cost/tool-count are deliberately not here.
otel_turn_metrics::record(&row, fresh_session);
if let Some(stats) = stats {
stats.record(&row);
}
let pending = S::inbox_unread(socket).await;