use anyhow::{bail, Result};
use std::sync::Arc;
use tokio::sync::RwLock;
use super::models::{AgentLink, DelegationMode, DelegationRecord, LinkDirection};
use super::storage::SwarmStorage;
pub struct DelegationManager {
storage: Arc<dyn SwarmStorage>,
active_counts: Arc<RwLock<ActiveCounts>>,
}
struct ActiveCounts {
per_link: std::collections::HashMap<(String, String), u32>,
per_agent: std::collections::HashMap<String, u32>,
}
impl ActiveCounts {
fn new() -> Self {
Self {
per_link: std::collections::HashMap::new(),
per_agent: std::collections::HashMap::new(),
}
}
fn increment(&mut self, source: &str, target: &str) {
*self
.per_link
.entry((source.to_string(), target.to_string()))
.or_insert(0) += 1;
*self.per_agent.entry(target.to_string()).or_insert(0) += 1;
}
fn decrement(&mut self, source: &str, target: &str) {
if let Some(c) = self
.per_link
.get_mut(&(source.to_string(), target.to_string()))
{
*c = c.saturating_sub(1);
}
if let Some(c) = self.per_agent.get_mut(target) {
*c = c.saturating_sub(1);
}
}
fn link_count(&self, source: &str, target: &str) -> u32 {
self.per_link
.get(&(source.to_string(), target.to_string()))
.copied()
.unwrap_or(0)
}
fn agent_count(&self, agent: &str) -> u32 {
self.per_agent.get(agent).copied().unwrap_or(0)
}
}
impl DelegationManager {
pub fn new(storage: Arc<dyn SwarmStorage>) -> Self {
Self {
storage,
active_counts: Arc::new(RwLock::new(ActiveCounts::new())),
}
}
pub async fn create_link(
&self,
source_id: &str,
target_id: &str,
direction: LinkDirection,
max_concurrent: u32,
) -> Result<AgentLink> {
if self.storage.get_agent(source_id).await?.is_none() {
bail!("Source agent '{}' not found", source_id);
}
if self.storage.get_agent(target_id).await?.is_none() {
bail!("Target agent '{}' not found", target_id);
}
let link = AgentLink::new(source_id.to_string(), target_id.to_string(), direction)
.with_max_concurrent(max_concurrent);
self.storage.create_agent_link(&link).await?;
Ok(link)
}
pub async fn delete_link(&self, source_id: &str, target_id: &str) -> Result<()> {
self.storage.delete_agent_link(source_id, target_id).await
}
pub async fn delegate(
&self,
source_name: &str,
target_name: &str,
task: &str,
mode: DelegationMode,
context: Option<String>,
) -> Result<DelegationRecord> {
let source = self
.storage
.get_agent_by_name(source_name)
.await?
.ok_or_else(|| anyhow::anyhow!("Source agent '{}' not found", source_name))?;
let target = self
.storage
.get_agent_by_name(target_name)
.await?
.ok_or_else(|| anyhow::anyhow!("Target agent '{}' not found", target_name))?;
let link = self
.storage
.check_link_permission(&source.agent_id, &target.agent_id)
.await?
.ok_or_else(|| {
anyhow::anyhow!(
"No delegation permission from '{}' to '{}'",
source_name,
target_name
)
})?;
let counts = self.active_counts.read().await;
let link_count = counts.link_count(&source.agent_id, &target.agent_id);
if link_count >= link.max_concurrent {
bail!(
"Per-link concurrency limit reached ({}/{}) for {} -> {}",
link_count,
link.max_concurrent,
source_name,
target_name
);
}
let agent = self
.storage
.get_agent(&target.agent_id)
.await?
.ok_or_else(|| anyhow::anyhow!("Target agent vanished"))?;
let max_agent = agent.max_concurrent.unwrap_or(5) as u32;
let agent_count = counts.agent_count(&target.agent_id);
if agent_count >= max_agent {
bail!(
"Per-agent concurrency limit reached ({}/{}) for '{}'",
agent_count,
max_agent,
target_name
);
}
drop(counts);
let mut record = DelegationRecord::new(
source.agent_id.clone(),
target.agent_id.clone(),
task.to_string(),
mode,
);
if let Some(ctx) = context {
record = record.with_context(ctx);
}
record.status = "running".to_string();
self.storage.record_delegation(&record).await?;
let mut counts = self.active_counts.write().await;
counts.increment(&source.agent_id, &target.agent_id);
Ok(record)
}
pub async fn complete_delegation(&self, delegation_id: &str, result: String) -> Result<()> {
let record = self
.storage
.get_delegation(delegation_id)
.await?
.ok_or_else(|| anyhow::anyhow!("Delegation '{}' not found", delegation_id))?;
self.storage
.update_delegation_status(delegation_id, "completed", Some(result))
.await?;
let mut counts = self.active_counts.write().await;
counts.decrement(&record.source_agent_id, &record.target_agent_id);
Ok(())
}
pub async fn fail_delegation(&self, delegation_id: &str, error: String) -> Result<()> {
let record = self
.storage
.get_delegation(delegation_id)
.await?
.ok_or_else(|| anyhow::anyhow!("Delegation '{}' not found", delegation_id))?;
self.storage
.update_delegation_status(delegation_id, "failed", Some(error))
.await?;
let mut counts = self.active_counts.write().await;
counts.decrement(&record.source_agent_id, &record.target_agent_id);
Ok(())
}
pub async fn cancel_delegation(&self, delegation_id: &str) -> Result<()> {
let record = self
.storage
.get_delegation(delegation_id)
.await?
.ok_or_else(|| anyhow::anyhow!("Delegation '{}' not found", delegation_id))?;
self.storage
.update_delegation_status(delegation_id, "cancelled", None)
.await?;
let mut counts = self.active_counts.write().await;
counts.decrement(&record.source_agent_id, &record.target_agent_id);
Ok(())
}
pub async fn list_active(&self, agent_id: &str) -> Result<Vec<DelegationRecord>> {
self.storage.list_active_delegations(agent_id).await
}
pub async fn get_links(&self, agent_id: &str) -> Result<Vec<AgentLink>> {
self.storage.get_agent_links(agent_id).await
}
pub async fn sync_counts(&self) -> Result<()> {
let agents = self.storage.list_all_agents().await?;
let mut counts = self.active_counts.write().await;
*counts = ActiveCounts::new();
for agent in &agents {
let delegations = self
.storage
.list_active_delegations(&agent.agent_id)
.await?;
for d in delegations {
counts.increment(&d.source_agent_id, &d.target_agent_id);
}
}
Ok(())
}
}