scoring: configurable agent_budget, squared Elo distribution
agent_budget config (default 1000) replaces health-metric-computed totals. The budget is the total agent runs per cycle — use it all. Elo distribution is squared for power-law unfairness: top-rated agents get disproportionately more runs. If linker has Elo 1123 and connector has 876, linker gets ~7x more runs (squared ratio) vs ~3.5x (linear). Minimum 2 runs per type so underperformers still get evaluated. No Elo file → equal distribution as fallback. Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
parent
e9791991a7
commit
46b4f6f434
2 changed files with 50 additions and 33 deletions
|
|
@ -51,6 +51,8 @@ pub struct Config {
|
||||||
pub context_groups: Vec<ContextGroup>,
|
pub context_groups: Vec<ContextGroup>,
|
||||||
/// Max concurrent LLM calls in the daemon.
|
/// Max concurrent LLM calls in the daemon.
|
||||||
pub llm_concurrency: usize,
|
pub llm_concurrency: usize,
|
||||||
|
/// Total agent runs per consolidation cycle.
|
||||||
|
pub agent_budget: usize,
|
||||||
/// Directory containing prompt templates for agents.
|
/// Directory containing prompt templates for agents.
|
||||||
pub prompts_dir: PathBuf,
|
pub prompts_dir: PathBuf,
|
||||||
/// Separate Claude config dir for background agent work (daemon jobs).
|
/// Separate Claude config dir for background agent work (daemon jobs).
|
||||||
|
|
@ -83,6 +85,7 @@ impl Default for Config {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
llm_concurrency: 1,
|
llm_concurrency: 1,
|
||||||
|
agent_budget: 1000,
|
||||||
prompts_dir: home.join("poc/memory/prompts"),
|
prompts_dir: home.join("poc/memory/prompts"),
|
||||||
agent_config_dir: None,
|
agent_config_dir: None,
|
||||||
}
|
}
|
||||||
|
|
@ -141,6 +144,9 @@ impl Config {
|
||||||
if let Some(n) = cfg.get("llm_concurrency").and_then(|v| v.as_u64()) {
|
if let Some(n) = cfg.get("llm_concurrency").and_then(|v| v.as_u64()) {
|
||||||
config.llm_concurrency = n.max(1) as usize;
|
config.llm_concurrency = n.max(1) as usize;
|
||||||
}
|
}
|
||||||
|
if let Some(n) = cfg.get("agent_budget").and_then(|v| v.as_u64()) {
|
||||||
|
config.agent_budget = n as usize;
|
||||||
|
}
|
||||||
if let Some(s) = cfg.get("prompts_dir").and_then(|v| v.as_str()) {
|
if let Some(s) = cfg.get("prompts_dir").and_then(|v| v.as_str()) {
|
||||||
config.prompts_dir = expand_home(s);
|
config.prompts_dir = expand_home(s);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -358,16 +358,11 @@ fn consolidation_plan_inner(store: &Store, detect_interf: bool) -> Consolidation
|
||||||
nodes_per_community));
|
nodes_per_community));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rebalance using Elo ratings if available
|
// Distribute agent budget using Elo ratings
|
||||||
|
let budget = crate::config::get().agent_budget;
|
||||||
let elo_path = crate::config::get().data_dir.join("agent-elo.json");
|
let elo_path = crate::config::get().data_dir.join("agent-elo.json");
|
||||||
if let Ok(elo_json) = std::fs::read_to_string(&elo_path) {
|
if let Ok(elo_json) = std::fs::read_to_string(&elo_path) {
|
||||||
if let Ok(ratings) = serde_json::from_str::<std::collections::HashMap<String, f64>>(&elo_json) {
|
if let Ok(ratings) = serde_json::from_str::<std::collections::HashMap<String, f64>>(&elo_json) {
|
||||||
let total_budget = plan.replay_count + plan.linker_count
|
|
||||||
+ plan.separator_count + plan.transfer_count
|
|
||||||
+ plan.organize_count + plan.connector_count;
|
|
||||||
|
|
||||||
if total_budget > 0 {
|
|
||||||
// Convert Elo to weights: subtract min, add 1 to avoid zero
|
|
||||||
let types = [
|
let types = [
|
||||||
"replay", "linker", "separator", "transfer",
|
"replay", "linker", "separator", "transfer",
|
||||||
"organize", "connector",
|
"organize", "connector",
|
||||||
|
|
@ -376,13 +371,19 @@ fn consolidation_plan_inner(store: &Store, detect_interf: bool) -> Consolidation
|
||||||
.map(|t| ratings.get(*t).copied().unwrap_or(1000.0))
|
.map(|t| ratings.get(*t).copied().unwrap_or(1000.0))
|
||||||
.collect();
|
.collect();
|
||||||
let min_elo = elos.iter().copied().fold(f64::MAX, f64::min);
|
let min_elo = elos.iter().copied().fold(f64::MAX, f64::min);
|
||||||
|
|
||||||
|
// Square the shifted ratings for unfair distribution —
|
||||||
|
// top agents get disproportionately more runs
|
||||||
let weights: Vec<f64> = elos.iter()
|
let weights: Vec<f64> = elos.iter()
|
||||||
.map(|e| (e - min_elo + 100.0)) // shift so lowest gets 100
|
.map(|e| {
|
||||||
|
let shifted = e - min_elo + 50.0; // lowest gets 50
|
||||||
|
shifted * shifted // square for power-law distribution
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let total_weight: f64 = weights.iter().sum();
|
let total_weight: f64 = weights.iter().sum();
|
||||||
|
|
||||||
let allocate = |w: f64| -> usize {
|
let allocate = |w: f64| -> usize {
|
||||||
((w / total_weight * total_budget as f64).round() as usize).max(1)
|
((w / total_weight * budget as f64).round() as usize).max(2)
|
||||||
};
|
};
|
||||||
|
|
||||||
plan.replay_count = allocate(weights[0]);
|
plan.replay_count = allocate(weights[0]);
|
||||||
|
|
@ -393,12 +394,22 @@ fn consolidation_plan_inner(store: &Store, detect_interf: bool) -> Consolidation
|
||||||
plan.connector_count = allocate(weights[5]);
|
plan.connector_count = allocate(weights[5]);
|
||||||
|
|
||||||
plan.rationale.push(format!(
|
plan.rationale.push(format!(
|
||||||
"Elo rebalance (budget={}): replay={} linker={} separator={} transfer={} organize={} connector={}",
|
"Elo allocation (budget={}): replay={} linker={} separator={} transfer={} organize={} connector={}",
|
||||||
total_budget,
|
budget,
|
||||||
plan.replay_count, plan.linker_count, plan.separator_count,
|
plan.replay_count, plan.linker_count, plan.separator_count,
|
||||||
plan.transfer_count, plan.organize_count, plan.connector_count));
|
plan.transfer_count, plan.organize_count, plan.connector_count));
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
// No Elo file — use budget with equal distribution
|
||||||
|
let per_type = budget / 6;
|
||||||
|
plan.replay_count = per_type;
|
||||||
|
plan.linker_count = per_type;
|
||||||
|
plan.separator_count = per_type;
|
||||||
|
plan.transfer_count = per_type;
|
||||||
|
plan.organize_count = per_type;
|
||||||
|
plan.connector_count = per_type;
|
||||||
|
plan.rationale.push(format!(
|
||||||
|
"No Elo ratings — equal distribution ({} each, budget={})", per_type, budget));
|
||||||
}
|
}
|
||||||
|
|
||||||
plan
|
plan
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue