use std::sync::Arc; use async_trait::async_trait; use uuid::Uuid; use vapora_swarm::coordinator::SwarmCoordinator; use crate::coordination::{AgentAssignment, AgentLoad, AgentProfile, SwarmCoordination}; /// Adapter: SwarmCoordination → SwarmCoordinator /// Implements the coordination abstraction using the real swarm coordinator. pub struct SwarmCoordinationAdapter { swarm: Arc, } impl SwarmCoordinationAdapter { pub fn new(swarm: Arc) -> Self { Self { swarm } } } #[async_trait] impl SwarmCoordination for SwarmCoordinationAdapter { async fn register_profiles(&self, profiles: Vec) -> anyhow::Result<()> { for profile in profiles { let swarm_profile = vapora_swarm::messages::AgentProfile { id: profile.id.clone(), roles: vec![profile.role.clone()], capabilities: vec![profile.role], current_load: 0.0, availability: true, success_rate: profile.success_rate, }; self.swarm.register_agent(swarm_profile)?; } Ok(()) } /// Select best agent via swarm bidding. /// /// Uses `submit_task_for_bidding` which applies load-balanced scoring /// (success_rate / (1 + current_load)) across all available agents with /// matching capabilities. async fn select_agent( &self, task_type: &str, required_expertise: Option<&str>, ) -> anyhow::Result { let capabilities: Vec = match required_expertise { Some(exp) => vec![task_type.to_string(), exp.to_string()], None => vec![task_type.to_string()], }; // Use a ephemeral task_id for selection; the caller manages actual task IDs. let selection_id = Uuid::new_v4().to_string(); let agent_id = self .swarm .submit_task_for_bidding(selection_id, task_type.to_string(), capabilities) .await .map_err(|e| anyhow::anyhow!("Swarm bidding failed: {}", e))? .ok_or_else(|| anyhow::anyhow!("No available agent for task_type: {}", task_type))?; let confidence = self .swarm .get_agent(&agent_id) .map(|profile| profile.success_rate) .unwrap_or(0.5); Ok(AgentAssignment { // Swarm profiles use ID as display name (no separate name field) agent_name: agent_id.clone(), agent_id, confidence, }) } /// Report task completion and update agent load in the swarm. /// /// On success the agent is marked available with minimal load. /// On failure the agent receives a penalty load (0.5) to deprioritize it /// in future selections until it recovers. async fn report_completion( &self, agent_id: &str, success: bool, _duration_ms: u64, ) -> anyhow::Result<()> { let new_load = if success { 0.0 } else { 0.5 }; self.swarm .update_agent_status(agent_id, new_load, true) .map_err(|e| anyhow::anyhow!("Failed to update agent status: {}", e)) } /// Query current agent load from swarm profile. /// /// Infers `current_tasks` from the fractional load stored in the swarm /// profile (each task represents ~10% of a capacity-10 agent). async fn agent_load(&self, agent_id: &str) -> anyhow::Result { let profile = self .swarm .get_agent(agent_id) .ok_or_else(|| anyhow::anyhow!("Agent not found in swarm: {}", agent_id))?; const CAPACITY: usize = 10; let current_tasks = (profile.current_load * CAPACITY as f64).round() as usize; Ok(AgentLoad { agent_id: agent_id.to_string(), current_tasks, capacity: CAPACITY, }) } }