coalescent 0.1.0

High-level AI coordination patterns enabling intelligent agent coalescence
Documentation
//! Task management for AI coordination
//!
//! This module defines tasks that agents can coordinate around.

use crate::error::Result;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;

/// Unique identifier for a task
pub type TaskId = Uuid;

/// Task priority levels
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Priority {
    Low,
    Medium,
    High,
    Critical,
}

impl Priority {
    /// Get numeric value for priority comparison
    pub fn value(&self) -> u8 {
        match self {
            Priority::Low => 1,
            Priority::Medium => 2,
            Priority::High => 3,
            Priority::Critical => 4,
        }
    }
}

/// Current status of a task
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum TaskStatus {
    /// Task is created but not yet started
    Pending,
    /// Task is actively being worked on
    InProgress,
    /// Task is completed successfully
    Completed,
    /// Task failed to complete
    Failed(String),
    /// Task was cancelled
    Cancelled,
}

/// A task that can be coordinated by multiple agents
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Task {
    /// Unique identifier
    pub id: TaskId,
    
    /// Human-readable title
    pub title: String,
    
    /// Detailed description of what needs to be done
    pub description: String,
    
    /// Required capabilities for this task
    pub required_capabilities: Vec<String>,
    
    /// Optional capabilities that would be helpful
    pub preferred_capabilities: Vec<String>,
    
    /// Task priority
    pub priority: Priority,
    
    /// Current status
    pub status: TaskStatus,
    
    /// When the task was created
    pub created_at: DateTime<Utc>,
    
    /// When the task should be completed by (optional)
    pub deadline: Option<DateTime<Utc>>,
    
    /// Estimated effort required (in arbitrary units)
    pub estimated_effort: Option<f64>,
    
    /// Maximum number of agents that can work on this task
    pub max_agents: Option<usize>,
    
    /// Minimum number of agents required
    pub min_agents: usize,
    
    /// Custom metadata
    pub metadata: HashMap<String, serde_json::Value>,
}

impl Task {
    /// Create a new task with the given title
    pub fn new<S: Into<String>>(title: S) -> Self {
        Self {
            id: Uuid::new_v4(),
            title: title.into(),
            description: String::new(),
            required_capabilities: Vec::new(),
            preferred_capabilities: Vec::new(),
            priority: Priority::Medium,
            status: TaskStatus::Pending,
            created_at: Utc::now(),
            deadline: None,
            estimated_effort: None,
            max_agents: None,
            min_agents: 1,
            metadata: HashMap::new(),
        }
    }

    /// Set the task description
    pub fn description<S: Into<String>>(mut self, description: S) -> Self {
        self.description = description.into();
        self
    }

    /// Add required capabilities
    pub fn require_capabilities(mut self, capabilities: Vec<String>) -> Self {
        self.required_capabilities = capabilities;
        self
    }

    /// Add preferred capabilities
    pub fn prefer_capabilities(mut self, capabilities: Vec<String>) -> Self {
        self.preferred_capabilities = capabilities;
        self
    }

    /// Set the task priority
    pub fn priority(mut self, priority: Priority) -> Self {
        self.priority = priority;
        self
    }

    /// Set the deadline
    pub fn deadline(mut self, deadline: DateTime<Utc>) -> Self {
        self.deadline = Some(deadline);
        self
    }

    /// Set estimated effort
    pub fn estimated_effort(mut self, effort: f64) -> Self {
        self.estimated_effort = Some(effort);
        self
    }

    /// Set agent constraints
    pub fn agent_constraints(mut self, min: usize, max: Option<usize>) -> Self {
        self.min_agents = min;
        self.max_agents = max;
        self
    }

    /// Add custom metadata
    pub fn metadata<K, V>(mut self, key: K, value: V) -> Result<Self>
    where
        K: Into<String>,
        V: Serialize,
    {
        let json_value = serde_json::to_value(value)?;
        self.metadata.insert(key.into(), json_value);
        Ok(self)
    }

    /// Update task status
    pub fn update_status(&mut self, status: TaskStatus) {
        self.status = status;
    }

    /// Check if task is still active (not completed, failed, or cancelled)
    pub fn is_active(&self) -> bool {
        matches!(self.status, TaskStatus::Pending | TaskStatus::InProgress)
    }

    /// Check if task is overdue
    pub fn is_overdue(&self) -> bool {
        if let Some(deadline) = self.deadline {
            Utc::now() > deadline && self.is_active()
        } else {
            false
        }
    }

    /// Get priority score for sorting
    pub fn priority_score(&self) -> u8 {
        let mut score = self.priority.value() * 10;
        
        // Boost score if overdue
        if self.is_overdue() {
            score += 20;
        }
        
        score
    }
}

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

    #[test]
    fn test_task_creation() {
        let task = Task::new("Test task")
            .description("A test task")
            .priority(Priority::High)
            .require_capabilities(vec!["testing".to_string()]);

        assert_eq!(task.title, "Test task");
        assert_eq!(task.description, "A test task");
        assert_eq!(task.priority, Priority::High);
        assert_eq!(task.required_capabilities, vec!["testing"]);
        assert!(task.is_active());
        assert!(!task.is_overdue());
    }

    #[test]
    fn test_priority_ordering() {
        assert!(Priority::Critical.value() > Priority::High.value());
        assert!(Priority::High.value() > Priority::Medium.value());
        assert!(Priority::Medium.value() > Priority::Low.value());
    }

    #[test]
    fn test_task_metadata() {
        let task = Task::new("Test")
            .metadata("custom_field", "custom_value")
            .unwrap();
        
        assert!(task.metadata.contains_key("custom_field"));
    }
}