coalescent 0.1.0

High-level AI coordination patterns enabling intelligent agent coalescence
Documentation
//! Coordination engine for managing multi-agent workflows
//!
//! This module contains the core coordination logic that enables agents to
//! coalesce around tasks and form effective working coalitions.

use crate::agent::{Agent, AgentId};
use crate::error::{CoalescentError, Result};
use crate::task::{Task, TaskStatus};
use crate::trust::TrustManager;
use std::collections::HashMap;
use tokio::sync::RwLock;
use uuid::Uuid;

/// A coalition of agents working together on a task
#[derive(Debug, Clone)]
pub struct Coalition {
    /// Unique coalition identifier
    pub id: Uuid,
    
    /// The task this coalition is working on
    pub task: Task,
    
    /// Agents participating in this coalition
    pub agents: Vec<AgentId>,
    
    /// Coalition leader (optional)
    pub leader: Option<AgentId>,
    
    /// Current coordination state
    pub state: CoalitionState,
}

/// Current state of a coalition
#[derive(Debug, Clone, PartialEq)]
pub enum CoalitionState {
    /// Coalition is forming, agents are joining
    Forming,
    /// Coalition is actively working on the task
    Active,
    /// Coalition has completed its work
    Completed,
    /// Coalition failed to complete the task
    Failed(String),
    /// Coalition was disbanded
    Disbanded,
}

/// Result of a coordination operation
#[derive(Debug)]
pub struct CoordinationResult {
    /// Whether the coordination was successful
    pub success: bool,
    
    /// Coalition that was formed (if any)
    pub coalition: Option<Coalition>,
    
    /// Any messages or feedback from the coordination process
    pub messages: Vec<String>,
}

/// The main coordination engine that manages agent coalescence
pub struct CoordinationEngine {
    /// Registered agents
    agents: RwLock<HashMap<AgentId, Agent>>,
    
    /// Active coalitions
    coalitions: RwLock<HashMap<Uuid, Coalition>>,
    
    /// Trust manager for reputation tracking
    trust_manager: TrustManager,
    
    /// Configuration settings
    config: CoordinationConfig,
}

/// Configuration for the coordination engine
#[derive(Debug, Clone)]
pub struct CoordinationConfig {
    /// Maximum time to wait for coalition formation (seconds)
    pub formation_timeout: u64,
    
    /// Minimum trust score required for participation
    pub min_trust_score: f64,
    
    /// Whether to automatically assign leaders
    pub auto_assign_leaders: bool,
    
    /// Maximum coalition size
    pub max_coalition_size: usize,
}

impl Default for CoordinationConfig {
    fn default() -> Self {
        Self {
            formation_timeout: 300, // 5 minutes
            min_trust_score: 0.5,
            auto_assign_leaders: true,
            max_coalition_size: 10,
        }
    }
}

impl CoordinationEngine {
    /// Create a new coordination engine with default configuration
    pub fn new() -> Self {
        Self::with_config(CoordinationConfig::default())
    }

    /// Create a new coordination engine with custom configuration
    pub fn with_config(config: CoordinationConfig) -> Self {
        Self {
            agents: RwLock::new(HashMap::new()),
            coalitions: RwLock::new(HashMap::new()),
            trust_manager: TrustManager::new(),
            config,
        }
    }

    /// Register a new agent with the coordination engine
    pub async fn register_agent(&self, agent: Agent) -> Result<()> {
        let agent_id = agent.id;
        
        let mut agents = self.agents.write().await;
        if agents.contains_key(&agent_id) {
            return Err(CoalescentError::agent(format!(
                "Agent {} is already registered", agent_id
            )));
        }
        
        agents.insert(agent_id, agent);
        
        tracing::info!("Registered agent: {}", agent_id);
        Ok(())
    }

    /// Unregister an agent from the coordination engine
    pub async fn unregister_agent(&self, agent_id: &AgentId) -> Result<()> {
        let mut agents = self.agents.write().await;
        if agents.remove(agent_id).is_none() {
            return Err(CoalescentError::agent(format!(
                "Agent {} not found", agent_id
            )));
        }
        
        tracing::info!("Unregistered agent: {}", agent_id);
        Ok(())
    }

