diff --git a/src/agents.rs b/src/agents.rs index 07c5d60..a3e862f 100644 --- a/src/agents.rs +++ b/src/agents.rs @@ -88,7 +88,7 @@ impl AgentPolicy { AgentType::TestJudge => Self { agent_type: agent_type.clone(), auto_approve: true, - max_depth: 2, + max_depth: 3, require_preview: true, timeout_seconds: 60, max_tokens: 4096, @@ -98,7 +98,7 @@ impl AgentPolicy { AgentType::BuildAgent => Self { agent_type: agent_type.clone(), auto_approve: false, - max_depth: 3, + max_depth: 5, require_preview: false, timeout_seconds: 300, max_tokens: 16384, diff --git a/src/task.rs b/src/task.rs index aaeece5..b00d177 100644 --- a/src/task.rs +++ b/src/task.rs @@ -210,7 +210,7 @@ pub struct AgentTaskSpec { #[serde(default)] pub tools_json: Option, /* Overrides the agent-type default system prompt when set. Takes highest - precedence in the resolution chain (above DB overrides and defaults). */ + precedence in the resolution chain (above DB overrides and defaults). */ #[serde(default)] pub system_prompt: Option, } diff --git a/src/todo.rs b/src/todo.rs index 6586a7c..213b003 100644 --- a/src/todo.rs +++ b/src/todo.rs @@ -39,7 +39,6 @@ pub struct ToDo { pub automation_chain_id: Option, pub tags: Vec, pub estimated_tokens: u32, - pub tools_json: Option, } impl Default for ToDo { @@ -65,7 +64,6 @@ impl Default for ToDo { automation_chain_id: None, tags: Vec::new(), estimated_tokens: 0, - tools_json: None, } } } @@ -93,7 +91,6 @@ impl ToDo { automation_chain_id: None, tags: Vec::new(), estimated_tokens: 0, - tools_json: None, } } @@ -102,7 +99,7 @@ impl ToDo { self } - pub fn validate(&self, deps: &std::collections::HashMap>) -> Result<(), ValidationError> { + pub fn validate(&self) -> Result<(), ValidationError> { if self.title.is_empty() { return Err(ValidationError::EmptyTitle); } @@ -119,13 +116,21 @@ impl ToDo { return Err(ValidationError::InvalidPriority); } - if let Some(cycle_node) = Self::detect_cycle_in_graph(deps) { - return Err(ValidationError::CircularDependency(cycle_node.to_string())); + if self.has_circular_dependency() { + return Err(ValidationError::CircularDependency(self.id.to_string())); } Ok(()) } + fn has_circular_dependency(&self) -> bool { + // Direct self-dependency + if self.depends_on.contains(&self.id) { + return true; + } + false + } + /* DFS cycle detection over a full dependency graph. Returns the first todo_id found in a cycle, or None when the graph is acyclic. */ pub fn detect_cycle_in_graph(deps: &std::collections::HashMap>) -> Option { @@ -191,7 +196,6 @@ impl ToDo { (ToDoStatus::Delegated, ToDoStatus::Completed) => true, (ToDoStatus::Delegated, ToDoStatus::Blocked) => true, (ToDoStatus::Failed, ToDoStatus::Pending) => true, - (ToDoStatus::Failed, ToDoStatus::ReadyForAgent) => true, (ToDoStatus::PendingApproval, ToDoStatus::InProgress) => true, (ToDoStatus::PendingApproval, ToDoStatus::Blocked) => true, _ => self.status == status, @@ -209,25 +213,6 @@ impl ToDo { Ok(()) } - /// Walk the parent_todo_id chain and return the depth (1 = root, 2 = child, etc.) - pub fn calculate_depth( - todo_id: Uuid, - get_parent: impl Fn(Uuid) -> Option, - ) -> usize { - let mut depth = 1usize; - let mut current_id = get_parent(todo_id); - let mut visited = HashSet::new(); - - while let Some(pid) = current_id { - if !visited.insert(pid) { - break; // cycle detected - } - depth += 1; - current_id = get_parent(pid); - } - depth - } - pub fn update( &mut self, title: Option, @@ -285,42 +270,40 @@ mod tests { #[test] fn test_todo_validate_empty_title() { - let todo = ToDo::default(); - let deps = std::collections::HashMap::new(); - assert!(matches!(todo.validate(&deps), Err(ValidationError::EmptyTitle))); + let mut todo = ToDo::default(); + todo.title = "".to_string(); + assert!(matches!(todo.validate(), Err(ValidationError::EmptyTitle))); } #[test] fn test_todo_validate_title_too_long() { let mut todo = ToDo::default(); todo.title = "a".repeat(MAX_TITLE_LENGTH + 1); - let deps = std::collections::HashMap::new(); assert!(matches!( - todo.validate(&deps), + todo.validate(), Err(ValidationError::TitleTooLong) )); } #[test] fn test_todo_validate_priority() { - let deps = std::collections::HashMap::new(); let mut todo = ToDo::new("Test".to_string(), "desc".to_string(), Uuid::new_v4()); todo.priority = 0; assert!(matches!( - todo.validate(&deps), + todo.validate(), Err(ValidationError::InvalidPriority) )); let mut todo = ToDo::new("Test".to_string(), "desc".to_string(), Uuid::new_v4()); todo.priority = 1001; assert!(matches!( - todo.validate(&deps), + todo.validate(), Err(ValidationError::InvalidPriority) )); let mut todo = ToDo::new("Test".to_string(), "desc".to_string(), Uuid::new_v4()); todo.priority = 500; - assert!(todo.validate(&deps).is_ok()); + assert!(todo.validate().is_ok()); } #[test] @@ -392,19 +375,16 @@ mod tests { } #[test] - fn test_detect_cycle_in_graph_self_reference() { - let id = Uuid::new_v4(); - let mut deps = std::collections::HashMap::new(); - deps.insert(id, vec![id]); - assert!(ToDo::detect_cycle_in_graph(&deps).is_some()); + fn test_has_circular_dependency_self_reference() { + let mut todo = ToDo::default(); + todo.depends_on = vec![todo.id]; + assert!(todo.has_circular_dependency()); } #[test] - fn test_detect_cycle_in_graph_none() { - let id = Uuid::new_v4(); - let mut deps = std::collections::HashMap::new(); - deps.insert(id, vec![]); - assert!(ToDo::detect_cycle_in_graph(&deps).is_none()); + fn test_has_circular_dependency_none() { + let todo = ToDo::default(); + assert!(!todo.has_circular_dependency()); } #[test] @@ -426,20 +406,4 @@ mod tests { deps.insert(id_b, vec![]); assert!(ToDo::detect_cycle_in_graph(&deps).is_none()); } - - #[test] - fn test_validate_catches_indirect_cycle() { - let id_a = Uuid::new_v4(); - let id_b = Uuid::new_v4(); - let mut todo = ToDo::new("Test".to_string(), "desc".to_string(), Uuid::new_v4()); - todo.id = id_a; - todo.depends_on = vec![id_b]; - let mut deps = std::collections::HashMap::new(); - deps.insert(id_a, vec![id_b]); - deps.insert(id_b, vec![id_a]); - assert!(matches!( - todo.validate(&deps), - Err(ValidationError::CircularDependency(_)) - )); - } } diff --git a/src/tools.rs b/src/tools.rs index 631e13c..069ddaf 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -97,11 +97,6 @@ pub struct ToolDefinition { /* Caller surface (chat/planner/agent) in which this tool may appear. */ #[serde(default)] pub context_visibility: ContextVisibility, - /* Minimum agent depth required for this tool to be visible. - 0 = visible to all agents. - 1+ = only visible to agents with max_depth >= this value. */ - #[serde(default)] - pub required_depth: u32, } const fn default_timeout_ms() -> u64 { @@ -124,7 +119,6 @@ impl Default for ToolDefinition { deprecation_message: String::new(), execution_context: ExecutionContext::default(), context_visibility: ContextVisibility::default(), - required_depth: 0, } } } @@ -834,7 +828,6 @@ pub struct ToolDocumentation { pub const PLANNER_ONLY_TOOLS: &[&str] = &[ "change_planner_mode", "propose_strategic_item", - "split_task", "audit_assumptions", "identify_blind_spots", "check_dependencies", @@ -859,16 +852,13 @@ This is the Krill → Reef proxy set, not the full set of Reef-only tools. Tools like kanban_*, document_*, and strategic items are always executed on Reef via MTP and are NOT in this list — they have no local implementation. */ pub const REEF_PROXY_TOOLS: &[&str] = &[ - "read_file", - "file_read", - "write_file", - "file_write", + "read_file", "file_read", + "write_file", "file_write", "edit_file", "list_directory", "search_files", "grep", - "bash", - "execute", + "bash", "execute", "delete_workspace", "list_files", ]; @@ -1549,34 +1539,6 @@ pub fn tool_definitions() -> Vec { "required": ["project_id", "title", "description", "rationale", "proposer_id"] }) ), - tool!( - "split_task", - "Split a Pending todo into multiple subtasks with proper dependency tracking, depth validation, and cycle detection. Each subtask should be independently deployable to an agent.", - "strategic", - false, false, true, - serde_json::json!({ - "type": "object", - "properties": { - "project_id": { "type": "string", "description": "Project ID" }, - "parent_todo_id": { "type": "string", "description": "ID of the todo to split (must be in Pending status)" }, - "subtasks": { - "type": "array", - "description": "Array of subtask definitions", - "items": { - "type": "object", - "properties": { - "title": { "type": "string", "description": "Title of the subtask" }, - "description": { "type": "string", "description": "Detailed description" }, - "depends_on": { "type": "array", "items": { "type": "integer" }, "description": "Indices (0-based) of subtasks this one depends on" }, - "agent_type_hint": { "type": "string", "description": "Optional agent type hint (build_agent, doc_agent, explore_agent, test_judge)" } - }, - "required": ["title"] - } - } - }, - "required": ["project_id", "parent_todo_id", "subtasks"] - }) - ), tool!( "change_planner_mode", "Switch the Planner's working mode. Modes: cooperative (fast shipping), critical (audit), red_team (pre-mortem), socratic (discovery), execution (task decomposition).", @@ -1738,81 +1700,6 @@ pub fn tool_definitions() -> Vec { } } - /* Assign required_depth per tool. Depth 0 = visible to all agents. - Depth 1 = needs one planning cycle (bash, write/edits, build tools). - Depth 2 = needs moderate depth (kanban mutation, workspace creation). - Depth 3 = needs deep reasoning (strategic, batch planning). */ - let depth_map: std::collections::HashMap<&str, u32> = [ - // Depth 0 — always visible - ("read_file", 0), - ("file_read", 0), - ("list_directory", 0), - ("search_files", 0), - ("list_files", 0), - ("grep", 0), - ("web_search", 0), - ("web_fetch", 0), - ("web_api", 0), - ("workspace_info", 0), - ("kanban_list_board", 0), - ("kanban_add_tag", 0), - ("git_status", 0), - ("git_diff", 0), - ("git_log", 0), - ("git_branch", 0), - ("get_documentation_context", 0), - ("documentation_tree", 0), - ("verify_documentation", 0), - ("find_references", 0), - ("file_dependencies", 0), - ("audit_assumptions", 0), - ("identify_blind_spots", 0), - ("check_dependencies", 0), - ("evaluate_plan_risk", 0), - ("compare_project_patterns", 0), - ("find_similar_risks", 0), - ("report_completion", 0), - // Depth 1 — needs one planning cycle - ("write_file", 1), - ("file_write", 1), - ("edit_file", 1), - ("bash", 1), - ("execute", 1), - ("cargo_check", 1), - ("npm_build", 1), - ("python_check", 1), - ("test_runner", 1), - ("delete_workspace", 1), - ("create_venv", 1), - ("install_dependencies", 1), - ("document_file", 1), - ("document_project", 1), - ("document_folder", 1), - ("store_file_doc", 1), - // Depth 2 — needs moderate depth - ("kanban_create_todo", 2), - ("kanban_update_todo", 2), - ("kanban_delete_todo", 2), - ("kanban_move_todo", 2), - ("kanban_create_task", 2), - ("kanban_update_task", 2), - ("create_workspace", 2), - ("split_task", 2), - // Depth 3 — needs deep reasoning - ("submit_batch_plan", 3), - ("change_planner_mode", 3), - ("propose_strategic_item", 3), - ] - .iter() - .cloned() - .collect(); - - for tool in tools.iter_mut() { - if let Some(&depth) = depth_map.get(tool.id.as_str()) { - tool.required_depth = depth; - } - } - tools } @@ -1834,7 +1721,7 @@ pub fn tool_ids_for_agent_type(agent_type: &crate::agents::AgentType) -> Option< .collect::>() }; - let ids = Some(match agent_type { + Some(match agent_type { crate::agents::AgentType::BuildAgent => curated(&[ "read_file", "write_file", @@ -1890,25 +1777,7 @@ pub fn tool_ids_for_agent_type(agent_type: &crate::agents::AgentType) -> Option< "web_search", "web_fetch", ]), - }); - - // Filter by depth: remove tools whose required_depth exceeds the agent's max_depth - let policy = crate::agents::AgentPolicy::defaults_for(agent_type); - let max_depth = policy.max_depth; - let all_tools = tool_definitions(); - let depth_map: std::collections::HashMap<&str, u32> = all_tools - .iter() - .map(|t| (t.id.as_str(), t.required_depth)) - .collect(); - - ids.retain(|id| { - depth_map - .get(id.as_str()) - .map(|&req| req <= max_depth) - .unwrap_or(true) - }); - - Some(ids) + }) } /* Formats tool definitions as OpenAI-compatible function-calling JSON. */