From 50d404c616181ee46780d28926f018abafc71be9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 25 Jul 2026 11:11:24 +0800 Subject: [PATCH] fix(agent): avoid fabricated fallback plans --- CHANGELOG.md | 6 +++ core/prompts/planning/plan_fallback_step.md | 1 - core/src/agent/execution_mode.rs | 6 +-- core/src/agent/extra_agent_tests.rs | 17 +++++++ core/src/planning/llm_planner.rs | 55 +++++++++------------ core/src/prompts.rs | 3 -- core/src/prompts/tests.rs | 1 - 7 files changed, 49 insertions(+), 40 deletions(-) delete mode 100644 core/prompts/planning/plan_fallback_step.md diff --git a/CHANGELOG.md b/CHANGELOG.md index a56c6d44..0fb75192 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Kept Auto execution direct when structured pre-analysis is unavailable, and + replaced fabricated numbered fallback tasks with one step containing the + original request when planning is explicitly enabled. + ## [6.4.2] - 2026-07-23 ### Changed diff --git a/core/prompts/planning/plan_fallback_step.md b/core/prompts/planning/plan_fallback_step.md deleted file mode 100644 index f97a5e57..00000000 --- a/core/prompts/planning/plan_fallback_step.md +++ /dev/null @@ -1 +0,0 @@ -Execute step {step_num} of the plan diff --git a/core/src/agent/execution_mode.rs b/core/src/agent/execution_mode.rs index 03bd00e6..40cf40eb 100644 --- a/core/src/agent/execution_mode.rs +++ b/core/src/agent/execution_mode.rs @@ -227,9 +227,9 @@ impl AgentLoop { style } - fn resolve_planning_decision( + pub(super) fn resolve_planning_decision( &self, - style: AgentStyle, + _style: AgentStyle, pre_analysis: Option<&PreAnalysis>, ) -> bool { match self.config.planning_mode { @@ -237,7 +237,7 @@ impl AgentLoop { PlanningMode::Enabled => true, PlanningMode::Auto => pre_analysis .map(|analysis| analysis.requires_planning) - .unwrap_or_else(|| style.requires_planning()), + .unwrap_or(false), } } diff --git a/core/src/agent/extra_agent_tests.rs b/core/src/agent/extra_agent_tests.rs index 55a8cb5a..d2e059ab 100644 --- a/core/src/agent/extra_agent_tests.rs +++ b/core/src/agent/extra_agent_tests.rs @@ -610,6 +610,23 @@ fn test_disabled_planning_never_runs_pre_analysis() { assert!(!agent.should_run_pre_analysis()); } +#[test] +fn auto_mode_does_not_fabricate_a_plan_when_pre_analysis_is_unavailable() { + let mock_client = Arc::new(MockLlmClient::new(vec![])); + let tool_executor = Arc::new(ToolExecutor::new("/tmp".to_string())); + let agent = AgentLoop::new( + mock_client, + tool_executor, + test_tool_context(), + AgentConfig::default(), + ); + + assert!( + !agent.resolve_planning_decision(crate::prompts::AgentStyle::Plan, None), + "Auto must fall back to direct execution when structured pre-analysis failed" + ); +} + #[derive(Debug)] struct PlanningHookRecorder { events: Arc>>, diff --git a/core/src/planning/llm_planner.rs b/core/src/planning/llm_planner.rs index 7cda96d3..92bd4ace 100644 --- a/core/src/planning/llm_planner.rs +++ b/core/src/planning/llm_planner.rs @@ -152,37 +152,18 @@ impl LlmPlanner { Self::achievement_from_value(result.object) } - /// Create a fallback plan using heuristic logic (no LLM required) + /// Create a minimal fallback plan when explicit planning cannot use the LLM. + /// + /// The original request is the only honest executable step available here. + /// Fabricating numbered placeholder steps makes the task tracker look active + /// without conveying useful progress. pub fn fallback_plan(prompt: &str) -> ExecutionPlan { - let complexity = if prompt.len() < 50 { - Complexity::Simple - } else if prompt.len() < 150 { - Complexity::Medium - } else if prompt.len() < 300 { - Complexity::Complex - } else { - Complexity::VeryComplex - }; - - let mut plan = ExecutionPlan::new(prompt, complexity); - - let step_count = match complexity { - Complexity::Simple => 2, - Complexity::Medium => 4, - Complexity::Complex => 7, - Complexity::VeryComplex => 10, + let content = match prompt.trim() { + "" => "Complete the requested task", + prompt => prompt, }; - - for i in 0..step_count { - let step = Task::new( - format!("step-{}", i + 1), - crate::prompts::render( - crate::prompts::PLAN_FALLBACK_STEP, - &[("step_num", &(i + 1).to_string())], - ), - ); - plan.add_step(step); - } + let mut plan = ExecutionPlan::new(content, Complexity::Simple); + plan.add_step(Task::new("step-1", content)); plan } @@ -579,13 +560,23 @@ mod tests { let short_prompt = "Fix bug"; let plan = LlmPlanner::fallback_plan(short_prompt); assert_eq!(plan.complexity, Complexity::Simple); - assert_eq!(plan.steps.len(), 2); + assert_eq!(plan.steps.len(), 1); assert_eq!(plan.goal, short_prompt); + assert_eq!(plan.steps[0].content, short_prompt); let long_prompt = "Implement a comprehensive authentication system with OAuth2 support, JWT tokens, refresh token rotation, multi-factor authentication, and role-based access control across all API endpoints with proper audit logging and session management capabilities for both web and mobile clients, including password reset flows, account lockout policies, and integration with external identity providers such as Google, GitHub, and SAML-based enterprise SSO systems"; let plan = LlmPlanner::fallback_plan(long_prompt); - assert_eq!(plan.complexity, Complexity::VeryComplex); - assert_eq!(plan.steps.len(), 10); + assert_eq!(plan.complexity, Complexity::Simple); + assert_eq!(plan.steps.len(), 1); + assert_eq!(plan.steps[0].content, long_prompt); + assert!( + !plan.steps[0].content.contains("Execute step"), + "fallback plans must not expose placeholder task text" + ); + + let plan = LlmPlanner::fallback_plan(" "); + assert_eq!(plan.goal, "Complete the requested task"); + assert_eq!(plan.steps[0].content, "Complete the requested task"); } #[test] diff --git a/core/src/prompts.rs b/core/src/prompts.rs index 40187457..a6a652ad 100644 --- a/core/src/prompts.rs +++ b/core/src/prompts.rs @@ -87,9 +87,6 @@ pub const PLAN_EXECUTE_GOAL: &str = include_str!("../prompts/planning/plan_execu /// Template for per-step execution prompt pub const PLAN_EXECUTE_STEP: &str = include_str!("../prompts/planning/plan_execute_step.md"); -/// Template for fallback plan step description -pub const PLAN_FALLBACK_STEP: &str = include_str!("../prompts/planning/plan_fallback_step.md"); - /// Skill catalog header injected before listing available skill names/descriptions. pub const SKILLS_CATALOG_HEADER: &str = include_str!("../prompts/common/skills_catalog_header.md"); diff --git a/core/src/prompts/tests.rs b/core/src/prompts/tests.rs index 17c1ad87..2a3ce31c 100644 --- a/core/src/prompts/tests.rs +++ b/core/src/prompts/tests.rs @@ -16,7 +16,6 @@ fn test_all_prompts_loaded() { assert!(!SKILLS_CATALOG_HEADER.is_empty()); assert!(!PLAN_EXECUTE_GOAL.is_empty()); assert!(!PLAN_EXECUTE_STEP.is_empty()); - assert!(!PLAN_FALLBACK_STEP.is_empty()); } #[test]