use std::sync::OnceLock;
#[derive(Debug, Clone)]
pub struct EvolutionConfig {
pub enabled: bool,
pub cluster: ClusterConfig,
pub promotion: PromotionConfig,
pub meditation: MeditationParams,
pub workflow: WorkflowConfig,
}
#[derive(Debug, Clone)]
pub struct WorkflowConfig {
pub enabled: bool,
pub score_threshold: f32,
pub min_tool_calls: usize,
pub min_errors: usize,
pub max_per_hour: usize,
}
#[derive(Debug, Clone)]
pub struct ClusterConfig {
pub min_size: usize,
pub similarity_threshold: f32,
}
#[derive(Debug, Clone)]
pub struct PromotionConfig {
pub access_only: i64,
pub importance_only: f32,
pub both_access: i64,
pub both_importance: f32,
}
#[derive(Debug, Clone)]
pub struct MeditationParams {
pub max_per_cycle: usize,
pub dedup_threshold: f32,
pub crystallized_ttl_days: u32,
}
impl Default for EvolutionConfig {
fn default() -> Self {
Self {
enabled: true,
cluster: ClusterConfig {
min_size: 3,
similarity_threshold: 0.75,
},
promotion: PromotionConfig {
access_only: 15,
importance_only: 0.9,
both_access: 5,
both_importance: 0.8,
},
meditation: MeditationParams {
max_per_cycle: 5,
dedup_threshold: 0.92,
crystallized_ttl_days: 7,
},
workflow: WorkflowConfig {
enabled: false,
score_threshold: 0.7,
min_tool_calls: 5,
min_errors: 1,
max_per_hour: 3,
},
}
}
}
impl EvolutionConfig {
pub fn test_preset() -> Self {
Self {
enabled: true,
cluster: ClusterConfig {
min_size: 2,
similarity_threshold: 0.5,
},
promotion: PromotionConfig {
access_only: 3,
importance_only: 0.6,
both_access: 2,
both_importance: 0.5,
},
meditation: MeditationParams {
max_per_cycle: 1,
dedup_threshold: 0.92,
crystallized_ttl_days: 7,
},
workflow: WorkflowConfig {
enabled: true,
score_threshold: 0.4,
min_tool_calls: 2,
min_errors: 0,
max_per_hour: 5,
},
}
}
pub fn from_raw(raw: Option<&rsclaw_config::schema::EvolutionConfig>) -> Self {
let raw = match raw {
Some(r) => r,
None => return Self::default(),
};
let mut cfg = match raw.preset.as_deref() {
Some("test") => Self::test_preset(),
_ => Self::default(),
};
if let Some(v) = raw.enabled {
cfg.enabled = v;
}
if let Some(c) = &raw.cluster {
if let Some(v) = c.min_size {
cfg.cluster.min_size = v;
}
if let Some(v) = c.similarity_threshold {
cfg.cluster.similarity_threshold = v;
}
}
if let Some(p) = &raw.promotion {
if let Some(v) = p.access_only {
cfg.promotion.access_only = v;
}
if let Some(v) = p.importance_only {
cfg.promotion.importance_only = v;
}
if let Some(v) = p.both_access {
cfg.promotion.both_access = v;
}
if let Some(v) = p.both_importance {
cfg.promotion.both_importance = v;
}
}
if let Some(m) = &raw.meditation {
if let Some(v) = m.max_per_cycle {
cfg.meditation.max_per_cycle = v;
}
if let Some(v) = m.dedup_threshold {
cfg.meditation.dedup_threshold = v;
}
if let Some(v) = m.crystallized_ttl_days {
cfg.meditation.crystallized_ttl_days = v;
}
}
if let Some(w) = &raw.workflow {
if let Some(v) = w.enabled {
cfg.workflow.enabled = v;
}
if let Some(v) = w.score_threshold {
cfg.workflow.score_threshold = v;
}
if let Some(v) = w.min_tool_calls {
cfg.workflow.min_tool_calls = v;
}
if let Some(v) = w.min_errors {
cfg.workflow.min_errors = v;
}
if let Some(v) = w.max_per_hour {
cfg.workflow.max_per_hour = v;
}
}
cfg
}
}
static EVO: OnceLock<EvolutionConfig> = OnceLock::new();
pub fn init_evolution_config(cfg: EvolutionConfig) {
let _ = EVO.set(cfg);
}
pub fn evolution_config() -> &'static EvolutionConfig {
EVO.get_or_init(EvolutionConfig::default)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_match_pre_config_constants() {
let cfg = EvolutionConfig::default();
assert!(cfg.enabled);
assert_eq!(cfg.cluster.min_size, 3);
assert!((cfg.cluster.similarity_threshold - 0.75).abs() < 0.001);
assert_eq!(cfg.promotion.access_only, 15);
assert!((cfg.promotion.importance_only - 0.9).abs() < 0.001);
assert_eq!(cfg.promotion.both_access, 5);
assert!((cfg.promotion.both_importance - 0.8).abs() < 0.001);
assert_eq!(cfg.meditation.max_per_cycle, 5);
assert!((cfg.meditation.dedup_threshold - 0.92).abs() < 0.001);
assert_eq!(cfg.meditation.crystallized_ttl_days, 7);
}
#[test]
fn test_preset_is_looser() {
let d = EvolutionConfig::default();
let t = EvolutionConfig::test_preset();
assert!(t.cluster.min_size < d.cluster.min_size);
assert!(t.cluster.similarity_threshold < d.cluster.similarity_threshold);
assert!(t.promotion.access_only < d.promotion.access_only);
assert!(t.promotion.importance_only < d.promotion.importance_only);
}
#[test]
fn from_raw_none_returns_default() {
let cfg = EvolutionConfig::from_raw(None);
assert_eq!(cfg.cluster.min_size, 3);
}
#[test]
fn from_raw_preset_then_override() {
let raw = rsclaw_config::schema::EvolutionConfig {
enabled: None,
preset: Some("test".to_owned()),
cluster: Some(rsclaw_config::schema::EvolutionClusterConfig {
min_size: Some(7), similarity_threshold: None, }),
promotion: None,
meditation: None,
workflow: None,
};
let cfg = EvolutionConfig::from_raw(Some(&raw));
assert_eq!(cfg.cluster.min_size, 7); assert!((cfg.cluster.similarity_threshold - 0.5).abs() < 0.001); assert_eq!(cfg.promotion.access_only, 3); }
}