1use brainwires_core::{Tool, ToolInputSchema};
6use serde_json::json;
7use std::collections::HashMap;
8
9pub struct AgentToolRegistry {
11 tools: Vec<Tool>,
12}
13
14impl AgentToolRegistry {
15 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 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 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 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 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 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 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 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 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}