Persist agent stats across restarts, add per-tool metrics grid
Stats now survive daemon restarts via ~/.consciousness/agent-stats.json, loaded into a global Mutex<HashMap> on first access. Each tool type tracks last count, EWMA (alpha=0.3), and total calls. UI shows a grid view: tool | last | avg | total, sorted by total desc. Failures row appears at bottom if any occurred. Also fixes temperature/priority not being applied to spawned agents. Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
314ae9c4cb
commit
f408bb5d86
4 changed files with 148 additions and 56 deletions
|
|
@ -22,7 +22,8 @@ use super::Agent;
|
|||
// Agent logging — shared by Mind and CLI paths
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone, Default, serde::Serialize)]
|
||||
/// Stats from a single run.
|
||||
#[derive(Clone, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct RunStats {
|
||||
pub messages: usize,
|
||||
pub tool_calls: usize,
|
||||
|
|
@ -30,6 +31,57 @@ pub struct RunStats {
|
|||
pub tool_calls_by_type: HashMap<String, usize>,
|
||||
}
|
||||
|
||||
/// Per-tool accumulated stats.
|
||||
#[derive(Clone, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ToolStats {
|
||||
pub last: usize,
|
||||
pub ewma: f64,
|
||||
pub total: usize,
|
||||
}
|
||||
|
||||
/// Persisted stats for an agent (survives restarts).
|
||||
#[derive(Clone, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct PersistedStats {
|
||||
pub runs: usize,
|
||||
pub last_stats: Option<RunStats>,
|
||||
/// Per-tool-type stats: last, ewma, total.
|
||||
pub by_tool: HashMap<String, ToolStats>,
|
||||
/// Failed calls stats.
|
||||
pub failures: ToolStats,
|
||||
}
|
||||
|
||||
fn stats_path() -> std::path::PathBuf {
|
||||
dirs::home_dir().unwrap_or_default()
|
||||
.join(".consciousness/agent-stats.json")
|
||||
}
|
||||
|
||||
static AGENT_STATS: std::sync::OnceLock<std::sync::Mutex<HashMap<String, PersistedStats>>> =
|
||||
std::sync::OnceLock::new();
|
||||
|
||||
fn stats_map() -> &'static std::sync::Mutex<HashMap<String, PersistedStats>> {
|
||||
AGENT_STATS.get_or_init(|| {
|
||||
let map: HashMap<String, PersistedStats> = std::fs::read_to_string(stats_path()).ok()
|
||||
.and_then(|s| serde_json::from_str(&s).ok())
|
||||
.unwrap_or_default();
|
||||
std::sync::Mutex::new(map)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_stats(name: &str) -> PersistedStats {
|
||||
stats_map().lock().ok()
|
||||
.and_then(|m| m.get(name).cloned())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn set_stats(name: &str, stats: PersistedStats) {
|
||||
if let Ok(mut map) = stats_map().lock() {
|
||||
map.insert(name.to_string(), stats);
|
||||
if let Ok(json) = serde_json::to_string_pretty(&*map) {
|
||||
let _ = std::fs::write(stats_path(), json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Save agent conversation to JSON log file.
|
||||
/// Used by both mind-run agents and CLI-run agents.
|
||||
pub async fn save_agent_log(name: &str, agent: &std::sync::Arc<Agent>) -> RunStats {
|
||||
|
|
@ -109,13 +161,11 @@ pub struct AutoAgent {
|
|||
pub current_phase: String,
|
||||
pub turn: usize,
|
||||
pub enabled: bool,
|
||||
// Stats tracking
|
||||
pub runs: usize,
|
||||
pub last_stats: Option<RunStats>,
|
||||
pub tool_calls_ewma: f64,
|
||||
pub tool_failures_ewma: f64,
|
||||
pub temperature: f32,
|
||||
pub priority: i32,
|
||||
}
|
||||
|
||||
|
||||
/// Per-run conversation backend — wraps a forked agent.
|
||||
struct Backend(std::sync::Arc<Agent>);
|
||||
|
||||
|
|
@ -178,18 +228,16 @@ impl AutoAgent {
|
|||
name: String,
|
||||
tools: Vec<agent_tools::Tool>,
|
||||
steps: Vec<AutoStep>,
|
||||
_temperature: f32,
|
||||
_priority: i32,
|
||||
temperature: f32,
|
||||
priority: i32,
|
||||
) -> Self {
|
||||
Self {
|
||||
name, tools, steps,
|
||||
current_phase: String::new(),
|
||||
turn: 0,
|
||||
enabled: true,
|
||||
runs: 0,
|
||||
last_stats: None,
|
||||
tool_calls_ewma: 0.0,
|
||||
tool_failures_ewma: 0.0,
|
||||
temperature,
|
||||
priority,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -225,7 +273,8 @@ impl AutoAgent {
|
|||
let mut st = agent.state.lock().await;
|
||||
st.provenance = format!("standalone:{}", self.name);
|
||||
st.tools = self.tools.clone();
|
||||
st.priority = Some(10);
|
||||
st.temperature = self.temperature;
|
||||
st.priority = Some(self.priority);
|
||||
}
|
||||
|
||||
let mut backend = Backend(agent.clone());
|
||||
|
|
@ -265,14 +314,33 @@ impl AutoAgent {
|
|||
}
|
||||
|
||||
/// Update stats after a run completes. Called with the stats from save_agent_log.
|
||||
pub fn update_stats(&mut self, stats: RunStats) {
|
||||
pub fn update_stats(&self, run_stats: RunStats) {
|
||||
const ALPHA: f64 = 0.3;
|
||||
self.runs += 1;
|
||||
self.tool_calls_ewma = ALPHA * (stats.tool_calls as f64)
|
||||
+ (1.0 - ALPHA) * self.tool_calls_ewma;
|
||||
self.tool_failures_ewma = ALPHA * (stats.tool_failures as f64)
|
||||
+ (1.0 - ALPHA) * self.tool_failures_ewma;
|
||||
self.last_stats = Some(stats);
|
||||
let old = get_stats(&self.name);
|
||||
|
||||
// Update per-tool stats
|
||||
let mut by_tool = old.by_tool;
|
||||
for (tool, count) in &run_stats.tool_calls_by_type {
|
||||
let entry = by_tool.entry(tool.clone()).or_default();
|
||||
entry.last = *count;
|
||||
entry.ewma = ALPHA * (*count as f64) + (1.0 - ALPHA) * entry.ewma;
|
||||
entry.total += count;
|
||||
}
|
||||
|
||||
// Update failure stats
|
||||
let failures = ToolStats {
|
||||
last: run_stats.tool_failures,
|
||||
ewma: ALPHA * (run_stats.tool_failures as f64) + (1.0 - ALPHA) * old.failures.ewma,
|
||||
total: old.failures.total + run_stats.tool_failures,
|
||||
};
|
||||
|
||||
let new = PersistedStats {
|
||||
runs: old.runs + 1,
|
||||
last_stats: Some(run_stats),
|
||||
by_tool,
|
||||
failures,
|
||||
};
|
||||
set_stats(&self.name, new);
|
||||
}
|
||||
|
||||
async fn run_with_backend(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue