Skip to main content

matrixcode_core/tools/
agent.rs

1//! `agent` tool - Spawn subagents to execute tasks.
2//!
3//! This tool allows the model to spawn independent subagent instances
4//! for parallel task execution, matching Claude Code's Agent tool interface.
5
6use std::sync::Arc;
7
8use anyhow::Result;
9use async_trait::async_trait;
10use serde_json::{Value, json};
11
12use super::{Tool, ToolDefinition, subagent_tools_arc};
13use crate::tools::subagent_executor::{SubagentExecutor, SubagentTask, SubagentConfig};
14use crate::event::AgentEvent;
15use crate::skills::Skill;
16use tokio::sync::mpsc;
17
18/// Agent tool - spawns subagents for task execution
19pub struct AgentTool {
20    /// Event channel for forwarding subagent events
21    event_tx: Option<mpsc::Sender<AgentEvent>>,
22    /// Skills for subagent tool creation
23    skills: Arc<Vec<Skill>>,
24}
25
26impl AgentTool {
27    /// Create a new Agent tool
28    pub fn new() -> Self {
29        Self {
30            event_tx: None,
31            skills: Arc::new(Vec::new()),
32        }
33    }
34
35    /// Create with event channel for event forwarding
36    pub fn with_event_tx(event_tx: mpsc::Sender<AgentEvent>) -> Self {
37        Self {
38            event_tx: Some(event_tx),
39            skills: Arc::new(Vec::new()),
40        }
41    }
42
43    /// Create with skills (for subagent tool creation)
44    pub fn with_skills(skills: Arc<Vec<Skill>>) -> Self {
45        Self {
46            event_tx: None,
47            skills,
48        }
49    }
50
51    /// Create with both event channel and skills
52    pub fn with_event_tx_and_skills(
53        event_tx: mpsc::Sender<AgentEvent>,
54        skills: Arc<Vec<Skill>>,
55    ) -> Self {
56        Self {
57            event_tx: Some(event_tx),
58            skills,
59        }
60    }
61}
62
63impl Default for AgentTool {
64    fn default() -> Self {
65        Self::new()
66    }
67}
68
69#[async_trait]
70impl Tool for AgentTool {
71    fn definition(&self) -> ToolDefinition {
72        ToolDefinition {
73            name: "Agent".to_string(),
74            description: "启动一个新的子代理来处理复杂、多步骤任务。每个子代理类型有特定能力和工具。
75
76可用的子代理类型:
77- claude: 通用代理,适合任何任务(默认)
78- Explore: 只读搜索代理,用于广泛搜索文件/目录/命名约定
79- Plan: 软件架构代理,用于设计实现计划
80- general-purpose: 通用代理,用于复杂搜索和多步骤任务
81
82使用场景:
83- 当任务匹配可用代理类型时
84- 当有独立工作可并行运行时
85- 当回答需要跨多个文件搜索时
86
87重要:
88- 代理的最终消息作为工具结果返回
89- 子代理类型通过 subagent_type 参数选择
90- 可选 isolation: \"worktree\" 创建独立 git 工树
91- 可选 run_in_background: true 异步运行代理".to_string(),
92            parameters: json!({
93                "type": "object",
94                "properties": {
95                    "subagent_type": {
96                        "type": "string",
97                        "description": "子代理类型(默认 general-purpose)",
98                        "enum": ["claude", "Explore", "Plan", "general-purpose"],
99                        "default": "general-purpose"
100                    },
101                    "description": {
102                        "type": "string",
103                        "description": "任务的简短描述(3-5词)"
104                    },
105                    "prompt": {
106                        "type": "string",
107                        "description": "子代理要执行的任务"
108                    },
109                    "isolation": {
110                        "type": "string",
111                        "enum": ["worktree"],
112                        "description": "隔离模式。\"worktree\" 创建临时 git 工树"
113                    },
114                    "run_in_background": {
115                        "type": "boolean",
116                        "default": false,
117                        "description": "设为 true 异步运行代理"
118                    }
119                },
120                "required": ["description", "prompt"]
121            }),
122            ..Default::default()
123        }
124    }
125
126    async fn execute(&self, params: Value) -> Result<String> {
127        let subagent_type = params["subagent_type"]
128            .as_str()
129            .unwrap_or("general-purpose");
130
131        let description = params["description"]
132            .as_str()
133            .ok_or_else(|| anyhow::anyhow!("missing 'description'"))?;
134
135        let prompt = params["prompt"]
136            .as_str()
137            .ok_or_else(|| anyhow::anyhow!("missing 'prompt'"))?;
138
139        let isolation = params["isolation"]
140            .as_str()
141            .unwrap_or("none");
142
143        let run_in_background = params["run_in_background"]
144            .as_bool()
145            .unwrap_or(false);
146
147        // Create subagent task
148        let task = SubagentTask {
149            id: uuid::Uuid::new_v4().to_string(),
150            description: description.to_string(),
151            prompt: prompt.to_string(),
152            subagent_type: subagent_type.to_string(),
153            isolation: isolation.to_string(),
154            work_path: None,
155        };
156
157        // Create event channel if not provided
158        let (event_tx, _event_rx) = mpsc::channel::<AgentEvent>(100);
159        let tx = self.event_tx.clone().unwrap_or(event_tx);
160
161        // Configure subagent based on type
162        let config = match subagent_type {
163            "Explore" => SubagentConfig {
164                model_name: "claude-sonnet-4-20250514".to_string(),
165                max_tokens: 4096,
166                system_prompt_prefix: Some("You are a fast, read-only search agent.".to_string()),
167                think: false,
168                tool_names: Some(vec![
169                    "read".to_string(), "grep".to_string(), "glob".to_string(),
170                    "ls".to_string(), "search".to_string(),
171                    "code_search".to_string(), "code_callers".to_string(),
172                    "code_callees".to_string(), "code_status".to_string(),
173                ]),
174            },
175            "Plan" => SubagentConfig {
176                model_name: "claude-sonnet-4-20250514".to_string(),
177                max_tokens: 8192,
178                system_prompt_prefix: Some("You are a software architecture planning agent.".to_string()),
179                think: true,
180                tool_names: Some(vec![
181                    "read".to_string(), "grep".to_string(), "glob".to_string(),
182                    "ls".to_string(), "search".to_string(),
183                    "code_search".to_string(), "code_callers".to_string(),
184                    "code_callees".to_string(), "code_status".to_string(),
185                    "enter_plan_mode".to_string(), "exit_plan_mode".to_string(),
186                    "todo_write".to_string(),
187                ]),
188            },
189            _ => SubagentConfig {
190                model_name: "claude-sonnet-4-20250514".to_string(),
191                max_tokens: 4096,
192                system_prompt_prefix: None,
193                think: false,
194                tool_names: None,
195            },
196        };
197
198        if run_in_background {
199            // Spawn background task
200            let task_clone = task.clone();
201            let config_clone = config.clone();
202            let tools = subagent_tools_arc(self.skills.clone());
203
204            tokio::spawn(async move {
205                let mut executor = SubagentExecutor::new(config_clone, tx, tools);
206                let result = executor.execute(task_clone).await;
207
208                if let Ok(r) = result {
209                    log::info!("Background agent task {} completed: success={}",
210                        r.task_id, r.success);
211                }
212            });
213
214            Ok(format!("Agent task '{}' started in background. Task ID: {}",
215                description, task.id))
216        } else {
217            // Execute synchronously
218            let tools = subagent_tools_arc(self.skills.clone());
219            let mut executor = SubagentExecutor::new(config, tx, tools);
220            let result = executor.execute(task).await?;
221
222            if result.success {
223                Ok(result.content)
224            } else {
225                Ok(format!("Agent task failed: {}", result.content))
226            }
227        }
228    }
229
230    fn risk_level(&self) -> crate::approval::RiskLevel {
231        // Agent tool spawns independent processes, medium risk
232        crate::approval::RiskLevel::Mutating
233    }
234}