Skip to main content

brainwires_network/
agent_tools.rs

1//! Agent Management Tools for MCP
2//!
3//! Provides MCP tools for spawning and managing task agents
4
5use brainwires_core::{Tool, ToolInputSchema};
6use serde_json::json;
7use std::collections::HashMap;
8
9/// Registry of agent management tools
10pub struct AgentToolRegistry {
11    tools: Vec<Tool>,
12}
13
14impl AgentToolRegistry {
15    /// Create a new agent tool registry
16    pub fn new() -> Self {
17        let tools = vec![
18            Tool {
19                name: "agent_spawn".to_string(),
20                description: "Spawn a new task agent to work on a subtask autonomously. \
21                              The agent will execute the task in the background and report results. \
22                              Useful for breaking down large workloads hierarchically."
23                    .to_string(),
24                input_schema: ToolInputSchema::object(
25                    {
26                        let mut props = HashMap::new();
27                        props.insert("description".to_string(), json!({
28                            "type": "string",
29                            "description": "Description of the task for the agent to execute"
30                        }));
31                        props.insert("working_directory".to_string(), json!({
32                            "type": "string",
33                            "description": "Optional working directory for file operations. If not specified, uses the MCP server's current directory."
34                        }));
35                        props.insert("max_iterations".to_string(), json!({
36                            "type": "integer",
37                            "description": "Optional maximum number of iterations before the agent stops (default: 100). Set lower for simple tasks or higher for complex ones."
38                        }));
39                        props.insert("enable_validation".to_string(), json!({
40                            "type": "boolean",
41                            "description": "Enable automatic validation checks before completion (default: true). Validates syntax, duplicates, and build success."
42                        }));
43                        props.insert("build_type".to_string(), json!({
44                            "type": "string",
45                            "enum": ["npm", "cargo", "typescript"],
46                            "description": "Optional build type for validation (npm, cargo, or typescript). If specified, agent must pass build before completing."
47                        }));
48                        props.insert("enable_mdap".to_string(), json!({
49                            "type": "boolean",
50                            "description": "Enable MDAP (Massively Decomposed Agentic Processes) for zero-error execution through task decomposition and multi-agent voting (default: false)"
51                        }));
52                        props.insert("mdap_k".to_string(), json!({
53                            "type": "integer",
54                            "description": "Vote margin threshold for MDAP (default: 3). Higher values = more reliability but higher cost. Range: 1-7."
55                        }));
56                        props.insert("mdap_target_success".to_string(), json!({
57                            "type": "number",
58                            "description": "Target success rate for MDAP (default: 0.95). Range: 0.90-0.99."
59                        }));
60                        props.insert("mdap_preset".to_string(), json!({
61                            "type": "string",
62                            "enum": ["default", "high_reliability", "cost_optimized"],
63                            "description": "MDAP preset: 'default' (k=3, 95%), 'high_reliability' (k=5, 99%), 'cost_optimized' (k=2, 90%)"
64                        }));
65                        props
66                    },
67                    vec!["description".to_string()],
68                ),
69                requires_approval: false,
70                ..Default::default()
71            },
72            Tool {
73                name: "agent_list".to_string(),
74                description: "List all currently running task agents and their status"
75                    .to_string(),
76                input_schema: ToolInputSchema::object(HashMap::new(), vec![]),
77                requires_approval: false,
78                ..Default::default()
79            },
80            Tool {
81                name: "agent_status".to_string(),
82                description: "Get detailed status of a specific task agent".to_string(),
83                input_schema: ToolInputSchema::object(
84                    {
85                        let mut props = HashMap::new();
86                        props.insert("agent_id".to_string(), json!({
87                            "type": "string",
88                            "description": "ID of the agent to query"
89                        }));
90                        props
91                    },
92                    vec!["agent_id".to_string()],
93                ),
94                requires_approval: false,
95                ..Default::default()
96            },
97            Tool {
98                name: "agent_stop".to_string(),
99                description: "Stop a running task agent".to_string(),
100                input_schema: ToolInputSchema::object(
101                    {
102                        let mut props = HashMap::new();
103                        props.insert("agent_id".to_string(), json!({
104                            "type": "string",
105                            "description": "ID of the agent to stop"
106                        }));
107                        props
108                    },
109                    vec!["agent_id".to_string()],
110                ),
111                requires_approval: false,
112                ..Default::default()
113            },
114            Tool {
115                name: "agent_await".to_string(),
116                description: "Wait for a task agent to complete and return its result. \
117                              Unlike agent_status which returns immediately, this tool blocks \
118                              until the agent finishes (completes or fails) and returns the final result."
119                    .to_string(),
120                input_schema: ToolInputSchema::object(
121                    {
122                        let mut props = HashMap::new();
123                        props.insert("agent_id".to_string(), json!({
124                            "type": "string",
125                            "description": "ID of the agent to wait for"
126                        }));
127                        props.insert("timeout_secs".to_string(), json!({
128                            "type": "integer",
129                            "description": "Optional timeout in seconds. If not provided, waits indefinitely."
130                        }));
131                        props
132                    },
133                    vec!["agent_id".to_string()],
134                ),
135                requires_approval: false,
136                ..Default::default()
137            },
138            Tool {
139                name: "agent_pool_stats".to_string(),
140                description: "Get statistics about the agent pool".to_string(),
141                input_schema: ToolInputSchema::object(HashMap::new(), vec![]),
142                requires_approval: false,
143                ..Default::default()
144            },
145            Tool {
146                name: "agent_file_locks".to_string(),
147                description: "List all currently held file locks by agents".to_string(),
148                input_schema: ToolInputSchema::object(HashMap::new(), vec![]),
149                requires_approval: false,
150                ..Default::default()
151            },
152            // Self-improvement tools
153            Tool {
154                name: "self_improve_start".to_string(),
155                description: "Start an autonomous self-improvement loop that analyzes the codebase \
156                              and spawns agents to fix issues (clippy warnings, TODOs, missing docs, \
157                              dead code, test gaps, code smells)"
158                    .to_string(),
159                input_schema: ToolInputSchema::object(
160                    {
161                        let mut props = HashMap::new();
162                        props.insert("max_cycles".to_string(), json!({
163                            "type": "integer",
164                            "description": "Maximum number of improvement cycles (default: 10)"
165                        }));
166                        props.insert("max_budget".to_string(), json!({
167                            "type": "number",
168                            "description": "Maximum budget in dollars (default: 10.0)"
169                        }));
170                        props.insert("dry_run".to_string(), json!({
171                            "type": "boolean",
172                            "description": "List tasks without executing (default: false)"
173                        }));
174                        props.insert("strategies".to_string(), json!({
175                            "type": "string",
176                            "description": "Comma-separated list of strategies: clippy,todo_scanner,doc_gaps,test_coverage,refactoring,dead_code (empty = all)"
177                        }));
178                        props.insert("no_bridge".to_string(), json!({
179                            "type": "boolean",
180                            "description": "Disable MCP bridge execution path (default: false)"
181                        }));
182                        props.insert("no_direct".to_string(), json!({
183                            "type": "boolean",
184                            "description": "Disable direct agent execution path (default: false)"
185                        }));
186                        props
187                    },
188                    vec![],
189                ),
190                requires_approval: false,
191                ..Default::default()
192            },
193            Tool {
194                name: "self_improve_status".to_string(),
195                description: "Get the status of a running self-improvement session"
196                    .to_string(),
197                input_schema: ToolInputSchema::object(HashMap::new(), vec![]),
198                requires_approval: false,
199                ..Default::default()
200            },
201            Tool {
202                name: "self_improve_stop".to_string(),
203                description: "Stop a running self-improvement session"
204                    .to_string(),
205                input_schema: ToolInputSchema::object(HashMap::new(), vec![]),
206                requires_approval: false,
207                ..Default::default()
208            },
209        ];
210
211        Self { tools }
212    }
213
214    /// Get all agent management tools
215    pub fn get_tools(&self) -> &[Tool] {
216        &self.tools
217    }
218}
219
220impl Default for AgentToolRegistry {
221    fn default() -> Self {
222        Self::new()
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229
230    #[test]
231    fn test_registry_creation() {
232        let registry = AgentToolRegistry::new();
233        assert_eq!(registry.get_tools().len(), 10, "Should have 10 agent tools");
234    }
235
236    #[test]
237    fn test_default_creation() {
238        let registry = AgentToolRegistry::default();
239        assert_eq!(registry.get_tools().len(), 10);
240    }
241
242    #[test]
243    fn test_agent_spawn_tool() {
244        let registry = AgentToolRegistry::new();
245        let tools = registry.get_tools();
246
247        let spawn_tool = tools
248            .iter()
249            .find(|t| t.name == "agent_spawn")
250            .expect("agent_spawn tool should exist");
251
252        assert_eq!(spawn_tool.name, "agent_spawn");
253        assert!(spawn_tool.description.contains("autonomous"));
254        assert!(!spawn_tool.requires_approval);
255
256        // Check schema structure
257        assert_eq!(spawn_tool.input_schema.schema_type, "object");
258        assert!(spawn_tool.input_schema.properties.is_some());
259        let props = spawn_tool.input_schema.properties.as_ref().unwrap();
260        assert!(props.contains_key("description"));
261
262        assert!(spawn_tool.input_schema.required.is_some());
263        let required = spawn_tool.input_schema.required.as_ref().unwrap();
264        assert!(required.contains(&"description".to_string()));
265    }
266
267    #[test]
268    fn test_agent_list_tool() {
269        let registry = AgentToolRegistry::new();
270        let tools = registry.get_tools();
271
272        let list_tool = tools
273            .iter()
274            .find(|t| t.name == "agent_list")
275            .expect("agent_list tool should exist");
276
277        assert_eq!(list_tool.name, "agent_list");
278        assert!(list_tool.description.contains("running"));
279        assert!(!list_tool.requires_approval);
280
281        // Should have empty properties
282        assert_eq!(list_tool.input_schema.schema_type, "object");
283        let props = list_tool.input_schema.properties.as_ref().unwrap();
284        assert!(props.is_empty());
285    }
286
287    #[test]
288    fn test_agent_status_tool() {
289        let registry = AgentToolRegistry::new();
290        let tools = registry.get_tools();
291
292        let status_tool = tools
293            .iter()
294            .find(|t| t.name == "agent_status")
295            .expect("agent_status tool should exist");
296
297        assert_eq!(status_tool.name, "agent_status");
298        assert!(status_tool.description.contains("status"));
299        assert!(!status_tool.requires_approval);
300
301        // Should require agent_id parameter
302        let props = status_tool.input_schema.properties.as_ref().unwrap();
303        assert!(props.contains_key("agent_id"));
304
305        let required = status_tool.input_schema.required.as_ref().unwrap();
306        assert!(required.contains(&"agent_id".to_string()));
307    }
308
309    #[test]
310    fn test_agent_stop_tool() {
311        let registry = AgentToolRegistry::new();
312        let tools = registry.get_tools();
313
314        let stop_tool = tools
315            .iter()
316            .find(|t| t.name == "agent_stop")
317            .expect("agent_stop tool should exist");
318
319        assert_eq!(stop_tool.name, "agent_stop");
320        assert!(stop_tool.description.contains("Stop"));
321        assert!(!stop_tool.requires_approval);
322
323        // Should require agent_id parameter
324        let props = stop_tool.input_schema.properties.as_ref().unwrap();
325        assert!(props.contains_key("agent_id"));
326
327        let required = stop_tool.input_schema.required.as_ref().unwrap();
328        assert!(required.contains(&"agent_id".to_string()));
329    }
330
331    #[test]
332    fn test_agent_await_tool() {
333        let registry = AgentToolRegistry::new();
334        let tools = registry.get_tools();
335
336        let await_tool = tools
337            .iter()
338            .find(|t| t.name == "agent_await")
339            .expect("agent_await tool should exist");
340
341        assert_eq!(await_tool.name, "agent_await");
342        assert!(await_tool.description.contains("Wait"));
343        assert!(await_tool.description.contains("complete"));
344        assert!(!await_tool.requires_approval);
345
346        // Should require agent_id parameter
347        let props = await_tool.input_schema.properties.as_ref().unwrap();
348        assert!(props.contains_key("agent_id"));
349        assert!(props.contains_key("timeout_secs"));
350
351        let required = await_tool.input_schema.required.as_ref().unwrap();
352        assert!(required.contains(&"agent_id".to_string()));
353        // timeout_secs is optional
354        assert!(!required.contains(&"timeout_secs".to_string()));
355    }
356
357    #[test]
358    fn test_all_tools_have_descriptions() {
359        let registry = AgentToolRegistry::new();
360        let tools = registry.get_tools();
361
362        for tool in tools {
363            assert!(
364                !tool.description.is_empty(),
365                "Tool {} should have a description",
366                tool.name
367            );
368        }
369    }
370
371    #[test]
372    fn test_all_tools_have_object_schemas() {
373        let registry = AgentToolRegistry::new();
374        let tools = registry.get_tools();
375
376        for tool in tools {
377            assert_eq!(
378                tool.input_schema.schema_type, "object",
379                "Tool {} should have object schema",
380                tool.name
381            );
382        }
383    }
384
385    #[test]
386    fn test_tool_names_are_prefixed() {
387        let registry = AgentToolRegistry::new();
388        let tools = registry.get_tools();
389
390        for tool in tools {
391            assert!(
392                tool.name.starts_with("agent_") || tool.name.starts_with("self_improve_"),
393                "Tool {} should be prefixed with 'agent_' or 'self_improve_'",
394                tool.name
395            );
396        }
397    }
398
399    #[test]
400    fn test_no_approval_required() {
401        let registry = AgentToolRegistry::new();
402        let tools = registry.get_tools();
403
404        for tool in tools {
405            assert!(
406                !tool.requires_approval,
407                "Tool {} should not require approval for autonomous operation",
408                tool.name
409            );
410        }
411    }
412
413    #[test]
414    fn test_schema_serialization() {
415        let registry = AgentToolRegistry::new();
416        let tools = registry.get_tools();
417
418        for tool in tools {
419            let serialized = serde_json::to_value(&tool.input_schema);
420            assert!(
421                serialized.is_ok(),
422                "Tool {} schema should serialize to JSON",
423                tool.name
424            );
425
426            let value = serialized.unwrap();
427            assert!(value.is_object());
428            assert_eq!(value["type"], "object");
429        }
430    }
431}