telos_agent/tools/builtin/
team_create.rs1use async_trait::async_trait;
7use serde_json::{Value, json};
8use std::sync::{Arc, Mutex};
9
10use crate::error::AgentError;
11use crate::orchestration::team::{self, TeamConfig, TeamMember, lead_agent_id};
12use crate::tools::api::{Tool, ToolContext, ToolDefinition, ToolOutput};
13
14#[derive(Debug, Clone, Default)]
16pub struct TeamContext {
17 pub team_name: Option<String>,
18 pub lead_agent_id: Option<String>,
19}
20
21pub type SharedTeamContext = Arc<Mutex<TeamContext>>;
22
23pub struct TeamCreateTool {
25 team_ctx: SharedTeamContext,
26}
27
28impl TeamCreateTool {
29 pub fn new(team_ctx: SharedTeamContext) -> Self {
30 Self { team_ctx }
31 }
32}
33
34#[async_trait]
35impl Tool for TeamCreateTool {
36 fn definition(&self) -> ToolDefinition {
37 ToolDefinition {
38 name: "TeamCreate".into(),
39 description: "Create a collaboration team for parallel multi-agent work. \
40Use when a task can be split into independent subtasks. Creates shared task list and config. One team at a time."
41 .into(),
42 input_schema: json!({
43 "type": "object",
44 "properties": {
45 "team_name": {
46 "type": "string",
47 "description": "A short kebab-case name for the team (e.g. 'refactor-auth', 'api-migration')"
48 },
49 "description": {
50 "type": "string",
51 "description": "What this team is working on — shared context for all members"
52 },
53 "agent_type": {
54 "type": "string",
55 "description": "The subagent_type to use when spawning teammates (e.g. 'general-purpose', 'Explore')"
56 }
57 },
58 "required": ["team_name"]
59 }),
60 }
61 }
62
63 fn prompt_text(&self) -> Option<&'static str> {
64 Some(TEAM_CREATE_PROMPT)
65 }
66
67 async fn invoke(
68 &self,
69 arguments: Value,
70 context: ToolContext,
71 ) -> Result<ToolOutput, AgentError> {
72 let mut team_name = arguments
73 .get("team_name")
74 .and_then(|v| v.as_str())
75 .ok_or_else(|| AgentError::Validation("missing `team_name`".into()))?
76 .to_string();
77
78 {
80 let ctx = self.team_ctx.lock().unwrap();
81 if ctx.team_name.is_some() {
82 return Err(AgentError::Validation(
83 "Already managing a team. Call TeamDelete first before creating a new team."
84 .into(),
85 ));
86 }
87 }
88
89 if team::team_config_path(&team_name).map(|p| p.exists()).unwrap_or(false) {
91 team_name = format!("{team_name}-{}", short_id());
92 }
93
94 let description =
95 arguments.get("description").and_then(|v| v.as_str()).unwrap_or("").to_string();
96
97 let now = std::time::SystemTime::now()
98 .duration_since(std::time::UNIX_EPOCH)
99 .unwrap_or_default()
100 .as_secs();
101
102 let lead_id = lead_agent_id(&team_name);
103
104 let config = TeamConfig {
105 name: team_name.clone(),
106 description: if description.is_empty() { None } else { Some(description) },
107 created_at: now,
108 lead_agent_id: lead_id.clone(),
109 lead_session_id: context.session_id.clone(),
110 members: vec![TeamMember {
111 agent_id: lead_id.clone(),
112 name: "team-lead".into(),
113 agent_type: arguments.get("agent_type").and_then(|v| v.as_str()).map(String::from),
114 joined_at: now,
115 is_active: true,
116 }],
117 };
118
119 team::save_team_config(&config)?;
120
121 if let Some(tasks_dir) = team::team_tasks_dir(&team_name) {
123 let _ = std::fs::create_dir_all(&tasks_dir);
124 }
125
126 {
128 let mut ctx = self.team_ctx.lock().unwrap();
129 ctx.team_name = Some(team_name.clone());
130 ctx.lead_agent_id = Some(lead_id.clone());
131 }
132
133 Ok(ToolOutput::json(json!({
134 "team_name": team_name,
135 "lead_agent_id": lead_id,
136 "message": format!(
137 "Team '{team_name}' created. Lead agent: {lead_id}. \
138 Next steps: 1) Create tasks using TaskCreate or TodoWrite. \
139 2) Spawn teammates using Subagent tool with this team's task list. \
140 3) Teammates will discover the team via ~/.telos/teams/{team_name}/config.json"
141 )
142 })))
143 }
144
145 fn is_concurrency_safe(&self, _: &Value) -> bool {
146 true
147 }
148}
149
150fn short_id() -> String {
151 use std::time::{SystemTime, UNIX_EPOCH};
152 let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default();
153 format!("{:x}", now.as_nanos() & 0xFFF)
154}
155
156const TEAM_CREATE_PROMPT: &str = r#"TeamCreate initializes a multi-agent collaboration team.
157
158Use when the task has 3+ independent subtasks or when different agent types should work in parallel.
159
160Workflow: TeamCreate → create tasks → spawn teammates via Subagent → collect results → TeamDelete.
161
162Agent types: 'Explore' (codebase research), 'general-purpose' (implementation), 'Plan' (architecture), 'Debug' (debugging). Only ONE team at a time."#;