use std::sync::Arc;
use tokio::sync::mpsc;
use tracing::info;
use uuid::Uuid;
use crate::config::{AgentConfig, LlmConfig};
use crate::error::SdkResult;
use crate::traits::llm_client::LlmClient;
use crate::traits::prompt_builder::{DefaultPromptBuilder, PromptBuilder};
use crate::mailbox::broker::MessageBroker;
use crate::storage::AgentPaths;
use crate::task::store::TaskStore;
use crate::types::task::Task;
use super::agent_loop::{AgentLoop, AgentLoopResult};
use super::events::AgentEvent;
use super::hooks::HookRegistry;
use super::memory::MemoryStore;
use super::team_lead::{ExecutionSummary, TeamLead, TeammateSpec};
#[derive(Debug)]
pub enum TeamResult {
Single(AgentLoopResult),
Team(ExecutionSummary),
}
impl TeamResult {
pub fn total_tokens(&self) -> u64 {
match self {
Self::Single(r) => r.total_tokens,
Self::Team(s) => s.total_tokens_used,
}
}
}
pub struct AgentTeam {
llm_config: LlmConfig,
agent_config: AgentConfig,
llm_client: Option<Arc<dyn LlmClient>>,
prompt_builder: Arc<dyn PromptBuilder>,
hooks: HookRegistry,
source_root: std::path::PathBuf,
work_dir: std::path::PathBuf,
event_tx: Option<mpsc::UnboundedSender<AgentEvent>>,
teammate_specs: Vec<TeammateSpec>,
tasks: Vec<Task>,
}
impl AgentTeam {
pub fn new(llm_config: LlmConfig, agent_config: AgentConfig) -> Self {
Self {
llm_config,
agent_config,
llm_client: None,
prompt_builder: Arc::new(DefaultPromptBuilder),
hooks: HookRegistry::new(),
source_root: std::path::PathBuf::from("."),
work_dir: std::path::PathBuf::from("./output"),
event_tx: None,
teammate_specs: Vec::new(),
tasks: Vec::new(),
}
}
pub fn source_root(mut self, path: impl Into<std::path::PathBuf>) -> Self {
self.source_root = path.into();
self
}
pub fn work_dir(mut self, path: impl Into<std::path::PathBuf>) -> Self {
self.work_dir = path.into();
self
}
pub fn prompt_builder(mut self, builder: Arc<dyn PromptBuilder>) -> Self {
self.prompt_builder = builder;
self
}
pub fn event_channel(mut self, tx: mpsc::UnboundedSender<AgentEvent>) -> Self {
self.event_tx = Some(tx);
self
}
pub fn llm_client(mut self, client: Arc<dyn LlmClient>) -> Self {
self.llm_client = Some(client);
self
}
pub fn add_hook(mut self, hook: impl super::hooks::Hook + 'static) -> Self {
self.hooks.add(hook);
self
}
pub fn add_teammate(
mut self,
name: impl Into<String>,
prompt: impl Into<String>,
) -> Self {
self.teammate_specs.push(TeammateSpec {
name: name.into(),
prompt: prompt.into(),
require_plan_approval: false,
});
self
}
pub fn add_teammate_with_plan_approval(
mut self,
name: impl Into<String>,
prompt: impl Into<String>,
) -> Self {
self.teammate_specs.push(TeammateSpec {
name: name.into(),
prompt: prompt.into(),
require_plan_approval: true,
});
self
}
pub fn add_task(mut self, task: Task) -> Self {
self.tasks.push(task);
self
}
pub async fn run(mut self, _goal: &str) -> SdkResult<TeamResult> {
let client = match self.llm_client.take() {
Some(c) => c,
None => crate::llm::create_client(&self.llm_config)?,
};
let paths = AgentPaths::for_work_dir(&self.work_dir)?;
let team_name = paths.new_team_name();
let team_config_path = paths.team_config_path(&team_name);
let hooks = Arc::new(std::mem::take(&mut self.hooks));
let task_store = Arc::new(TaskStore::new(paths.team_tasks_dir(&team_name)));
task_store.init()?;
for task in &self.tasks {
let hook_result = hooks.evaluate(
&super::hooks::HookEvent::TaskCreated { task: task.clone() },
);
if let super::hooks::HookResult::Reject { feedback } = hook_result {
self.emit_event(AgentEvent::HookRejected {
event_name: "TaskCreated".to_string(),
feedback,
});
continue;
}
task_store.create_task(task)?;
}
std::fs::create_dir_all(paths.team_dir(&team_name)).map_err(crate::error::SdkError::Io)?;
let broker = Arc::new(MessageBroker::new(paths.team_mailbox_dir(&team_name))?);
let memory = Arc::new(MemoryStore::new(paths.team_memory_dir(&team_name))?);
let lead = TeamLead {
id: Uuid::new_v4(),
team_name,
team_config_path,
task_store,
broker,
llm_client: client,
prompt_builder: self.prompt_builder.clone(),
config: self.agent_config.clone(),
source_root: self.source_root.clone(),
work_dir: self.work_dir.clone(),
memory_store: memory,
event_tx: self.event_tx.clone(),
hooks,
teammate_specs: self.teammate_specs.clone(),
};
self.emit_event(AgentEvent::TeamSpawned {
teammate_count: self.teammate_specs.len().max(self.agent_config.max_parallel_agents),
});
lead.run().await.map(TeamResult::Team)
}
pub async fn run_single(mut self, user_message: &str) -> SdkResult<AgentLoopResult> {
let client = match self.llm_client.take() {
Some(c) => c,
None => crate::llm::create_client(&self.llm_config)?,
};
use crate::tools::command_tools::RunCommandTool;
use crate::tools::fs_tools::{ListDirectoryTool, ReadFileTool, WriteFileTool};
use crate::tools::registry::ToolRegistry;
use crate::tools::search_tools::SearchFilesTool;
use crate::tools::web_tools::WebSearchTool;
let mut tools = ToolRegistry::new();
tools.register(Arc::new(ReadFileTool {
source_root: self.source_root.clone(),
work_dir: self.work_dir.clone(),
}));
tools.register(Arc::new(WriteFileTool {
work_dir: self.work_dir.clone(),
}));
tools.register(Arc::new(ListDirectoryTool {
source_root: self.source_root.clone(),
work_dir: self.work_dir.clone(),
}));
tools.register(Arc::new(SearchFilesTool {
source_root: self.source_root.clone(),
}));
tools.register(Arc::new(WebSearchTool));
tools.register(Arc::new(RunCommandTool::with_defaults(
self.work_dir.clone(),
)));
let system = crate::prompts::single_agent_system_prompt(
&self.source_root,
&self.work_dir,
);
let mut agent = AgentLoop::new(
Uuid::new_v4(),
client,
tools,
system,
self.agent_config.max_loop_iterations,
);
if let Some(ref tx) = self.event_tx {
agent.set_event_sink(tx.clone());
}
info!("Running as single agent");
agent.run(user_message.to_string()).await
}
fn emit_event(&self, event: AgentEvent) {
if let Some(ref tx) = self.event_tx {
let _ = tx.send(event);
}
}
}