huskies: merge 650_bug_watchdog_turns_used_and_budget_used_usd_accumulate_across_all_sessions_restart_counts_against_limits_from_prior_runs

This commit is contained in:
dave
2026-04-26 16:24:10 +00:00
parent 148c88bd40
commit 365b907ba4
5 changed files with 542 additions and 175 deletions
+38 -12
View File
@@ -290,10 +290,10 @@ pub(super) fn tool_get_agent_remaining_turns_and_budget(
let max_turns = agent_config.and_then(|a| a.max_turns);
let max_budget_usd = agent_config.and_then(|a| a.max_budget_usd);
// Count turns by reading log files and counting assistant events.
// ── Cumulative counters (all sessions) ─────────────────────────────
let log_files =
crate::agent_log::list_story_log_files(&project_root, story_id, Some(agent_name));
let mut turns_used: u64 = 0;
let mut cumulative_turns: u64 = 0;
for path in &log_files {
if let Ok(entries) = crate::agent_log::read_log(path) {
for entry in &entries {
@@ -301,15 +301,13 @@ pub(super) fn tool_get_agent_remaining_turns_and_budget(
&& let Some(data) = entry.event.get("data")
&& data.get("type").and_then(|v| v.as_str()) == Some("assistant")
{
turns_used += 1;
cumulative_turns += 1;
}
}
}
}
// Compute budget from log-based per-message estimates (works for running
// agents) and completed-session records from token_usage.jsonl.
let log_cost = crate::agents::pool::auto_assign::watchdog::compute_budget_from_logs(
let cumulative_log_cost = crate::agents::pool::auto_assign::watchdog::compute_budget_from_logs(
&project_root,
story_id,
agent_name,
@@ -320,19 +318,43 @@ pub(super) fn tool_get_agent_remaining_turns_and_budget(
.filter(|r| r.story_id == story_id && r.agent_name == agent_name)
.map(|r| r.usage.total_cost_usd)
.sum();
let budget_used_usd: f64 = log_cost.max(record_cost);
let cumulative_budget: f64 = cumulative_log_cost.max(record_cost);
let remaining_turns = max_turns.map(|max| (max as i64) - (turns_used as i64));
let remaining_budget_usd = max_budget_usd.map(|max| max - budget_used_usd);
// ── Per-session counters (current session only — enforcement basis) ──
use crate::agents::pool::auto_assign::watchdog::{
compute_budget_from_single_log, count_turns_in_log, resolve_session_log,
};
let session_log = resolve_session_log(
&project_root,
story_id,
agent_name,
&agent_info.log_session_id,
);
let session_turns: u64 = session_log
.as_ref()
.map(|p| count_turns_in_log(p))
.unwrap_or(0);
let session_budget: f64 = session_log
.as_ref()
.map(|p| compute_budget_from_single_log(p))
.unwrap_or(0.0);
let remaining_turns = max_turns.map(|max| (max as i64) - (session_turns as i64));
let remaining_budget_usd = max_budget_usd.map(|max| max - session_budget);
serde_json::to_string_pretty(&json!({
"story_id": story_id,
"agent_name": agent_name,
"status": agent_info.status.to_string(),
"turns_used": turns_used,
// Per-session values (watchdog enforcement basis):
"turns_used": session_turns,
"budget_used_usd": session_budget,
// Cumulative values (all sessions, useful for cost analysis):
"cumulative_turns_used": cumulative_turns,
"cumulative_budget_used_usd": cumulative_budget,
// Limits and remaining (computed from per-session values):
"max_turns": max_turns,
"remaining_turns": remaining_turns,
"budget_used_usd": budget_used_usd,
"max_budget_usd": max_budget_usd,
"remaining_budget_usd": remaining_budget_usd,
}))
@@ -1038,9 +1060,13 @@ stage = "coder"
assert_eq!(parsed["story_id"], "42_story");
assert_eq!(parsed["agent_name"], "coder-1");
assert_eq!(parsed["status"], "running");
// Per-session values (enforcement basis).
assert!(parsed.get("turns_used").is_some());
assert!(parsed.get("budget_used_usd").is_some());
// max_turns and max_budget_usd may be null if not configured
// Cumulative values (all sessions, for cost analysis).
assert!(parsed.get("cumulative_turns_used").is_some());
assert!(parsed.get("cumulative_budget_used_usd").is_some());
// Limits and remaining.
assert!(parsed.get("max_turns").is_some());
assert!(parsed.get("remaining_turns").is_some());
assert!(parsed.get("max_budget_usd").is_some());