use super::config::SafetyLevel;
use super::{KaneruConfig, Outcome};
pub struct SelfModifier {
rules: Vec<BehaviorRule>,
history: Vec<RuleChange>,
safety_bounds: SafetyBounds,
max_rules: usize,
learning_rate: f32,
safety_violations: usize,
}
impl SelfModifier {
pub fn new(config: &KaneruConfig) -> Self {
Self {
rules: Vec::new(),
history: Vec::new(),
safety_bounds: SafetyBounds::new(config.safety_level),
max_rules: config.max_rules,
learning_rate: config.learning_rate,
safety_violations: 0,
}
}
pub fn evolve(&mut self, outcome: &Outcome) -> bool {
let rule_scores = self.evaluate_rules(outcome);
let proposals = self.generate_modifications(&rule_scores, outcome);
let safe_proposals: Vec<_> = proposals
.into_iter()
.filter(|p| {
let is_safe = self.safety_bounds.is_safe(p);
if !is_safe {
self.safety_violations += 1;
}
is_safe
})
.collect();
if safe_proposals.is_empty() {
return false;
}
if let Some(best) = self.select_best(&safe_proposals) {
self.apply_modification(&best);
self.history.push(RuleChange {
modification: best,
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64,
outcome_reward: outcome.reward,
});
return true;
}
false
}
pub fn get_rules(&self) -> Vec<BehaviorRule> {
self.rules.clone()
}
pub fn rule_count(&self) -> usize {
self.rules.len()
}
pub fn safety_violation_count(&self) -> usize {
self.safety_violations
}
fn evaluate_rules(&self, outcome: &Outcome) -> Vec<f32> {
self.rules
.iter()
.map(|rule| {
if rule.active {
outcome.reward * rule.weight
} else {
0.0
}
})
.collect()
}
fn generate_modifications(&self, scores: &[f32], outcome: &Outcome) -> Vec<Modification> {
let mut proposals = Vec::new();
for (i, &score) in scores.iter().enumerate() {
if score.abs() > 0.1 {
proposals.push(Modification::AdjustWeight {
rule_idx: i,
delta: score * self.learning_rate,
});
}
}
if outcome.success && outcome.reward > 0.5 && self.rules.len() < self.max_rules {
proposals.push(Modification::AddRule {
rule: BehaviorRule {
name: format!("learned_rule_{}", self.rules.len()),
condition: RuleCondition::RewardThreshold(0.5),
action: RuleAction::IncreaseValidationPriority,
weight: 0.5,
active: true,
},
});
}
for (i, &score) in scores.iter().enumerate() {
if score < -0.5 && !self.rules.is_empty() {
proposals.push(Modification::RemoveRule { rule_idx: i });
}
}
proposals
}
fn select_best(&self, proposals: &[Modification]) -> Option<Modification> {
proposals
.iter()
.max_by(|a, b| {
let score_a = match a {
Modification::AdjustWeight { delta, .. } => delta.abs(),
Modification::AddRule { .. } => 0.1,
Modification::RemoveRule { .. } => 0.05,
};
let score_b = match b {
Modification::AdjustWeight { delta, .. } => delta.abs(),
Modification::AddRule { .. } => 0.1,
Modification::RemoveRule { .. } => 0.05,
};
score_a.partial_cmp(&score_b).unwrap()
})
.cloned()
}
fn apply_modification(&mut self, modification: &Modification) {
match modification {
Modification::AdjustWeight { rule_idx, delta } => {
if let Some(rule) = self.rules.get_mut(*rule_idx) {
rule.weight = (rule.weight + delta).clamp(-1.0, 1.0);
}
}
Modification::AddRule { rule } => {
if self.rules.len() < self.max_rules {
self.rules.push(rule.clone());
}
}
Modification::RemoveRule { rule_idx } => {
if *rule_idx < self.rules.len() {
self.rules.remove(*rule_idx);
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct BehaviorRule {
pub name: String,
pub condition: RuleCondition,
pub action: RuleAction,
pub weight: f32,
pub active: bool,
}
#[derive(Debug, Clone)]
pub enum RuleCondition {
RewardThreshold(f32),
ErrorThreshold(f32),
Always,
}
#[derive(Debug, Clone)]
pub enum RuleAction {
IncreaseValidationPriority,
DecreaseValidationPriority,
AdjustGossipFrequency(f32),
LogWarning,
}
#[derive(Debug, Clone)]
pub enum Modification {
AdjustWeight { rule_idx: usize, delta: f32 },
AddRule { rule: BehaviorRule },
RemoveRule { rule_idx: usize },
}
impl Modification {
pub fn touches_critical_path(&self) -> bool {
match self {
Modification::AddRule { rule } => {
matches!(rule.action, RuleAction::AdjustGossipFrequency(_))
}
_ => false,
}
}
}
#[derive(Debug, Clone)]
pub struct SafetyBounds {
level: SafetyLevel,
blocked_actions: Vec<String>,
}
impl SafetyBounds {
pub fn new(level: SafetyLevel) -> Self {
let blocked_actions = match level {
SafetyLevel::Strict => vec![
"crypto".to_string(),
"consensus".to_string(),
"identity".to_string(),
"gossip".to_string(),
],
SafetyLevel::Moderate => vec![
"crypto".to_string(),
"consensus".to_string(),
"identity".to_string(),
],
SafetyLevel::Permissive => vec!["crypto".to_string(), "identity".to_string()],
};
Self {
level,
blocked_actions,
}
}
pub fn is_safe(&self, modification: &Modification) -> bool {
if modification.touches_critical_path() && self.level == SafetyLevel::Strict {
return false;
}
if let Modification::AddRule { rule } = modification {
let action_name = format!("{:?}", rule.action).to_lowercase();
if self.blocked_actions.iter().any(|b| action_name.contains(b)) {
return false;
}
}
true
}
}
#[allow(dead_code)]
struct RuleChange {
modification: Modification,
timestamp: u64,
outcome_reward: f32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_self_modifier_basic() {
let config = KaneruConfig::default();
let mut modifier = SelfModifier::new(&config);
let outcome = Outcome {
success: true,
reward: 0.8,
};
let modified = modifier.evolve(&outcome);
assert!(modifier.safety_violation_count() == 0 || !modified);
}
#[test]
fn test_safety_bounds() {
let bounds = SafetyBounds::new(SafetyLevel::Strict);
let safe_mod = Modification::AdjustWeight {
rule_idx: 0,
delta: 0.1,
};
assert!(bounds.is_safe(&safe_mod));
let unsafe_mod = Modification::AddRule {
rule: BehaviorRule {
name: "test".to_string(),
condition: RuleCondition::Always,
action: RuleAction::AdjustGossipFrequency(2.0),
weight: 1.0,
active: true,
},
};
assert!(!bounds.is_safe(&unsafe_mod));
}
#[test]
fn test_rule_evolution() {
let mut config = KaneruConfig::default();
config.max_rules = 10;
let mut modifier = SelfModifier::new(&config);
for _ in 0..5 {
let outcome = Outcome {
success: true,
reward: 0.9,
};
modifier.evolve(&outcome);
}
assert!(modifier.rule_count() > 0);
}
}