harness metrics for tool calls, bash commands, reminders and first-turn context, on the agents board
The per-agent /stats page read these from its sqlite; the swarm board had none of them. hive-agent now counts the tools a turn called, the commands bash tasks ran (from the bash daemon's own table, newer rows only), reminders scheduled and delivered, and records the first turn of a fresh session; the turns tab shows them, with fresh sessions per agent. Claude's own export has no tool-call counter, so the docs no longer say it does.
This commit is contained in:
parent
5f9629dfc7
commit
23c2527199
1 changed files with 618 additions and 28 deletions
|
|
@ -449,6 +449,32 @@ fn read_bash_breakdown(conn: &Connection, from: i64) -> rusqlite::Result<Vec<Key
|
|||
Ok(top_n(totals, 10))
|
||||
}
|
||||
|
||||
/// The shell-command heads recorded after `since` (unix seconds, exclusive),
|
||||
/// with how often each ran, for the OTEL bash-command counter. Reads the live
|
||||
/// database read-only; a missing database or `bash_commands` table (no bash
|
||||
/// task has run yet) is an empty list, not an error.
|
||||
#[must_use]
|
||||
pub fn bash_heads_since(since: i64) -> Vec<(String, u64)> {
|
||||
let Ok(conn) = Connection::open_with_flags(default_path(), OpenFlags::SQLITE_OPEN_READ_ONLY)
|
||||
else {
|
||||
return Vec::new();
|
||||
};
|
||||
let _ = conn.busy_timeout(std::time::Duration::from_millis(500));
|
||||
read_bash_heads_since(&conn, since).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn read_bash_heads_since(conn: &Connection, since: i64) -> rusqlite::Result<Vec<(String, u64)>> {
|
||||
let mut stmt =
|
||||
conn.prepare("SELECT head, COUNT(*) FROM bash_commands WHERE ts > ?1 GROUP BY head")?;
|
||||
let rows = stmt.query_map([since], |row| {
|
||||
Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
u64_from_i64(row.get::<_, i64>(1)?),
|
||||
))
|
||||
})?;
|
||||
rows.collect()
|
||||
}
|
||||
|
||||
struct Row {
|
||||
started_at: i64,
|
||||
duration_ms: i64,
|
||||
|
|
@ -782,6 +808,29 @@ mod tests {
|
|||
assert!(s.bash_breakdown.is_empty());
|
||||
}
|
||||
|
||||
/// The OTEL bash-command counter reads only the heads recorded after its
|
||||
/// watermark, and a database without the table yields nothing.
|
||||
#[test]
|
||||
fn bash_heads_since_counts_only_newer_rows() {
|
||||
let db = tmp_db();
|
||||
let _ = std::fs::remove_file(&db);
|
||||
let conn = Connection::open(&db).unwrap();
|
||||
assert!(read_bash_heads_since(&conn, 0).is_err(), "no table yet");
|
||||
conn.execute_batch("CREATE TABLE bash_commands (ts INTEGER NOT NULL, head TEXT NOT NULL);")
|
||||
.unwrap();
|
||||
for (ts, head) in [(10, "cargo"), (20, "cargo"), (30, "git"), (30, "cargo")] {
|
||||
conn.execute(
|
||||
"INSERT INTO bash_commands (ts, head) VALUES (?1, ?2)",
|
||||
params![ts, head],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
let mut got = read_bash_heads_since(&conn, 10).unwrap();
|
||||
got.sort();
|
||||
assert_eq!(got, vec![("cargo".to_owned(), 2), ("git".to_owned(), 1)]);
|
||||
assert!(read_bash_heads_since(&conn, 30).unwrap().is_empty());
|
||||
}
|
||||
|
||||
/// With a populated `bash_commands` table, `bash_breakdown` rolls up
|
||||
/// per-head counts (busiest first) and respects the window cutoff.
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in a new issue