Skip to main content

oris_evolution/
evolver.rs

1//! Evolver automation feedback loop for self-evolution.
2//!
3//! This module provides the core components for automated evolution:
4//! - Signal extraction from execution feedback
5//! - Mutation proposal generation
6//! - Validation before application
7//! - Feedback collection and iteration
8
9use std::sync::{Arc, RwLock};
10use std::time::{SystemTime, UNIX_EPOCH};
11
12use serde::{Deserialize, Serialize};
13
14/// Generate a unique ID
15fn generate_id(prefix: &str) -> String {
16    let now = SystemTime::now()
17        .duration_since(UNIX_EPOCH)
18        .unwrap_or_default();
19    format!("{}-{:x}", prefix, now.as_nanos())
20}
21
22/// A signal extracted from execution feedback
23#[derive(Clone, Debug, Deserialize, Serialize)]
24pub struct EvolutionSignal {
25    pub signal_id: String,
26    pub signal_type: SignalType,
27    pub source_task_id: String,
28    pub confidence: f32,
29    pub description: String,
30    pub metadata: serde_json::Value,
31}
32
33/// Types of evolution signals
34#[derive(Clone, Debug, Deserialize, Serialize)]
35#[serde(tag = "type", rename_all = "snake_case")]
36pub enum SignalType {
37    /// Performance improvement opportunity
38    Performance {
39        metric: String,
40        improvement_potential: f32,
41    },
42    /// Error pattern detected
43    ErrorPattern { error_type: String, frequency: u32 },
44    /// Resource optimization
45    ResourceOptimization {
46        resource_type: String,
47        current_usage: f32,
48    },
49    /// Quality issue
50    QualityIssue { issue_type: String, severity: f32 },
51    /// Success pattern that could be generalized
52    SuccessPattern { pattern: String, repeatability: f32 },
53}
54
55/// A proposed mutation based on signals
56#[derive(Clone, Debug, Deserialize, Serialize)]
57pub struct MutationProposal {
58    pub proposal_id: String,
59    pub signal_ids: Vec<String>,
60    pub gene_id: String,
61    pub description: String,
62    pub estimated_impact: f32,
63    pub risk_level: MutationRiskLevel,
64    pub proposed_changes: serde_json::Value,
65}
66
67/// Risk level for mutations
68#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
69pub enum MutationRiskLevel {
70    Low,
71    Medium,
72    High,
73    Critical,
74}
75
76/// Status of a mutation proposal
77#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
78pub enum ProposalStatus {
79    Proposed,
80    Validating,
81    Validated,
82    Rejected { reason: String },
83    Approved,
84    Applied,
85    Failed { error: String },
86}
87
88/// Result of validation
89#[derive(Clone, Debug, Deserialize, Serialize)]
90pub struct ValidationResult {
91    pub proposal_id: String,
92    pub passed: bool,
93    pub score: f32,
94    pub issues: Vec<ValidationIssue>,
95    pub simulation_results: Option<serde_json::Value>,
96}
97
98/// A validation issue
99#[derive(Clone, Debug, Deserialize, Serialize)]
100pub struct ValidationIssue {
101    pub severity: IssueSeverity,
102    pub description: String,
103    pub location: Option<String>,
104}
105
106#[derive(Clone, Debug, Deserialize, Serialize)]
107pub enum IssueSeverity {
108    Info,
109    Warning,
110    Error,
111}
112
113/// Configuration for the evolver automation
114#[derive(Clone, Debug, Deserialize, Serialize)]
115pub struct EvolverConfig {
116    /// Enable automated evolution
117    #[serde(default = "default_enabled")]
118    pub enabled: bool,
119    /// Minimum signal confidence to trigger proposal
120    #[serde(default = "default_min_confidence")]
121    pub min_signal_confidence: f32,
122    /// Maximum proposals per cycle
123    #[serde(default = "default_max_proposals")]
124    pub max_proposals_per_cycle: usize,
125    /// Whether to require governor approval for high-risk mutations
126    #[serde(default = "default_governor_required")]
127    pub governor_required_for_high_risk: bool,
128    /// Number of validation iterations
129    #[serde(default = "default_validation_iterations")]
130    pub validation_iterations: u32,
131    /// Confidence threshold for auto-approval
132    #[serde(default = "default_auto_approve_threshold")]
133    pub auto_approve_threshold: f32,
134}
135
136fn default_enabled() -> bool {
137    false
138}
139fn default_min_confidence() -> f32 {
140    0.7
141}
142fn default_max_proposals() -> usize {
143    10
144}
145fn default_governor_required() -> bool {
146    true
147}
148fn default_validation_iterations() -> u32 {
149    3
150}
151fn default_auto_approve_threshold() -> f32 {
152    0.9
153}
154
155/// The evolver automation engine
156#[derive(Clone)]
157pub struct EvolverAutomation {
158    config: EvolverConfig,
159    signals: Arc<RwLock<Vec<EvolutionSignal>>>,
160    proposals: Arc<RwLock<Vec<MutationProposal>>>,
161    local_peer_id: String,
162}
163
164impl EvolverAutomation {
165    /// Create a new evolver automation instance
166    pub fn new(config: EvolverConfig, local_peer_id: String) -> Self {
167        Self {
168            config,
169            signals: Arc::new(RwLock::new(Vec::new())),
170            proposals: Arc::new(RwLock::new(Vec::new())),
171            local_peer_id,
172        }
173    }
174
175    /// Add a signal extracted from feedback
176    pub fn add_signal(&self, signal: EvolutionSignal) {
177        if signal.confidence >= self.config.min_signal_confidence {
178            let mut signals = self.signals.write().unwrap();
179            signals.push(signal);
180        }
181    }
182
183    /// Get all signals
184    pub fn get_signals(&self) -> Vec<EvolutionSignal> {
185        self.signals.read().unwrap().clone()
186    }
187
188    /// Clear processed signals
189    pub fn clear_signals(&self, signal_ids: &[String]) {
190        let mut signals = self.signals.write().unwrap();
191        signals.retain(|s| !signal_ids.contains(&s.signal_id));
192    }
193
194    /// Generate mutation proposals from signals
195    pub fn generate_proposals(&self) -> Vec<MutationProposal> {
196        let signals = self.signals.read().unwrap();
197        let mut proposals = Vec::new();
198
199        // Group signals by gene/target
200        let mut by_gene: std::collections::HashMap<String, Vec<&EvolutionSignal>> =
201            std::collections::HashMap::new();
202        for signal in signals.iter() {
203            if let Some(gene_id) = signal.metadata.get("gene_id").and_then(|v| v.as_str()) {
204                by_gene.entry(gene_id.to_string()).or_default().push(signal);
205            }
206        }
207
208        // Generate proposals
209        let max = self.config.max_proposals_per_cycle;
210        for (gene_id, gene_signals) in by_gene.iter().take(max) {
211            let avg_confidence: f32 =
212                gene_signals.iter().map(|s| s.confidence).sum::<f32>() / gene_signals.len() as f32;
213            let risk_level = if avg_confidence > self.config.auto_approve_threshold {
214                MutationRiskLevel::Low
215            } else if avg_confidence > 0.5 {
216                MutationRiskLevel::Medium
217            } else {
218                MutationRiskLevel::High
219            };
220
221            let proposal = MutationProposal {
222                proposal_id: generate_id("proposal"),
223                signal_ids: gene_signals.iter().map(|s| s.signal_id.clone()).collect(),
224                gene_id: gene_id.clone(),
225                description: format!(
226                    "{} mutation proposal based on {} signals",
227                    gene_id,
228                    gene_signals.len()
229                ),
230                estimated_impact: avg_confidence,
231                risk_level,
232                proposed_changes: serde_json::json!({
233                    "signals": gene_signals.iter().map(|s| s.signal_type.clone()).collect::<Vec<_>>()
234                }),
235            };
236            proposals.push(proposal);
237        }
238
239        // Store proposals
240        let mut stored = self.proposals.write().unwrap();
241        stored.extend(proposals.clone());
242
243        proposals
244    }
245
246    /// Validate a proposal (simplified - real impl would use sandbox)
247    pub fn validate_proposal(&self, proposal_id: &str) -> ValidationResult {
248        let proposals = self.proposals.read().unwrap();
249        let proposal = proposals.iter().find(|p| p.proposal_id == proposal_id);
250
251        if let Some(p) = proposal {
252            // Simplified validation
253            let passed = p.risk_level != MutationRiskLevel::Critical;
254            let score = if passed { p.estimated_impact } else { 0.0 };
255
256            ValidationResult {
257                proposal_id: proposal_id.to_string(),
258                passed,
259                score,
260                issues: if !passed {
261                    vec![ValidationIssue {
262                        severity: IssueSeverity::Error,
263                        description: "Critical risk mutations require governor approval"
264                            .to_string(),
265                        location: None,
266                    }]
267                } else {
268                    vec![]
269                },
270                simulation_results: Some(serde_json::json!({
271                    "estimated_improvement": p.estimated_impact,
272                    "risk_level": p.risk_level
273                })),
274            }
275        } else {
276            ValidationResult {
277                proposal_id: proposal_id.to_string(),
278                passed: false,
279                score: 0.0,
280                issues: vec![ValidationIssue {
281                    severity: IssueSeverity::Error,
282                    description: "Proposal not found".to_string(),
283                    location: None,
284                }],
285                simulation_results: None,
286            }
287        }
288    }
289
290    /// Approve a proposal
291    pub fn approve_proposal(&self, proposal_id: &str) -> bool {
292        let mut proposals = self.proposals.write().unwrap();
293        if let Some(p) = proposals.iter_mut().find(|p| p.proposal_id == proposal_id) {
294            // Check if governor is required
295            if self.config.governor_required_for_high_risk
296                && p.risk_level == MutationRiskLevel::Critical
297            {
298                return false;
299            }
300            return true;
301        }
302        false
303    }
304
305    /// Get proposals by status
306    pub fn get_proposals(&self, _status: Option<ProposalStatus>) -> Vec<MutationProposal> {
307        let proposals = self.proposals.read().unwrap();
308        proposals.clone()
309    }
310
311    /// Get config
312    pub fn config(&self) -> &EvolverConfig {
313        &self.config
314    }
315}
316
317/// Builder for creating signals
318pub struct SignalBuilder {
319    signal_type: Option<SignalType>,
320    source_task_id: String,
321    confidence: f32,
322    description: String,
323    metadata: serde_json::Value,
324}
325
326impl SignalBuilder {
327    pub fn new(source_task_id: String) -> Self {
328        Self {
329            signal_type: None,
330            source_task_id,
331            confidence: 0.0,
332            description: String::new(),
333            metadata: serde_json::json!({}),
334        }
335    }
336
337    pub fn signal_type(mut self, signal_type: SignalType) -> Self {
338        self.signal_type = Some(signal_type);
339        self
340    }
341
342    pub fn confidence(mut self, confidence: f32) -> Self {
343        self.confidence = confidence;
344        self
345    }
346
347    pub fn description(mut self, description: String) -> Self {
348        self.description = description;
349        self
350    }
351
352    pub fn metadata(mut self, metadata: serde_json::Value) -> Self {
353        self.metadata = metadata;
354        self
355    }
356
357    pub fn build(self) -> Option<EvolutionSignal> {
358        let signal_type = self.signal_type?;
359
360        Some(EvolutionSignal {
361            signal_id: generate_id("signal"),
362            signal_type,
363            source_task_id: self.source_task_id,
364            confidence: self.confidence,
365            description: self.description,
366            metadata: self.metadata,
367        })
368    }
369}
370
371impl Default for EvolverConfig {
372    fn default() -> Self {
373        Self {
374            enabled: false,
375            min_signal_confidence: default_min_confidence(),
376            max_proposals_per_cycle: default_max_proposals(),
377            governor_required_for_high_risk: default_governor_required(),
378            validation_iterations: default_validation_iterations(),
379            auto_approve_threshold: default_auto_approve_threshold(),
380        }
381    }
382}
383
384#[cfg(test)]
385mod tests {
386    use super::*;
387
388    #[test]
389    fn test_signal_builder() {
390        let signal = SignalBuilder::new("task-123".to_string())
391            .signal_type(SignalType::Performance {
392                metric: "latency".to_string(),
393                improvement_potential: 0.5,
394            })
395            .confidence(0.8)
396            .description("High latency detected".to_string())
397            .build();
398
399        assert!(signal.is_some());
400        let s = signal.unwrap();
401        assert_eq!(s.source_task_id, "task-123");
402        assert!(s.confidence >= 0.7); // Above min threshold
403    }
404
405    #[test]
406    fn test_proposal_generation() {
407        let config = EvolverConfig {
408            enabled: true,
409            min_signal_confidence: 0.5,
410            ..Default::default()
411        };
412        let evolver = EvolverAutomation::new(config, "local".to_string());
413
414        // Add a signal
415        let signal = SignalBuilder::new("task-1".to_string())
416            .signal_type(SignalType::Performance {
417                metric: "throughput".to_string(),
418                improvement_potential: 0.8,
419            })
420            .confidence(0.9)
421            .metadata(serde_json::json!({ "gene_id": "gene-1" }))
422            .build()
423            .unwrap();
424
425        evolver.add_signal(signal);
426
427        let proposals = evolver.generate_proposals();
428        assert!(!proposals.is_empty());
429    }
430
431    #[test]
432    fn test_validation() {
433        let config = EvolverConfig::default();
434        let evolver = EvolverAutomation::new(config, "local".to_string());
435
436        let result = evolver.validate_proposal("non-existent");
437        assert!(!result.passed);
438    }
439}