Skip to main content

telos_agent/tools/builtin/
team_create.rs

1//! `TeamCreate` tool — create a named collaboration team for multi-agent work.
2//!
3//! Writes a team config to `~/.telos/teams/{name}/config.json` and initializes
4//! a shared task list directory at `~/.telos/tasks/{name}/`.
5
6use 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/// Shared state tracking the currently active team for this session.
15#[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
23/// `TeamCreate` — initialize a new collaboration team.
24pub 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        // Check we're not already on a team.
79        {
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        // Ensure team name is unique — append suffix if already exists.
90        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        // Initialize shared task directory.
122        if let Some(tasks_dir) = team::team_tasks_dir(&team_name) {
123            let _ = std::fs::create_dir_all(&tasks_dir);
124        }
125
126        // Set team context.
127        {
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."#;