ryo-executor 0.1.0

[experimental] Mutation execution engine for RYO - parallel execution, conflict detection, workspace management
Documentation
//! Decider: Agent decision-making layer for ryo
//!
//! This module provides the abstraction for agents to make decisions
//! about what action to take next, based on context and world state.
//!
//! # Architecture
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────┐
//! │  Agent Mode                                                 │
//! ├─────────────────────────────────────────────────────────────┤
//! │  Plain Mode      │ Just execute the given operation        │
//! │  Agentic Mode    │ Autonomous decision-making via Decider  │
//! └─────────────────────────────────────────────────────────────┘
//!
//! ┌─────────────────────────────────────────────────────────────┐
//! │  Decider Implementations                                    │
//! ├─────────────────────────────────────────────────────────────┤
//! │  PlainDecider        │ μs   │ Pass-through (no decision)   │
//! │  KeywordDecider      │ μs   │ Keyword matching             │
//! │  ParameterizedDecider│ μs   │ WorldState-based decisions   │
//! │  MurmurationDecider  │ μs   │ Swarm-like collision avoid   │
//! └─────────────────────────────────────────────────────────────┘
//! ```
//!
//! # Usage
//!
//! ```rust,ignore
//! use ryo_executor::decider::{AgentMode, DeciderConfig, KeywordDecider};
//!
//! // Plain mode - just execute operations
//! let config = DeciderConfig::plain();
//!
//! // Agentic mode - autonomous decision-making
//! let config = DeciderConfig::agentic(Box::new(KeywordDecider::default()));
//! ```

mod action;
mod context;
mod deciders;
mod state;

pub use action::{Action, ActionKind, ActionResult};
pub use context::DecisionContext;
pub use deciders::{
    ComposableDecider,
    Decider,
    // Composable
    DecisionModifier,
    ErrorAwareModifier,
    MurmurationDecider,
    ParameterizedDecider,
    PlainDecider,
    RetryModifier,
    StallDetectionModifier,
    SuccessRateModifier,
};
pub use state::{AgentState, ErrorInfo, FileState};

use std::sync::Arc;

// ============================================================================
// Agent Mode
// ============================================================================

/// Agent execution mode
#[derive(Debug, Clone, Default)]
pub enum AgentMode {
    /// Plain mode: Execute the given operation directly without autonomous decision-making
    #[default]
    Plain,

    /// Agentic mode: Use a Decider to make autonomous decisions
    Agentic(Arc<dyn Decider>),
}

impl AgentMode {
    /// Create plain mode
    pub fn plain() -> Self {
        Self::Plain
    }

    /// Create agentic mode with a decider
    pub fn agentic(decider: impl Decider + 'static) -> Self {
        Self::Agentic(Arc::new(decider))
    }

    /// Check if this is plain mode
    pub fn is_plain(&self) -> bool {
        matches!(self, Self::Plain)
    }

    /// Check if this is agentic mode
    pub fn is_agentic(&self) -> bool {
        matches!(self, Self::Agentic(_))
    }

    /// Get the decider if in agentic mode
    pub fn decider(&self) -> Option<&dyn Decider> {
        match self {
            Self::Agentic(d) => Some(d.as_ref()),
            Self::Plain => None,
        }
    }
}

// ============================================================================
// Decider Configuration
// ============================================================================

/// Configuration for agent decision-making
#[derive(Debug, Clone)]
pub struct DeciderConfig {
    /// The execution mode
    pub mode: AgentMode,

    /// Maximum retries for failed actions
    pub max_retries: u32,

    /// Enable escalation on repeated failures
    pub enable_escalation: bool,

    /// Escalation threshold (failure rate that triggers escalation)
    pub escalation_threshold: f64,
}

impl Default for DeciderConfig {
    fn default() -> Self {
        Self {
            mode: AgentMode::Plain,
            max_retries: 3,
            enable_escalation: true,
            escalation_threshold: 0.4,
        }
    }
}

impl DeciderConfig {
    /// Create plain mode configuration
    pub fn plain() -> Self {
        Self::default()
    }

    /// Create agentic mode with parameterized decisions
    pub fn parameterized() -> Self {
        Self {
            mode: AgentMode::agentic(ParameterizedDecider::default()),
            ..Default::default()
        }
    }

    /// Create agentic mode with murmuration (swarm) behavior
    pub fn murmuration() -> Self {
        Self {
            mode: AgentMode::agentic(MurmurationDecider::default()),
            ..Default::default()
        }
    }

    /// Create agentic mode with a custom decider
    pub fn with_decider(decider: impl Decider + 'static) -> Self {
        Self {
            mode: AgentMode::agentic(decider),
            ..Default::default()
        }
    }

    /// Set maximum retries
    pub fn max_retries(mut self, n: u32) -> Self {
        self.max_retries = n;
        self
    }

    /// Enable or disable escalation
    pub fn escalation(mut self, enable: bool) -> Self {
        self.enable_escalation = enable;
        self
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_agent_mode_plain() {
        let mode = AgentMode::plain();
        assert!(mode.is_plain());
        assert!(!mode.is_agentic());
        assert!(mode.decider().is_none());
    }

    #[test]
    fn test_agent_mode_agentic() {
        let mode = AgentMode::agentic(ParameterizedDecider::default());
        assert!(!mode.is_plain());
        assert!(mode.is_agentic());
        assert!(mode.decider().is_some());
        assert_eq!(mode.decider().unwrap().name(), "ParameterizedDecider");
    }

    #[test]
    fn test_decider_config() {
        let config = DeciderConfig::parameterized();
        assert!(config.mode.is_agentic());
        assert_eq!(config.max_retries, 3);

        let config = DeciderConfig::plain().max_retries(5).escalation(false);
        assert!(config.mode.is_plain());
        assert_eq!(config.max_retries, 5);
        assert!(!config.enable_escalation);
    }
}