pub const SCHEDULER_POLICY_VERSION: u32 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SchedulerPolicyConfig {
pub version: u32,
pub critical_path_weight: i64,
pub fanout_weight: i64,
pub age_weight: i64,
pub token_cost_weight: i64,
}
impl Default for SchedulerPolicyConfig {
fn default() -> Self {
Self {
version: SCHEDULER_POLICY_VERSION,
critical_path_weight: 1_000_000,
fanout_weight: 10_000,
age_weight: 1_000,
token_cost_weight: 1,
}
}
}
impl SchedulerPolicyConfig {
pub fn validate(&self) -> Result<(), String> {
if self.version != SCHEDULER_POLICY_VERSION {
return Err(format!(
"scheduler_policy version must be {SCHEDULER_POLICY_VERSION}"
));
}
for (name, weight) in [
("critical_path_weight", self.critical_path_weight),
("fanout_weight", self.fanout_weight),
("age_weight", self.age_weight),
("token_cost_weight", self.token_cost_weight),
] {
if !(0..=1_000_000_000).contains(&weight) {
return Err(format!(
"scheduler_policy {name} must be between 0 and 1000000000"
));
}
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct SchedulerBudget {
pub max_tokens: u32,
pub max_turns: u32,
pub max_total_tokens: u64,
pub max_wall_ms: Option<u64>,
}
impl Default for SchedulerBudget {
fn default() -> Self {
Self {
max_tokens: 128_000,
max_turns: 25,
max_total_tokens: 1_000_000,
max_wall_ms: None,
}
}
}
impl SchedulerBudget {
pub fn should_terminate(
&self,
turns: u32,
total_tokens: u64,
now_ms: Option<u64>,
started_at_ms: Option<u64>,
) -> Option<&'static str> {
if turns >= self.max_turns {
return Some("max_turns");
}
if total_tokens >= self.max_total_tokens {
return Some("token_budget");
}
if let (Some(limit), Some(now), Some(start)) = (self.max_wall_ms, now_ms, started_at_ms) {
if now.saturating_sub(start) >= limit {
return Some("wall_time");
}
}
None
}
}