agent-base 0.1.1

A lightweight Agent Runtime Kernel for building AI agents in Rust
Documentation
use serde::{Deserialize, Serialize};
use serde_json::Value;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionPlan {
    pub id: String,
    pub objective: String,
    pub steps: Vec<PlanStep>,
    pub status: PlanStatus,
    #[serde(default)]
    pub context: Value,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlanStep {
    pub id: String,
    pub description: String,
    /// Business-agnostic payload. Each domain defines its own schema.
    /// For example, an ops step might be:
    /// `{"type":"ssh_command","command":"df -h","host_id":"host1"}`
    pub payload: Value,
    #[serde(default)]
    pub dependencies: Vec<String>,
    pub status: StepStatus,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub result: Option<StepResult>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PlanStatus {
    Created,
    Approved,
    Executing,
    Completed,
    Failed,
    Cancelled,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum StepStatus {
    Pending,
    Running,
    Completed,
    Failed,
    Skipped,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StepResult {
    pub success: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub output: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,
    pub duration_ms: u64,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RecoveryAction {
    Retry,
    Skip,
    Abort,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlanStoreData {
    pub plan: ExecutionPlan,
    #[serde(default)]
    pub metadata: Value,
}

impl ExecutionPlan {
    pub fn new(id: impl Into<String>, objective: impl Into<String>) -> Self {
        Self {
            id: id.into(),
            objective: objective.into(),
            steps: Vec::new(),
            status: PlanStatus::Created,
            context: Value::Null,
        }
    }

    pub fn current_step(&self) -> Option<&PlanStep> {
        self.steps.iter().find(|s| s.status == StepStatus::Running)
    }

    pub fn next_pending_step(&self) -> Option<&PlanStep> {
        self.steps.iter().find(|s| s.status == StepStatus::Pending)
    }

    pub fn is_completed(&self) -> bool {
        self.status == PlanStatus::Completed
            || self
                .steps
                .iter()
                .all(|s| matches!(s.status, StepStatus::Completed | StepStatus::Skipped))
    }

    pub fn has_failed(&self) -> bool {
        self.status == PlanStatus::Failed || self.steps.iter().any(|s| s.status == StepStatus::Failed)
    }

    pub fn progress(&self) -> (usize, usize) {
        let total = self.steps.len();
        let completed = self
            .steps
            .iter()
            .filter(|s| matches!(s.status, StepStatus::Completed | StepStatus::Skipped))
            .count();
        (completed, total)
    }
}

impl PlanStep {
    pub fn new(
        id: impl Into<String>,
        description: impl Into<String>,
        payload: Value,
    ) -> Self {
        Self {
            id: id.into(),
            description: description.into(),
            payload,
            dependencies: Vec::new(),
            status: StepStatus::Pending,
            result: None,
        }
    }

    pub fn with_dependencies(mut self, dependencies: Vec<String>) -> Self {
        self.dependencies = dependencies;
        self
    }
}

impl StepResult {
    pub fn success(output: impl Into<String>, duration_ms: u64) -> Self {
        Self {
            success: true,
            output: Some(output.into()),
            error: None,
            duration_ms,
        }
    }

    pub fn failure(error: impl Into<String>, duration_ms: u64) -> Self {
        Self {
            success: false,
            output: None,
            error: Some(error.into()),
            duration_ms,
        }
    }
}