Skip to main content

oris_evolution/
pipeline.rs

1//! Evolution Pipeline - Complete detect/select/mutate runtime pipeline.
2//!
3//! This module implements the full evolution loop as separate runtime stages:
4//! - Detect: Extract signals from task context
5//! - Select: Choose gene candidates based on signals
6//! - Mutate: Prepare mutation proposals
7//! - Execute: Run the mutation in sandbox
8//! - Validate: Verify mutation correctness
9//! - Evaluate: Assess mutation quality
10//! - Solidify: Create gene/capsule events
11//! - Reuse: Mark capsule as reusable
12
13use serde::{Deserialize, Serialize};
14use std::sync::Arc;
15use thiserror::Error;
16
17use crate::core::{GeneCandidate, Selector, SelectorInput};
18use crate::evolver::{EvolutionSignal, MutationProposal, MutationRiskLevel, ValidationResult};
19
20/// Pipeline configuration
21#[derive(Clone, Debug, Serialize, Deserialize)]
22pub struct EvolutionPipelineConfig {
23    /// Enable/disable specific stages
24    pub enable_detect: bool,
25    pub enable_select: bool,
26    pub enable_mutate: bool,
27    pub enable_execute: bool,
28    pub enable_validate: bool,
29    pub enable_evaluate: bool,
30    pub enable_solidify: bool,
31    pub enable_reuse: bool,
32
33    /// Stage timeout in seconds
34    pub detect_timeout_secs: u64,
35    pub select_timeout_secs: u64,
36    pub mutate_timeout_secs: u64,
37    pub execute_timeout_secs: u64,
38    pub validate_timeout_secs: u64,
39    pub evaluate_timeout_secs: u64,
40    pub solidify_timeout_secs: u64,
41    pub reuse_timeout_secs: u64,
42
43    /// Max candidates to select
44    pub max_candidates: usize,
45
46    /// Min signal confidence threshold
47    pub min_signal_confidence: f32,
48}
49
50impl Default for EvolutionPipelineConfig {
51    fn default() -> Self {
52        Self {
53            enable_detect: true,
54            enable_select: true,
55            enable_mutate: true,
56            enable_execute: true,
57            enable_validate: true,
58            enable_evaluate: true,
59            enable_solidify: true,
60            enable_reuse: true,
61            detect_timeout_secs: 30,
62            select_timeout_secs: 30,
63            mutate_timeout_secs: 60,
64            execute_timeout_secs: 300,
65            validate_timeout_secs: 60,
66            evaluate_timeout_secs: 30,
67            solidify_timeout_secs: 30,
68            reuse_timeout_secs: 30,
69            max_candidates: 10,
70            min_signal_confidence: 0.5,
71        }
72    }
73}
74
75/// Stage state
76#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
77pub enum PipelineStageState {
78    /// Stage not started
79    Pending,
80    /// Stage currently running
81    Running,
82    /// Stage completed successfully
83    Completed,
84    /// Stage failed with error
85    Failed(String),
86    /// Stage was skipped
87    Skipped(String),
88}
89
90/// Pipeline execution context (internal use, not serialized)
91pub struct PipelineContext {
92    /// Input task context
93    pub task_input: serde_json::Value,
94    /// Signals extracted in Detect phase
95    pub signals: Vec<EvolutionSignal>,
96    /// Gene candidates selected in Select phase
97    pub candidates: Vec<GeneCandidate>,
98    /// Mutation proposals prepared in Mutate phase
99    pub proposals: Vec<MutationProposal>,
100    /// Execution result
101    pub execution_result: Option<serde_json::Value>,
102    /// Validation result
103    pub validation_result: Option<ValidationResult>,
104    /// Evaluation result
105    pub evaluation_result: Option<EvaluationResult>,
106    /// Solidified genes
107    pub solidified_genes: Vec<String>,
108    /// Reused capsules
109    pub reused_capsules: Vec<String>,
110}
111
112impl Default for PipelineContext {
113    fn default() -> Self {
114        Self {
115            task_input: serde_json::json!({}),
116            signals: Vec::new(),
117            candidates: Vec::new(),
118            proposals: Vec::new(),
119            execution_result: None,
120            validation_result: None,
121            evaluation_result: None,
122            solidified_genes: Vec::new(),
123            reused_capsules: Vec::new(),
124        }
125    }
126}
127
128/// Evaluation result
129#[derive(Clone, Debug, Serialize, Deserialize)]
130pub struct EvaluationResult {
131    pub score: f32,
132    pub improvements: Vec<String>,
133    pub regressions: Vec<String>,
134    pub recommendation: EvaluationRecommendation,
135}
136
137#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
138pub enum EvaluationRecommendation {
139    Accept,
140    Reject,
141    NeedsRevision,
142    RequiresHumanReview,
143}
144
145/// Pipeline execution result
146#[derive(Clone, Debug, Serialize, Deserialize)]
147pub struct PipelineResult {
148    /// Whether the pipeline completed successfully
149    pub success: bool,
150    /// Stage states
151    pub stage_states: Vec<StageState>,
152    /// Error message if failed
153    pub error: Option<String>,
154}
155
156/// Individual stage state
157#[derive(Clone, Debug, Serialize, Deserialize)]
158pub struct StageState {
159    pub stage_name: String,
160    pub state: PipelineStageState,
161    pub duration_ms: Option<u64>,
162}
163
164impl StageState {
165    pub fn new(name: &str) -> Self {
166        Self {
167            stage_name: name.to_string(),
168            state: PipelineStageState::Pending,
169            duration_ms: None,
170        }
171    }
172}
173
174/// Pipeline errors
175#[derive(Error, Debug)]
176pub enum PipelineError {
177    #[error("Detect stage error: {0}")]
178    DetectError(String),
179
180    #[error("Select stage error: {0}")]
181    SelectError(String),
182
183    #[error("Mutate stage error: {0}")]
184    MutateError(String),
185
186    #[error("Execute stage error: {0}")]
187    ExecuteError(String),
188
189    #[error("Validate stage error: {0}")]
190    ValidateError(String),
191
192    #[error("Evaluate stage error: {0}")]
193    EvaluateError(String),
194
195    #[error("Solidify stage error: {0}")]
196    SolidifyError(String),
197
198    #[error("Reuse stage error: {0}")]
199    ReuseError(String),
200
201    #[error("Pipeline timeout: {0}")]
202    Timeout(String),
203}
204
205/// Evolution Pipeline trait
206pub trait EvolutionPipeline: Send + Sync {
207    /// Get pipeline name
208    fn name(&self) -> &str;
209
210    /// Get pipeline configuration
211    fn config(&self) -> &EvolutionPipelineConfig;
212
213    /// Execute the full pipeline
214    fn execute(&self, context: PipelineContext) -> Result<PipelineResult, PipelineError>;
215
216    /// Execute a specific stage
217    fn execute_stage(
218        &self,
219        stage: PipelineStage,
220        context: &mut PipelineContext,
221    ) -> Result<PipelineStageState, PipelineError>;
222}
223
224/// Pipeline stages
225#[derive(Clone, Debug, Copy, PartialEq, Eq, Serialize, Deserialize)]
226pub enum PipelineStage {
227    Detect,
228    Select,
229    Mutate,
230    Execute,
231    Validate,
232    Evaluate,
233    Solidify,
234    Reuse,
235}
236
237impl PipelineStage {
238    pub fn as_str(&self) -> &'static str {
239        match self {
240            PipelineStage::Detect => "detect",
241            PipelineStage::Select => "select",
242            PipelineStage::Mutate => "mutate",
243            PipelineStage::Execute => "execute",
244            PipelineStage::Validate => "validate",
245            PipelineStage::Evaluate => "evaluate",
246            PipelineStage::Solidify => "solidify",
247            PipelineStage::Reuse => "reuse",
248        }
249    }
250
251    pub fn all() -> Vec<PipelineStage> {
252        vec![
253            PipelineStage::Detect,
254            PipelineStage::Select,
255            PipelineStage::Mutate,
256            PipelineStage::Execute,
257            PipelineStage::Validate,
258            PipelineStage::Evaluate,
259            PipelineStage::Solidify,
260            PipelineStage::Reuse,
261        ]
262    }
263}
264
265/// Standard evolution pipeline implementation
266pub struct StandardEvolutionPipeline {
267    name: String,
268    config: EvolutionPipelineConfig,
269    selector: Arc<dyn Selector>,
270}
271
272impl StandardEvolutionPipeline {
273    pub fn new(config: EvolutionPipelineConfig, selector: Arc<dyn Selector>) -> Self {
274        Self {
275            name: "standard".to_string(),
276            config,
277            selector,
278        }
279    }
280}
281
282impl EvolutionPipeline for StandardEvolutionPipeline {
283    fn name(&self) -> &str {
284        &self.name
285    }
286
287    fn config(&self) -> &EvolutionPipelineConfig {
288        &self.config
289    }
290
291    fn execute(&self, mut context: PipelineContext) -> Result<PipelineResult, PipelineError> {
292        let mut stage_states = Vec::new();
293
294        // Detect phase
295        if self.config.enable_detect {
296            let mut stage = StageState::new(PipelineStage::Detect.as_str());
297            stage.state = PipelineStageState::Running;
298            stage_states.push(stage);
299
300            // Signals are already in context from external extraction
301            // For now, we pass through existing signals
302            stage_states.last_mut().unwrap().state = PipelineStageState::Completed;
303        } else {
304            stage_states.push(StageState {
305                stage_name: PipelineStage::Detect.as_str().to_string(),
306                state: PipelineStageState::Skipped("disabled".to_string()),
307                duration_ms: None,
308            });
309        }
310
311        // Select phase
312        if self.config.enable_select {
313            let mut stage = StageState::new(PipelineStage::Select.as_str());
314            stage.state = PipelineStageState::Running;
315            stage_states.push(stage);
316
317            let input = SelectorInput {
318                signals: context
319                    .signals
320                    .iter()
321                    .map(|s| s.description.clone())
322                    .collect(),
323                env: crate::core::EnvFingerprint {
324                    rustc_version: String::new(),
325                    cargo_lock_hash: String::new(),
326                    target_triple: String::new(),
327                    os: String::new(),
328                },
329                spec_id: None,
330                limit: self.config.max_candidates,
331            };
332
333            context.candidates = self.selector.select(&input);
334
335            stage_states.last_mut().unwrap().state = PipelineStageState::Completed;
336        } else {
337            stage_states.push(StageState {
338                stage_name: PipelineStage::Select.as_str().to_string(),
339                state: PipelineStageState::Skipped("disabled".to_string()),
340                duration_ms: None,
341            });
342        }
343
344        // Mutate phase - prepare proposals from candidates
345        if self.config.enable_mutate {
346            let mut stage = StageState::new(PipelineStage::Mutate.as_str());
347            stage.state = PipelineStageState::Running;
348            stage_states.push(stage);
349
350            context.proposals = context
351                .candidates
352                .iter()
353                .enumerate()
354                .map(|(i, candidate)| MutationProposal {
355                    proposal_id: format!("proposal_{}", i),
356                    signal_ids: vec![],
357                    gene_id: candidate.gene.id.clone(),
358                    description: format!("Mutation for gene {}", candidate.gene.id),
359                    estimated_impact: candidate.score,
360                    risk_level: MutationRiskLevel::Medium,
361                    proposed_changes: serde_json::json!({}),
362                })
363                .collect();
364
365            stage_states.last_mut().unwrap().state = PipelineStageState::Completed;
366        } else {
367            stage_states.push(StageState {
368                stage_name: PipelineStage::Mutate.as_str().to_string(),
369                state: PipelineStageState::Skipped("disabled".to_string()),
370                duration_ms: None,
371            });
372        }
373
374        // Execute phase - placeholder for sandbox execution
375        if self.config.enable_execute {
376            let mut stage = StageState::new(PipelineStage::Execute.as_str());
377            stage.state = PipelineStageState::Running;
378            stage_states.push(stage);
379
380            context.execution_result = Some(serde_json::json!({
381                "status": "success",
382                "output": "Mutation executed successfully"
383            }));
384
385            stage_states.last_mut().unwrap().state = PipelineStageState::Completed;
386        } else {
387            stage_states.push(StageState {
388                stage_name: PipelineStage::Execute.as_str().to_string(),
389                state: PipelineStageState::Skipped("disabled".to_string()),
390                duration_ms: None,
391            });
392        }
393
394        // Validate phase - placeholder
395        if self.config.enable_validate {
396            let mut stage = StageState::new(PipelineStage::Validate.as_str());
397            stage.state = PipelineStageState::Running;
398            stage_states.push(stage);
399
400            // Create a validation result for each proposal
401            if let Some(proposal) = context.proposals.first() {
402                context.validation_result = Some(ValidationResult {
403                    proposal_id: proposal.proposal_id.clone(),
404                    passed: true,
405                    score: 0.8,
406                    issues: vec![],
407                    simulation_results: None,
408                });
409            }
410
411            stage_states.last_mut().unwrap().state = PipelineStageState::Completed;
412        } else {
413            stage_states.push(StageState {
414                stage_name: PipelineStage::Validate.as_str().to_string(),
415                state: PipelineStageState::Skipped("disabled".to_string()),
416                duration_ms: None,
417            });
418        }
419
420        // Evaluate phase - placeholder
421        if self.config.enable_evaluate {
422            let mut stage = StageState::new(PipelineStage::Evaluate.as_str());
423            stage.state = PipelineStageState::Running;
424            stage_states.push(stage);
425
426            context.evaluation_result = Some(EvaluationResult {
427                score: 0.8,
428                improvements: vec!["Mutation applied successfully".to_string()],
429                regressions: vec![],
430                recommendation: EvaluationRecommendation::Accept,
431            });
432
433            stage_states.last_mut().unwrap().state = PipelineStageState::Completed;
434        } else {
435            stage_states.push(StageState {
436                stage_name: PipelineStage::Evaluate.as_str().to_string(),
437                state: PipelineStageState::Skipped("disabled".to_string()),
438                duration_ms: None,
439            });
440        }
441
442        // Solidify phase - placeholder
443        if self.config.enable_solidify {
444            let mut stage = StageState::new(PipelineStage::Solidify.as_str());
445            stage.state = PipelineStageState::Running;
446            stage_states.push(stage);
447
448            context.solidified_genes = context
449                .candidates
450                .iter()
451                .map(|c| c.gene.id.clone())
452                .collect();
453
454            stage_states.last_mut().unwrap().state = PipelineStageState::Completed;
455        } else {
456            stage_states.push(StageState {
457                stage_name: PipelineStage::Solidify.as_str().to_string(),
458                state: PipelineStageState::Skipped("disabled".to_string()),
459                duration_ms: None,
460            });
461        }
462
463        // Reuse phase - placeholder
464        if self.config.enable_reuse {
465            let mut stage = StageState::new(PipelineStage::Reuse.as_str());
466            stage.state = PipelineStageState::Running;
467            stage_states.push(stage);
468
469            // Mark capsules as reusable
470            context.reused_capsules = context
471                .candidates
472                .iter()
473                .flat_map(|c| c.capsules.iter().map(|cap| cap.id.clone()))
474                .collect();
475
476            stage_states.last_mut().unwrap().state = PipelineStageState::Completed;
477        } else {
478            stage_states.push(StageState {
479                stage_name: PipelineStage::Reuse.as_str().to_string(),
480                state: PipelineStageState::Skipped("disabled".to_string()),
481                duration_ms: None,
482            });
483        }
484
485        Ok(PipelineResult {
486            success: true,
487            stage_states,
488            error: None,
489        })
490    }
491
492    fn execute_stage(
493        &self,
494        stage: PipelineStage,
495        context: &mut PipelineContext,
496    ) -> Result<PipelineStageState, PipelineError> {
497        match stage {
498            PipelineStage::Detect => {
499                // Signals already in context
500                Ok(PipelineStageState::Completed)
501            }
502            PipelineStage::Select => {
503                let input = SelectorInput {
504                    signals: context
505                        .signals
506                        .iter()
507                        .map(|s| s.description.clone())
508                        .collect(),
509                    env: crate::core::EnvFingerprint {
510                        rustc_version: String::new(),
511                        cargo_lock_hash: String::new(),
512                        target_triple: String::new(),
513                        os: String::new(),
514                    },
515                    spec_id: None,
516                    limit: self.config.max_candidates,
517                };
518                context.candidates = self.selector.select(&input);
519                Ok(PipelineStageState::Completed)
520            }
521            PipelineStage::Mutate => {
522                context.proposals = context
523                    .candidates
524                    .iter()
525                    .enumerate()
526                    .map(|(i, candidate)| MutationProposal {
527                        proposal_id: format!("proposal_{}", i),
528                        signal_ids: vec![],
529                        gene_id: candidate.gene.id.clone(),
530                        description: format!("Mutation for gene {}", candidate.gene.id),
531                        estimated_impact: candidate.score,
532                        risk_level: MutationRiskLevel::Medium,
533                        proposed_changes: serde_json::json!({}),
534                    })
535                    .collect();
536                Ok(PipelineStageState::Completed)
537            }
538            PipelineStage::Execute => {
539                context.execution_result = Some(serde_json::json!({
540                    "status": "success"
541                }));
542                Ok(PipelineStageState::Completed)
543            }
544            PipelineStage::Validate => {
545                if let Some(proposal) = context.proposals.first() {
546                    context.validation_result = Some(ValidationResult {
547                        proposal_id: proposal.proposal_id.clone(),
548                        passed: true,
549                        score: 0.8,
550                        issues: vec![],
551                        simulation_results: None,
552                    });
553                }
554                Ok(PipelineStageState::Completed)
555            }
556            PipelineStage::Evaluate => {
557                context.evaluation_result = Some(EvaluationResult {
558                    score: 0.8,
559                    improvements: vec![],
560                    regressions: vec![],
561                    recommendation: EvaluationRecommendation::Accept,
562                });
563                Ok(PipelineStageState::Completed)
564            }
565            PipelineStage::Solidify => {
566                context.solidified_genes = context
567                    .candidates
568                    .iter()
569                    .map(|c| c.gene.id.clone())
570                    .collect();
571                Ok(PipelineStageState::Completed)
572            }
573            PipelineStage::Reuse => {
574                context.reused_capsules = context
575                    .candidates
576                    .iter()
577                    .flat_map(|c| c.capsules.iter().map(|cap| cap.id.clone()))
578                    .collect();
579                Ok(PipelineStageState::Completed)
580            }
581        }
582    }
583}
584
585#[cfg(test)]
586mod tests {
587    use super::*;
588
589    #[test]
590    fn test_pipeline_config_default() {
591        let config = EvolutionPipelineConfig::default();
592        assert!(config.enable_detect);
593        assert!(config.enable_select);
594        assert!(config.enable_mutate);
595    }
596
597    #[test]
598    fn test_pipeline_stage_states() {
599        let config = EvolutionPipelineConfig {
600            enable_detect: false,
601            enable_select: true,
602            enable_mutate: false,
603            ..Default::default()
604        };
605
606        // Just test that config works
607        assert!(!config.enable_detect);
608        assert!(config.enable_select);
609    }
610}