Skip to main content

tycode_core/spawn/
spawn_agent.rs

1use std::collections::HashSet;
2use std::sync::Arc;
3
4use crate::agents::catalog::AgentCatalog;
5use crate::chat::events::{ToolExecutionResult, ToolRequest as ToolRequestEvent, ToolRequestType};
6use crate::tools::r#trait::{
7    ContinuationPreference, ToolCallHandle, ToolCategory, ToolExecutor, ToolOutput, ToolRequest,
8};
9use crate::tools::ToolName;
10use anyhow::Result;
11use serde::{Deserialize, Serialize};
12use serde_json::{json, Value};
13
14#[derive(Debug, Serialize, Deserialize)]
15struct SpawnAgentParams {
16    task: String,
17    agent_type: String,
18}
19
20pub struct SpawnAgent {
21    catalog: Arc<AgentCatalog>,
22    allowed_agents: HashSet<String>,
23    current_agent: String,
24}
25
26impl SpawnAgent {
27    pub fn tool_name() -> ToolName {
28        ToolName::new("spawn_agent")
29    }
30
31    pub fn new(
32        catalog: Arc<AgentCatalog>,
33        allowed_agents: HashSet<String>,
34        current_agent: String,
35    ) -> Self {
36        Self {
37            catalog,
38            allowed_agents,
39            current_agent,
40        }
41    }
42}
43
44struct SpawnAgentHandle {
45    catalog: Arc<AgentCatalog>,
46    allowed_agents: HashSet<String>,
47    current_agent: String,
48    agent_type: String,
49    task: String,
50    tool_use_id: String,
51}
52
53#[async_trait::async_trait(?Send)]
54impl ToolCallHandle for SpawnAgentHandle {
55    fn tool_request(&self) -> ToolRequestEvent {
56        ToolRequestEvent {
57            tool_call_id: self.tool_use_id.clone(),
58            tool_name: "spawn_agent".to_string(),
59            tool_type: ToolRequestType::Other {
60                args: json!({
61                    "agent_type": self.agent_type,
62                    "task": self.task
63                }),
64            },
65        }
66    }
67
68    async fn execute(self: Box<Self>) -> ToolOutput {
69        // Check for self-spawning
70        if self.current_agent == self.agent_type {
71            return ToolOutput::Result {
72                content: format!(
73                    "Cannot spawn agent of type '{}' from the same agent type. Use complete_task with failure instead.",
74                    self.agent_type
75                ),
76                is_error: true,
77                continuation: ContinuationPreference::Continue,
78                ui_result: ToolExecutionResult::Error {
79                    short_message: format!("Cannot spawn self ({})", self.agent_type),
80                    detailed_message: format!(
81                        "Agent '{}' cannot spawn another '{}'. Use complete_task with failure instead.",
82                        self.agent_type, self.agent_type
83                    ),
84                },
85            };
86        }
87
88        if !self.allowed_agents.contains(&self.agent_type) {
89            return ToolOutput::Result {
90                content: format!(
91                    "Agent type '{}' not allowed. Allowed types: {:?}",
92                    self.agent_type, self.allowed_agents
93                ),
94                is_error: true,
95                continuation: ContinuationPreference::Continue,
96                ui_result: ToolExecutionResult::Error {
97                    short_message: format!("Agent type '{}' not allowed", self.agent_type),
98                    detailed_message: format!(
99                        "Cannot spawn '{}'. Allowed agent types: {:?}",
100                        self.agent_type, self.allowed_agents
101                    ),
102                },
103            };
104        }
105
106        match self.catalog.create_agent(&self.agent_type) {
107            Some(agent) => ToolOutput::PushAgent {
108                agent,
109                task: self.task,
110            },
111            None => ToolOutput::Result {
112                content: format!("Unknown agent type: {}", self.agent_type),
113                is_error: true,
114                continuation: ContinuationPreference::Continue,
115                ui_result: ToolExecutionResult::Error {
116                    short_message: format!("Unknown agent: {}", self.agent_type),
117                    detailed_message: format!(
118                        "Agent type '{}' not found in catalog. Available: {:?}",
119                        self.agent_type,
120                        self.catalog.get_agent_names()
121                    ),
122                },
123            },
124        }
125    }
126}
127
128#[async_trait::async_trait(?Send)]
129impl ToolExecutor for SpawnAgent {
130    fn name(&self) -> String {
131        "spawn_agent".to_string()
132    }
133
134    fn description(&self) -> String {
135        let mut agents: Vec<&str> = self.allowed_agents.iter().map(|s| s.as_str()).collect();
136        agents.sort();
137        format!(
138            "Spawn a sub-agent to handle a specific task. Available agent types: {}. The sub-agent starts with fresh context and runs to completion. Use this to break complex tasks into focused subtasks. WARNING: Never use this to work around failures - if you're a sub-agent and get stuck, use complete_task with failure instead to let the parent handle it.",
139            agents.join(", ")
140        )
141    }
142
143    fn input_schema(&self) -> Value {
144        json!({
145            "type": "object",
146            "required": ["task", "agent_type"],
147            "properties": {
148                "task": {
149                    "type": "string",
150                    "description": "Clear, specific description of what the sub-agent should accomplish. Include any relevant context, constraints, or guidance."
151                },
152                "agent_type": {
153                    "type": "string",
154                    "description": "Type of agent to spawn"
155                }
156            }
157        })
158    }
159
160    fn category(&self) -> ToolCategory {
161        ToolCategory::Meta
162    }
163
164    async fn process(&self, request: &ToolRequest) -> Result<Box<dyn ToolCallHandle>> {
165        let params: SpawnAgentParams = serde_json::from_value(request.arguments.clone())?;
166
167        Ok(Box::new(SpawnAgentHandle {
168            catalog: self.catalog.clone(),
169            allowed_agents: self.allowed_agents.clone(),
170            current_agent: self.current_agent.clone(),
171            agent_type: params.agent_type,
172            task: params.task,
173            tool_use_id: request.tool_use_id.clone(),
174        }))
175    }
176}