    /// Find agents capable of working on a task
    pub async fn find_capable_agents(&self, task: &Task) -> Vec<AgentId> {
        let agents = self.agents.read().await;
        let mut capable_agents = Vec::new();

        for (agent_id, agent) in agents.iter() {
            // Check if agent has required capabilities
            let has_required = task.required_capabilities.iter()
                .all(|cap| agent.capabilities.iter().any(|agent_cap| &agent_cap.name == cap));

            if has_required {
                // Check trust score
                if let Ok(trust_score) = self.trust_manager.get_trust_score(agent_id).await {
                    if trust_score >= self.config.min_trust_score {
                        capable_agents.push(*agent_id);
                    }
                }
            }
        }

        capable_agents
    }

    /// Attempt to form a coalition around a task
    pub async fn coalesce_around_task(&self, mut task: Task) -> Result<CoordinationResult> {
        tracing::info!("Starting coalescence for task: {}", task.title);

        // Find capable agents
        let capable_agents = self.find_capable_agents(&task).await;
        
        if capable_agents.len() < task.min_agents {
            return Ok(CoordinationResult {
                success: false,
                coalition: None,
                messages: vec![format!(
                    "Not enough capable agents found. Required: {}, Found: {}", 
                    task.min_agents, capable_agents.len()
                )],
            });
        }

        // Select agents for the coalition (simple strategy: take the best ones)
        let coalition_size = std::cmp::min(
            capable_agents.len(),
            task.max_agents.unwrap_or(self.config.max_coalition_size)
        );
        
        let selected_agents: Vec<AgentId> = capable_agents.into_iter().take(coalition_size).collect();

        // Create coalition
        let coalition_id = Uuid::new_v4();
        let leader = if self.config.auto_assign_leaders && !selected_agents.is_empty() {
            Some(selected_agents[0])
        } else {
            None
        };

        task.update_status(TaskStatus::InProgress);

        let coalition = Coalition {
            id: coalition_id,
            task,
            agents: selected_agents,
            leader,
            state: CoalitionState::Active,
        };

        // Store the coalition
        let mut coalitions = self.coalitions.write().await;
        let agent_count = coalition.agents.len();
        coalitions.insert(coalition_id, coalition.clone());

        tracing::info!("Coalition {} formed with {} agents", coalition_id, agent_count);

        Ok(CoordinationResult {
            success: true,
            coalition: Some(coalition),
            messages: vec![format!("Coalition formed successfully with {} agents", agent_count)],
        })
    }

    /// Get information about an active coalition
    pub async fn get_coalition(&self, coalition_id: &Uuid) -> Option<Coalition> {
        let coalitions = self.coalitions.read().await;
        coalitions.get(coalition_id).cloned()
    }

    /// List all active coalitions
    pub async fn list_coalitions(&self) -> Vec<Coalition> {
        let coalitions = self.coalitions.read().await;
        coalitions.values().cloned().collect()
    }

    /// Get the current configuration
    pub fn config(&self) -> &CoordinationConfig {
        &self.config
    }
}

impl Default for CoordinationEngine {
    fn default() -> Self {
        Self::new()
    }
}

impl Coalition {
    /// Execute the coalition's task (placeholder implementation)
    pub async fn execute(&self) -> Result<String> {
        tracing::info!("Coalition {} executing task: {}", self.id, self.task.title);
        
        // This is a placeholder - in a real implementation, this would
        // coordinate the actual work between agents
        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
        
        Ok(format!("Task '{}' completed by {} agents", self.task.title, self.agents.len()))
    }
}

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

    #[tokio::test]
    async fn test_agent_registration() {
        let engine = CoordinationEngine::new();
        let agent = Agent::new("test-agent", "Test Agent").unwrap();
        
        let result = engine.register_agent(agent).await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_coalition_formation() {
        let engine = CoordinationEngine::new();
        
        // Register a capable agent
        let agent = Agent::with_capabilities("test-agent", "Test Agent", vec!["analysis"]).unwrap();
        engine.register_agent(agent).await.unwrap();
        
        // Create a task requiring analysis
        let task = Task::new("Test analysis task")
            .require_capabilities(vec!["analysis".to_string()]);
        
        // Attempt to form coalition
        let result = engine.coalesce_around_task(task).await.unwrap();
        
        assert!(result.success);
        assert!(result.coalition.is_some());
    }
}