From 63438b64a9731b7adfd99a5e84f4c988bf33eca8 Mon Sep 17 00:00:00 2001 From: Li Xiangtian <1193027052@qq.com> Date: Fri, 5 Jun 2026 19:46:11 +0800 Subject: [PATCH 1/3] feat: allow disabling delegation tools --- core/src/agent_api/capabilities.rs | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/core/src/agent_api/capabilities.rs b/core/src/agent_api/capabilities.rs index d7004ee..46287fc 100644 --- a/core/src/agent_api/capabilities.rs +++ b/core/src/agent_api/capabilities.rs @@ -18,6 +18,8 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::Arc; +const DISABLE_DELEGATION_TOOLS_ENV: &str = "A3S_CODE_DISABLE_DELEGATION_TOOLS"; + pub(super) struct SessionCapabilityInput<'a> { pub(super) code_config: &'a CodeConfig, pub(super) base_config: &'a AgentConfig, @@ -163,6 +165,9 @@ fn register_task_capability( use crate::tools::register_task_with_mcp; let registry = AgentRegistry::new(); + let disable_delegation_tools = disable_delegation_tools_from_env( + std::env::var(DISABLE_DELEGATION_TOOLS_ENV).ok().as_deref(), + ); let built_in_agent_dirs = built_in_agent_dirs(workspace); for dir in code_config .agent_dirs @@ -178,6 +183,10 @@ fn register_task_capability( registry.register_worker(worker.clone()); } + if disable_delegation_tools { + return Arc::new(registry); + } + let parent_context = ChildRunContext { security_provider: opts.security_provider.clone(), hook_engine: None, @@ -203,6 +212,17 @@ fn register_task_capability( registry } +fn disable_delegation_tools_from_env(value: Option<&str>) -> bool { + value + .map(|value| { + matches!( + value.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) + }) + .unwrap_or(false) +} + fn built_in_agent_dirs(workspace: &Path) -> Vec { let mut dirs = Vec::new(); if let Some(home) = std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE")) { @@ -265,6 +285,26 @@ fn group_mcp_tools_by_server(all_tools: Vec<(String, McpTool)>) -> HashMap Date: Mon, 22 Jun 2026 16:20:38 +0800 Subject: [PATCH 2/3] fix planning prompt preservation --- core/prompts/analysis/pre_analysis_system.md | 5 +- core/src/agent/execution_mode.rs | 24 ++++++++- core/src/agent/plan_execution.rs | 40 +++++++++++---- core/src/agent/planning_runtime.rs | 22 +++++++- core/src/agent/tests.rs | 53 +++++++++++++++++++- 5 files changed, 129 insertions(+), 15 deletions(-) diff --git a/core/prompts/analysis/pre_analysis_system.md b/core/prompts/analysis/pre_analysis_system.md index d232ab1..5c2bf75 100644 --- a/core/prompts/analysis/pre_analysis_system.md +++ b/core/prompts/analysis/pre_analysis_system.md @@ -23,7 +23,7 @@ Schema: ], "required_tools": ["tool_name"] }, - "optimized_input": "the same request with references resolved, in the user's language" + "optimized_input": "the full original request with references resolved, in the user's language" } Rules: @@ -43,4 +43,7 @@ Rules: VeryComplex = release, migration, security-sensitive, or broad architecture work. - Prefer `program` for repeated structured repository analysis; prefer `task`/`parallel_task` for delegated agent work. +- `optimized_input` must preserve every concrete constraint, path, name, branch, + environment variable, metric, and negative instruction from the original user + message. Do not replace the task with a short summary. - Respond with valid JSON only. No markdown fences, comments, or explanation. diff --git a/core/src/agent/execution_mode.rs b/core/src/agent/execution_mode.rs index 162bfd3..99ada7b 100644 --- a/core/src/agent/execution_mode.rs +++ b/core/src/agent/execution_mode.rs @@ -14,6 +14,26 @@ struct ExecutionRoute { } impl AgentLoop { + pub(super) fn preserve_original_prompt_for_execution( + original_prompt: &str, + optimized_input: &str, + ) -> String { + let original = original_prompt.trim(); + let optimized = optimized_input.trim(); + + if original.is_empty() { + return optimized.to_string(); + } + if optimized.is_empty() || optimized == original { + return original.to_string(); + } + if optimized.contains(original) { + return optimized.to_string(); + } + + format!("Original user request:\n{original}\n\nPlanner-optimized request:\n{optimized}") + } + pub(super) fn should_run_pre_analysis(&self) -> bool { match self.config.planning_mode { PlanningMode::Disabled => false, @@ -90,7 +110,9 @@ impl AgentLoop { let use_planning = self.resolve_planning_decision(style, pre_analysis.as_ref()); let effective_prompt = pre_analysis .as_ref() - .map(|analysis| analysis.optimized_input.clone()) + .map(|analysis| { + Self::preserve_original_prompt_for_execution(prompt, &analysis.optimized_input) + }) .unwrap_or_else(|| prompt.to_string()); ExecutionRoute { diff --git a/core/src/agent/plan_execution.rs b/core/src/agent/plan_execution.rs index ea0eb17..556003e 100644 --- a/core/src/agent/plan_execution.rs +++ b/core/src/agent/plan_execution.rs @@ -109,15 +109,22 @@ impl AgentLoop { } } - pub(super) fn delegated_prompt_for_step( + pub(super) fn delegated_prompt_for_step_with_goal( + plan_goal: Option<&str>, step: &Task, step_number: usize, total_steps: usize, ) -> String { - let mut prompt = format!( + let mut prompt = String::new(); + if let Some(goal) = plan_goal.map(str::trim).filter(|goal| !goal.is_empty()) { + prompt.push_str("Plan goal/context:\n"); + prompt.push_str(goal); + prompt.push_str("\n\n"); + } + prompt.push_str(&format!( "Execute plan step {}/{}.\n\nTask:\n{}\n", step_number, total_steps, step.content - ); + )); if let Some(criteria) = step .success_criteria .as_deref() @@ -131,7 +138,8 @@ impl AgentLoop { prompt } - pub(super) fn delegated_task_args( + pub(super) fn delegated_task_args_with_goal( + plan_goal: Option<&str>, step: &Task, step_number: usize, total_steps: usize, @@ -139,17 +147,20 @@ impl AgentLoop { json!({ "agent": Self::delegated_agent_for_step(step), "description": step.content, - "prompt": Self::delegated_prompt_for_step(step, step_number, total_steps), + "prompt": Self::delegated_prompt_for_step_with_goal(plan_goal, step, step_number, total_steps), }) } - pub(super) fn parallel_delegated_task_args( + pub(super) fn parallel_delegated_task_args_with_goal( + plan_goal: Option<&str>, steps: &[(Task, usize)], total_steps: usize, ) -> Value { let tasks = steps .iter() - .map(|(step, step_number)| Self::delegated_task_args(step, *step_number, total_steps)) + .map(|(step, step_number)| { + Self::delegated_task_args_with_goal(plan_goal, step, *step_number, total_steps) + }) .collect::>(); json!({ "tasks": tasks }) } @@ -275,9 +286,14 @@ impl AgentLoop { _ => "task", }; let args = if tool_name == "parallel_task" { - json!({ "tasks": [Self::delegated_task_args(&step, step_number, total_steps)] }) + json!({ "tasks": [Self::delegated_task_args_with_goal(Some(&plan.goal), &step, step_number, total_steps)] }) } else { - Self::delegated_task_args(&step, step_number, total_steps) + Self::delegated_task_args_with_goal( + Some(&plan.goal), + &step, + step_number, + total_steps, + ) }; let (output, _exit_code, is_error, _metadata) = self .execute_delegated_plan_tool(tool_name, &args, session_id, &event_tx) @@ -429,7 +445,11 @@ impl AgentLoop { .iter() .all(|(step, _)| Self::should_delegate_plan_step(step)) { - let args = Self::parallel_delegated_task_args(&ready_steps, total_steps); + let args = Self::parallel_delegated_task_args_with_goal( + Some(&plan.goal), + &ready_steps, + total_steps, + ); let (output, _exit_code, is_error, metadata) = self .execute_delegated_plan_tool("parallel_task", &args, session_id, &event_tx) .await; diff --git a/core/src/agent/planning_runtime.rs b/core/src/agent/planning_runtime.rs index 3867e71..49b7a68 100644 --- a/core/src/agent/planning_runtime.rs +++ b/core/src/agent/planning_runtime.rs @@ -5,6 +5,23 @@ use anyhow::Result; use tokio::sync::mpsc; impl AgentLoop { + pub(super) fn preserve_plan_goal_context( + mut plan: ExecutionPlan, + execution_prompt: &str, + ) -> ExecutionPlan { + let context = execution_prompt.trim(); + let goal = plan.goal.trim(); + + if context.is_empty() || goal == context || goal.contains(context) { + return plan; + } + + plan.goal = format!( + "Original user request and planning context:\n{context}\n\nPlanner goal:\n{goal}" + ); + plan + } + pub(super) async fn emit_task_updated( &self, event_tx: &Option>, @@ -60,7 +77,10 @@ impl AgentLoop { // Use pre-analysis result if available (goal + plan already computed in one LLM call). let (goal, plan) = if let Some(analysis) = pre_analysis { - (Some(analysis.goal.clone()), analysis.execution_plan.clone()) + ( + Some(analysis.goal.clone()), + Self::preserve_plan_goal_context(analysis.execution_plan.clone(), prompt), + ) } else { // Fall back: extract goal and create plan via separate LLM calls. let g = if self.config.goal_tracking { diff --git a/core/src/agent/tests.rs b/core/src/agent/tests.rs index 68d3cc2..15263fb 100644 --- a/core/src/agent/tests.rs +++ b/core/src/agent/tests.rs @@ -62,7 +62,7 @@ fn test_delegated_task_args_include_prompt_contract() { let task = Task::new("s1", "验证 program 工具") .with_tool("task") .with_success_criteria("All integration checks pass."); - let args = AgentLoop::delegated_task_args(&task, 2, 5); + let args = AgentLoop::delegated_task_args_with_goal(None, &task, 2, 5); assert_eq!(args["agent"], "verification"); assert_eq!(args["description"], "验证 program 工具"); @@ -81,7 +81,7 @@ fn test_parallel_delegated_task_args_preserve_order() { (Task::new("s1", "Find docs").with_tool("task"), 1), (Task::new("s2", "Run tests").with_tool("task"), 2), ]; - let args = AgentLoop::parallel_delegated_task_args(&steps, 2); + let args = AgentLoop::parallel_delegated_task_args_with_goal(None, &steps, 2); let tasks = args["tasks"].as_array().unwrap(); assert_eq!(tasks.len(), 2); @@ -89,6 +89,55 @@ fn test_parallel_delegated_task_args_preserve_order() { assert_eq!(tasks[1]["agent"], "verification"); } +#[test] +fn test_preserve_original_prompt_for_planning_execution() { + let original = + "Fix planning mode. Preserve /tmp/task.txt and do not drop negative instructions."; + let optimized = "Fix planning mode."; + + let preserved = AgentLoop::preserve_original_prompt_for_execution(original, optimized); + + assert!(preserved.contains("Original user request")); + assert!(preserved.contains("/tmp/task.txt")); + assert!(preserved.contains("do not drop negative instructions")); + assert!(preserved.contains("Planner-optimized request")); + assert!(preserved.contains(optimized)); +} + +#[test] +fn test_preserve_plan_goal_context_keeps_original_request_visible() { + use crate::planning::{Complexity, ExecutionPlan}; + + let plan = ExecutionPlan::new("Fix planning mode".to_string(), Complexity::Medium); + let execution_prompt = + "Original user request:\nFix planning mode for /workspace/app; do not change API."; + + let preserved = AgentLoop::preserve_plan_goal_context(plan, execution_prompt); + + assert!(preserved.goal.contains("/workspace/app")); + assert!(preserved.goal.contains("do not change API")); + assert!(preserved.goal.contains("Planner goal")); +} + +#[test] +fn test_delegated_plan_step_prompt_includes_plan_goal_context() { + use crate::planning::Task; + + let task = Task::new("s1", "Implement the first step").with_tool("task"); + let args = AgentLoop::delegated_task_args_with_goal( + Some("Original request: update /workspace/app and keep API stable."), + &task, + 1, + 1, + ); + let prompt = args["prompt"].as_str().unwrap(); + + assert!(prompt.contains("Plan goal/context")); + assert!(prompt.contains("/workspace/app")); + assert!(prompt.contains("keep API stable")); + assert!(prompt.contains("Implement the first step")); +} + #[test] fn test_memory_items_become_context_result() { let item = a3s_memory::MemoryItem::new("Use focused regression tests for context changes.") From c4403f7edd8796f077226c03e25783428a7ea01f Mon Sep 17 00:00:00 2001 From: Li Xiangtian <1193027052@qq.com> Date: Mon, 22 Jun 2026 17:20:17 +0800 Subject: [PATCH 3/3] refactor delegation tool gating into config --- CHANGELOG.md | 5 ++++ README.md | 14 ++++++--- core/src/agent_api.rs | 6 ++++ core/src/agent_api/capabilities.rs | 41 +++------------------------ core/src/agent_api/session_builder.rs | 12 +++----- core/src/agent_api/session_config.rs | 23 +++++++++++++++ core/src/agent_api/session_options.rs | 15 ++++++++++ core/src/agent_api/tests.rs | 23 +++++++++++++++ core/src/config/loader.rs | 11 +++++++ core/src/config/mod.rs | 9 ++++++ core/src/config/tests.rs | 2 ++ 11 files changed, 112 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e5130d..975a26b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -292,6 +292,11 @@ fields, new `SessionStore` trait methods with default no-op impls). global `auto_parallel` kill switch. Setting `auto_parallel = false` disables automatic parallel child-agent fan-out while keeping manual `parallel_task` available. +- Added `auto_delegation.allow_manual_delegation` and + `SessionOptions::with_manual_delegation_enabled(...)` so hosts can hide the + model-visible `task` / `parallel_task` tools per session while preserving the + child-agent registry for introspection and worker registration. This is an + operational cost/debug control, not a security sandbox. - Added `max_parallel_tasks` as the shared sibling fan-out limit for `parallel_task`, delegated plan waves, and safe parallel write batches. - Added a reusable ordered parallel executor so concurrent child results remain diff --git a/README.md b/README.md index 8ef256c..7ff6cf8 100644 --- a/README.md +++ b/README.md @@ -1527,10 +1527,11 @@ skill_dirs = ["./skills"] mcp_servers = [] auto_delegation { - enabled = false - auto_parallel = false - min_confidence = 0.72 - max_tasks = 4 + enabled = false + auto_parallel = false + allow_manual_delegation = true + min_confidence = 0.72 + max_tasks = 4 } ahp = { @@ -1549,6 +1550,11 @@ safe parallel write batches. `auto_delegation.enabled` controls Claude Code-style automatic subagent delegation. `auto_parallel = false` is a global kill switch for automatic parallel child-agent fan-out; manual `parallel_task` remains available. +Set `allow_manual_delegation = false` to hide the model-visible `task` and +`parallel_task` tools for cost control or debugging while preserving the child +agent registry for introspection and host-managed worker registration. This is +not a security sandbox: the parent agent may still use other registered tools, +MCP servers, or skills. --- diff --git a/core/src/agent_api.rs b/core/src/agent_api.rs index 0977e57..9f607dd 100644 --- a/core/src/agent_api.rs +++ b/core/src/agent_api.rs @@ -258,6 +258,12 @@ pub struct SessionOptions { pub max_parallel_tasks: Option, /// Per-session automatic subagent delegation override. pub auto_delegation: Option, + /// Per-session switch for model-visible manual child-agent tools. + /// + /// This overlays the effective automatic delegation config instead of + /// replacing it, so callers can hide `task` / `parallel_task` while + /// preserving other delegation settings. + pub manual_delegation_enabled: Option, /// Per-session kill switch for automatic parallel child-agent fan-out. /// /// This overlays the effective automatic delegation config instead of diff --git a/core/src/agent_api/capabilities.rs b/core/src/agent_api/capabilities.rs index 46287fc..1bb2507 100644 --- a/core/src/agent_api/capabilities.rs +++ b/core/src/agent_api/capabilities.rs @@ -18,8 +18,6 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::Arc; -const DISABLE_DELEGATION_TOOLS_ENV: &str = "A3S_CODE_DISABLE_DELEGATION_TOOLS"; - pub(super) struct SessionCapabilityInput<'a> { pub(super) code_config: &'a CodeConfig, pub(super) base_config: &'a AgentConfig, @@ -165,9 +163,7 @@ fn register_task_capability( use crate::tools::register_task_with_mcp; let registry = AgentRegistry::new(); - let disable_delegation_tools = disable_delegation_tools_from_env( - std::env::var(DISABLE_DELEGATION_TOOLS_ENV).ok().as_deref(), - ); + let auto_delegation = super::session_config::resolve_auto_delegation_config(code_config, opts); let built_in_agent_dirs = built_in_agent_dirs(workspace); for dir in code_config .agent_dirs @@ -183,7 +179,9 @@ fn register_task_capability( registry.register_worker(worker.clone()); } - if disable_delegation_tools { + if !auto_delegation.allow_manual_delegation { + // Keep the registry populated for introspection and host-managed worker + // registration even when the model-visible delegation tools are hidden. return Arc::new(registry); } @@ -212,17 +210,6 @@ fn register_task_capability( registry } -fn disable_delegation_tools_from_env(value: Option<&str>) -> bool { - value - .map(|value| { - matches!( - value.trim().to_ascii_lowercase().as_str(), - "1" | "true" | "yes" | "on" - ) - }) - .unwrap_or(false) -} - fn built_in_agent_dirs(workspace: &Path) -> Vec { let mut dirs = Vec::new(); if let Some(home) = std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE")) { @@ -285,26 +272,6 @@ fn group_mcp_tools_by_server(all_tools: Vec<(String, McpTool)>) -> HashMap SessionOptions { @@ -135,13 +137,7 @@ pub(super) fn build_agent_session( let init_warning = resolved_memory.init_warning; let base = agent.config.clone(); - let mut auto_delegation = opts - .auto_delegation - .clone() - .unwrap_or_else(|| base.auto_delegation.clone()); - if let Some(auto_parallel) = opts.auto_parallel_delegation { - auto_delegation.auto_parallel = auto_parallel; - } + let auto_delegation = resolve_auto_delegation_config(&agent.code_config, opts); let config = AgentConfig { prompt_slots, tools: tool_defs, diff --git a/core/src/agent_api/session_config.rs b/core/src/agent_api/session_config.rs index 9eb4ace..e4acbb0 100644 --- a/core/src/agent_api/session_config.rs +++ b/core/src/agent_api/session_config.rs @@ -5,6 +5,29 @@ use crate::llm::LlmClient; use anyhow::Context; use std::sync::Arc; +pub(super) fn resolve_auto_delegation_config( + code_config: &CodeConfig, + opts: &SessionOptions, +) -> crate::config::AutoDelegationConfig { + let mut auto_delegation = if let Some(config) = opts.auto_delegation.clone() { + config + } else { + let mut config = code_config.auto_delegation.clone(); + if let Some(auto_parallel) = code_config.auto_parallel { + config.auto_parallel = auto_parallel; + } + config + }; + if let Some(enabled) = opts.manual_delegation_enabled { + auto_delegation.allow_manual_delegation = enabled; + } + if let Some(auto_parallel) = opts.auto_parallel_delegation { + auto_delegation.auto_parallel = auto_parallel; + } + + auto_delegation +} + pub(super) fn resolve_session_llm_client( code_config: &CodeConfig, opts: &SessionOptions, diff --git a/core/src/agent_api/session_options.rs b/core/src/agent_api/session_options.rs index 0ce84bc..9d0a45f 100644 --- a/core/src/agent_api/session_options.rs +++ b/core/src/agent_api/session_options.rs @@ -54,6 +54,7 @@ impl std::fmt::Debug for SessionOptions { .field("max_tool_rounds", &self.max_tool_rounds) .field("max_parallel_tasks", &self.max_parallel_tasks) .field("auto_delegation", &self.auto_delegation) + .field("manual_delegation_enabled", &self.manual_delegation_enabled) .field("auto_parallel_delegation", &self.auto_parallel_delegation) .field("prompt_slots", &self.prompt_slots.is_some()) .finish() @@ -494,6 +495,20 @@ impl SessionOptions { self } + /// Enable or disable model-visible manual child-agent tools for this session. + /// + /// When false, `task` and `parallel_task` are not registered in the session + /// tool surface. Worker agents remain registered for introspection and hosts + /// that manage them directly. This is for cost control or debugging; it is + /// not a security sandbox for the parent agent. + pub fn with_manual_delegation_enabled(mut self, enabled: bool) -> Self { + if let Some(config) = &mut self.auto_delegation { + config.allow_manual_delegation = enabled; + } + self.manual_delegation_enabled = Some(enabled); + self + } + /// Globally enable or disable automatic parallel child-agent fan-out. /// /// Manual `parallel_task` calls remain available when this is false. diff --git a/core/src/agent_api/tests.rs b/core/src/agent_api/tests.rs index 2b42621..cbbd233 100644 --- a/core/src/agent_api/tests.rs +++ b/core/src/agent_api/tests.rs @@ -1807,6 +1807,26 @@ async fn test_session_uses_single_delegation_tool_surface() { assert!(!names.contains(&"run_team".to_string())); } +#[tokio::test] +async fn test_session_can_disable_manual_delegation_tools_without_dropping_registry() { + let agent = Agent::from_config(test_config()).await.unwrap(); + let opts = SessionOptions::new() + .with_worker_agent(crate::subagent::WorkerAgentSpec::planner( + "release-planner", + "Plan releases", + )) + .with_manual_delegation_enabled(false); + let session = agent + .session("/tmp/test-workspace-no-manual-delegation", Some(opts)) + .unwrap(); + let names = session.tool_names(); + + assert!(!names.contains(&"task".to_string())); + assert!(!names.contains(&"parallel_task".to_string())); + assert!(session.agent_registry.exists("release-planner")); + assert!(!session.config.auto_delegation.allow_manual_delegation); +} + #[tokio::test(flavor = "multi_thread")] async fn test_session_with_queue_config() { let agent = Agent::from_config(test_config()).await.unwrap(); @@ -2621,13 +2641,16 @@ async fn test_session_options_builders() { .with_auto_save(true) .with_max_parallel_tasks(3) .with_auto_delegation_enabled(true) + .with_manual_delegation_enabled(false) .with_auto_parallel_delegation(false); assert_eq!(opts.session_id, Some("test-id".to_string())); assert!(opts.auto_save); assert_eq!(opts.max_parallel_tasks, Some(3)); + assert_eq!(opts.manual_delegation_enabled, Some(false)); assert_eq!(opts.auto_parallel_delegation, Some(false)); let auto = opts.auto_delegation.expect("auto delegation config"); assert!(auto.enabled); + assert!(!auto.allow_manual_delegation); assert!(!auto.auto_parallel); } diff --git a/core/src/config/loader.rs b/core/src/config/loader.rs index 85a987f..79618c8 100644 --- a/core/src/config/loader.rs +++ b/core/src/config/loader.rs @@ -72,6 +72,17 @@ fn parse_auto_delegation_block( { config.auto_parallel = auto_parallel; } + if let Some(allow_manual_delegation) = acl_bool_attr( + block, + &[ + "allow_manual_delegation", + "allowManualDelegation", + "manual_delegation", + "manualDelegation", + ], + ) { + config.allow_manual_delegation = allow_manual_delegation; + } if let Some(min_confidence) = acl_f32_attr(block, &["min_confidence", "minConfidence"]) { config.min_confidence = min_confidence.clamp(0.0, 1.0); } diff --git a/core/src/config/mod.rs b/core/src/config/mod.rs index 9a7079a..d02dcca 100644 --- a/core/src/config/mod.rs +++ b/core/src/config/mod.rs @@ -61,6 +61,14 @@ pub struct AutoDelegationConfig { /// Manual `parallel_task` calls remain available when this is false. #[serde(alias = "auto_parallel")] pub auto_parallel: bool, + /// Allow model-visible manual `task` and `parallel_task` delegation tools. + /// + /// Set this to false for cost control or debugging when child-agent tools + /// should be absent from the session tool surface. This is not a security + /// sandbox: the parent agent may still have other tools such as `bash`, + /// MCP tools, or skills. + #[serde(alias = "allow_manual_delegation")] + pub allow_manual_delegation: bool, /// Minimum local confidence required to auto-delegate a child task. pub min_confidence: f32, /// Maximum number of automatic child tasks per user request. @@ -72,6 +80,7 @@ impl Default for AutoDelegationConfig { Self { enabled: false, auto_parallel: true, + allow_manual_delegation: true, min_confidence: 0.72, max_tasks: 4, } diff --git a/core/src/config/tests.rs b/core/src/config/tests.rs index f3ed62b..11abf0f 100644 --- a/core/src/config/tests.rs +++ b/core/src/config/tests.rs @@ -54,6 +54,7 @@ fn test_config_with_storage_backend() { auto_delegation { enabled = true auto_parallel = true + allow_manual_delegation = false min_confidence = 0.8 max_tasks = 2 } @@ -67,6 +68,7 @@ fn test_config_with_storage_backend() { assert_eq!(config.max_parallel_tasks, Some(3)); assert!(config.auto_delegation.enabled); assert!(!config.auto_delegation.auto_parallel); + assert!(!config.auto_delegation.allow_manual_delegation); assert!((config.auto_delegation.min_confidence - 0.8).abs() < f32::EPSILON); assert_eq!(config.auto_delegation.max_tasks, 2); }