🚀 Major Enhancement: Workflow-Based Tool Architecture v5.5.0 (#95)

* WIP: new workflow architecture

* WIP: further improvements and cleanup

* WIP: cleanup and docks, replace old tool with new

* WIP: cleanup and docks, replace old tool with new

* WIP: new planner implementation using workflow

* WIP: precommit tool working as a workflow instead of a basic tool
Support for passing False to use_assistant_model to skip external models completely and use Claude only

* WIP: precommit workflow version swapped with old

* WIP: codereview

* WIP: replaced codereview

* WIP: replaced codereview

* WIP: replaced refactor

* WIP: workflow for thinkdeep

* WIP: ensure files get embedded correctly

* WIP: thinkdeep replaced with workflow version

* WIP: improved messaging when an external model's response is received

* WIP: analyze tool swapped

* WIP: updated tests
* Extract only the content when building history
* Use "relevant_files" for workflow tools only

* WIP: updated tests
* Extract only the content when building history
* Use "relevant_files" for workflow tools only

* WIP: fixed get_completion_next_steps_message missing param

* Fixed tests
Request for files consistently

* Fixed tests
Request for files consistently

* Fixed tests

* New testgen workflow tool
Updated docs

* Swap testgen workflow

* Fix CI test failures by excluding API-dependent tests

- Update GitHub Actions workflow to exclude simulation tests that require API keys
- Fix collaboration tests to properly mock workflow tool expert analysis calls
- Update test assertions to handle new workflow tool response format
- Ensure unit tests run without external API dependencies in CI

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* WIP - Update tests to match new tools

* WIP - Update tests to match new tools

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Beehive Innovations
2025-06-21 00:08:11 +04:00
committed by GitHub
parent 4dae6e457e
commit 69a3121452
76 changed files with 17111 additions and 7725 deletions

View File

@@ -1,80 +1,43 @@
"""
Planner tool
Interactive Sequential Planner - Break down complex tasks through step-by-step planning
This tool helps you break down complex ideas, problems, or projects into multiple
manageable steps. It enables Claude to think through larger problems sequentially, creating
detailed action plans with clear dependencies and alternatives where applicable.
This tool enables structured planning through an interactive, step-by-step process that builds
plans incrementally with the ability to revise, branch, and adapt as understanding deepens.
=== CONTINUATION FLOW LOGIC ===
The planner guides users through sequential thinking with forced pauses between steps to ensure
thorough consideration of alternatives, dependencies, and strategic decisions before moving to
tactical implementation details.
The tool implements sophisticated continuation logic that enables multi-session planning:
Key features:
- Sequential planning with full context awareness
- Forced deep reflection for complex plans (≥5 steps) in early stages
- Branching capabilities for exploring alternative approaches
- Revision capabilities to update earlier decisions
- Dynamic step count adjustment as plans evolve
- Self-contained completion without external expert analysis
RULE 1: No continuation_id + step_number=1
→ Creates NEW planning thread
→ NO previous context loaded
→ Returns continuation_id for future steps
RULE 2: continuation_id provided + step_number=1
→ Loads PREVIOUS COMPLETE PLAN as context
→ Starts NEW planning session with historical context
→ Claude sees summary of previous completed plan
RULE 3: continuation_id provided + step_number>1
→ NO previous context loaded (middle of current planning session)
→ Continues current planning without historical interference
RULE 4: next_step_required=false (final step)
→ Stores COMPLETE PLAN summary in conversation memory
→ Returns continuation_id for future planning sessions
=== CONCRETE EXAMPLE ===
FIRST PLANNING SESSION (Feature A):
Call 1: planner(step="Plan user authentication", step_number=1, total_steps=3, next_step_required=true)
→ NEW thread created: "uuid-abc123"
→ Response: {"step_number": 1, "continuation_id": "uuid-abc123"}
Call 2: planner(step="Design login flow", step_number=2, total_steps=3, next_step_required=true, continuation_id="uuid-abc123")
→ Middle of current plan - NO context loading
→ Response: {"step_number": 2, "continuation_id": "uuid-abc123"}
Call 3: planner(step="Security implementation", step_number=3, total_steps=3, next_step_required=FALSE, continuation_id="uuid-abc123")
→ FINAL STEP: Stores "COMPLETE PLAN: Security implementation (3 steps completed)"
→ Response: {"step_number": 3, "planning_complete": true, "continuation_id": "uuid-abc123"}
LATER PLANNING SESSION (Feature B):
Call 1: planner(step="Plan dashboard system", step_number=1, total_steps=2, next_step_required=true, continuation_id="uuid-abc123")
→ Loads previous complete plan as context
→ Response includes: "=== PREVIOUS COMPLETE PLAN CONTEXT === Security implementation..."
→ Claude sees previous work and can build upon it
Call 2: planner(step="Dashboard widgets", step_number=2, total_steps=2, next_step_required=FALSE, continuation_id="uuid-abc123")
→ FINAL STEP: Stores new complete plan summary
→ Both planning sessions now available for future continuations
This enables Claude to say: "Continue planning feature C using the authentication and dashboard work"
and the tool will provide context from both previous completed planning sessions.
Perfect for: complex project planning, system design with unknowns, migration strategies,
architectural decisions, and breaking down large problems into manageable steps.
"""
import json
import logging
from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
from pydantic import Field, field_validator
if TYPE_CHECKING:
from tools.models import ToolModelCategory
from config import TEMPERATURE_BALANCED
from systemprompts import PLANNER_PROMPT
from tools.shared.base_models import WorkflowRequest
from .base import BaseTool, ToolRequest
from .workflow.base import WorkflowTool
logger = logging.getLogger(__name__)
# Field descriptions to avoid duplication between Pydantic and JSON schema
# Tool-specific field descriptions matching original planner tool
PLANNER_FIELD_DESCRIPTIONS = {
# Interactive planning fields for step-by-step planning
"step": (
"Your current planning step. For the first step, describe the task/problem to plan and be extremely expressive "
"so that subsequent steps can break this down into simpler steps. "
@@ -91,25 +54,11 @@ PLANNER_FIELD_DESCRIPTIONS = {
"branch_from_step": "If is_branch_point is true, which step number is the branching point",
"branch_id": "Identifier for the current branch (e.g., 'approach-A', 'microservices-path')",
"more_steps_needed": "True if more steps are needed beyond the initial estimate",
"continuation_id": "Thread continuation ID for multi-turn planning sessions (useful for seeding new plans with prior context)",
}
class PlanStep:
"""Represents a single step in the planning process."""
def __init__(
self, step_number: int, content: str, branch_id: Optional[str] = None, parent_step: Optional[int] = None
):
self.step_number = step_number
self.content = content
self.branch_id = branch_id or "main"
self.parent_step = parent_step
self.children = []
class PlannerRequest(ToolRequest):
"""Request model for the planner tool - interactive step-by-step planning."""
class PlannerRequest(WorkflowRequest):
"""Request model for planner workflow tool matching original planner exactly"""
# Required fields for each planning step
step: str = Field(..., description=PLANNER_FIELD_DESCRIPTIONS["step"])
@@ -117,7 +66,7 @@ class PlannerRequest(ToolRequest):
total_steps: int = Field(..., description=PLANNER_FIELD_DESCRIPTIONS["total_steps"])
next_step_required: bool = Field(..., description=PLANNER_FIELD_DESCRIPTIONS["next_step_required"])
# Optional revision/branching fields
# Optional revision/branching fields (planning-specific)
is_step_revision: Optional[bool] = Field(False, description=PLANNER_FIELD_DESCRIPTIONS["is_step_revision"])
revises_step_number: Optional[int] = Field(None, description=PLANNER_FIELD_DESCRIPTIONS["revises_step_number"])
is_branch_point: Optional[bool] = Field(False, description=PLANNER_FIELD_DESCRIPTIONS["is_branch_point"])
@@ -125,23 +74,58 @@ class PlannerRequest(ToolRequest):
branch_id: Optional[str] = Field(None, description=PLANNER_FIELD_DESCRIPTIONS["branch_id"])
more_steps_needed: Optional[bool] = Field(False, description=PLANNER_FIELD_DESCRIPTIONS["more_steps_needed"])
# Optional continuation field
continuation_id: Optional[str] = Field(None, description=PLANNER_FIELD_DESCRIPTIONS["continuation_id"])
# Exclude all investigation/analysis fields that aren't relevant to planning
findings: str = Field(
default="", exclude=True, description="Not used for planning - step content serves as findings"
)
files_checked: list[str] = Field(default_factory=list, exclude=True, description="Planning doesn't examine files")
relevant_files: list[str] = Field(default_factory=list, exclude=True, description="Planning doesn't use files")
relevant_context: list[str] = Field(
default_factory=list, exclude=True, description="Planning doesn't track code context"
)
issues_found: list[dict] = Field(default_factory=list, exclude=True, description="Planning doesn't find issues")
confidence: str = Field(default="planning", exclude=True, description="Planning uses different confidence model")
hypothesis: Optional[str] = Field(default=None, exclude=True, description="Planning doesn't use hypothesis")
backtrack_from_step: Optional[int] = Field(default=None, exclude=True, description="Planning uses revision instead")
# Override inherited fields to exclude them from schema
model: Optional[str] = Field(default=None, exclude=True)
# Exclude other non-planning fields
temperature: Optional[float] = Field(default=None, exclude=True)
thinking_mode: Optional[str] = Field(default=None, exclude=True)
use_websearch: Optional[bool] = Field(default=None, exclude=True)
images: Optional[list] = Field(default=None, exclude=True)
use_assistant_model: Optional[bool] = Field(default=False, exclude=True, description="Planning is self-contained")
images: Optional[list] = Field(default=None, exclude=True, description="Planning doesn't use images")
@field_validator("step_number")
@classmethod
def validate_step_number(cls, v):
if v < 1:
raise ValueError("step_number must be at least 1")
return v
@field_validator("total_steps")
@classmethod
def validate_total_steps(cls, v):
if v < 1:
raise ValueError("total_steps must be at least 1")
return v
class PlannerTool(BaseTool):
"""Sequential planning tool with step-by-step breakdown and refinement."""
class PlannerTool(WorkflowTool):
"""
Planner workflow tool for step-by-step planning using the workflow architecture.
This tool provides the same planning capabilities as the original planner tool
but uses the new workflow architecture for consistency with other workflow tools.
It maintains all the original functionality including:
- Sequential step-by-step planning
- Branching and revision capabilities
- Deep thinking pauses for complex plans
- Conversation memory integration
- Self-contained operation (no expert analysis)
"""
def __init__(self):
super().__init__()
self.step_history = []
self.branches = {}
def get_name(self) -> str:
@@ -172,351 +156,381 @@ class PlannerTool(BaseTool):
"migration strategies, architectural decisions, problem decomposition."
)
def get_input_schema(self) -> dict[str, Any]:
schema = {
"type": "object",
"properties": {
# Interactive planning fields
"step": {
"type": "string",
"description": PLANNER_FIELD_DESCRIPTIONS["step"],
},
"step_number": {
"type": "integer",
"description": PLANNER_FIELD_DESCRIPTIONS["step_number"],
"minimum": 1,
},
"total_steps": {
"type": "integer",
"description": PLANNER_FIELD_DESCRIPTIONS["total_steps"],
"minimum": 1,
},
"next_step_required": {
"type": "boolean",
"description": PLANNER_FIELD_DESCRIPTIONS["next_step_required"],
},
"is_step_revision": {
"type": "boolean",
"description": PLANNER_FIELD_DESCRIPTIONS["is_step_revision"],
},
"revises_step_number": {
"type": "integer",
"description": PLANNER_FIELD_DESCRIPTIONS["revises_step_number"],
"minimum": 1,
},
"is_branch_point": {
"type": "boolean",
"description": PLANNER_FIELD_DESCRIPTIONS["is_branch_point"],
},
"branch_from_step": {
"type": "integer",
"description": PLANNER_FIELD_DESCRIPTIONS["branch_from_step"],
"minimum": 1,
},
"branch_id": {
"type": "string",
"description": PLANNER_FIELD_DESCRIPTIONS["branch_id"],
},
"more_steps_needed": {
"type": "boolean",
"description": PLANNER_FIELD_DESCRIPTIONS["more_steps_needed"],
},
"continuation_id": {
"type": "string",
"description": PLANNER_FIELD_DESCRIPTIONS["continuation_id"],
},
},
# Required fields for interactive planning
"required": ["step", "step_number", "total_steps", "next_step_required"],
}
return schema
def get_system_prompt(self) -> str:
return PLANNER_PROMPT
def get_request_model(self):
return PlannerRequest
def get_default_temperature(self) -> float:
return TEMPERATURE_BALANCED
def get_model_category(self) -> "ToolModelCategory":
"""Planner requires deep analysis and reasoning"""
from tools.models import ToolModelCategory
return ToolModelCategory.EXTENDED_REASONING # Planning benefits from deep thinking
def get_default_thinking_mode(self) -> str:
return "high" # Default to high thinking for comprehensive planning
return ToolModelCategory.EXTENDED_REASONING
def requires_model(self) -> bool:
"""
Planner tool doesn't require AI model access - it's pure data processing.
Planner tool doesn't require model resolution at the MCP boundary.
This prevents the server from trying to resolve model names like "auto"
when the planner tool is used, since it overrides execute() and doesn't
make any AI API calls.
The planner is a pure data processing tool that organizes planning steps
and provides structured guidance without calling external AI models.
Returns:
bool: False - planner doesn't need AI model access
"""
return False
async def execute(self, arguments: dict[str, Any]) -> list:
def get_workflow_request_model(self):
"""Return the planner-specific request model."""
return PlannerRequest
def get_tool_fields(self) -> dict[str, dict[str, Any]]:
"""Return planning-specific field definitions beyond the standard workflow fields."""
return {
# Planning-specific optional fields
"is_step_revision": {
"type": "boolean",
"description": PLANNER_FIELD_DESCRIPTIONS["is_step_revision"],
},
"revises_step_number": {
"type": "integer",
"minimum": 1,
"description": PLANNER_FIELD_DESCRIPTIONS["revises_step_number"],
},
"is_branch_point": {
"type": "boolean",
"description": PLANNER_FIELD_DESCRIPTIONS["is_branch_point"],
},
"branch_from_step": {
"type": "integer",
"minimum": 1,
"description": PLANNER_FIELD_DESCRIPTIONS["branch_from_step"],
},
"branch_id": {
"type": "string",
"description": PLANNER_FIELD_DESCRIPTIONS["branch_id"],
},
"more_steps_needed": {
"type": "boolean",
"description": PLANNER_FIELD_DESCRIPTIONS["more_steps_needed"],
},
}
def get_input_schema(self) -> dict[str, Any]:
"""Generate input schema using WorkflowSchemaBuilder with field exclusion."""
from .workflow.schema_builders import WorkflowSchemaBuilder
# Exclude investigation-specific fields that planning doesn't need
excluded_workflow_fields = [
"findings", # Planning uses step content instead
"files_checked", # Planning doesn't examine files
"relevant_files", # Planning doesn't use files
"relevant_context", # Planning doesn't track code context
"issues_found", # Planning doesn't find issues
"confidence", # Planning uses different confidence model
"hypothesis", # Planning doesn't use hypothesis
"backtrack_from_step", # Planning uses revision instead
]
# Exclude common fields that planning doesn't need
excluded_common_fields = [
"temperature", # Planning doesn't need temperature control
"thinking_mode", # Planning doesn't need thinking mode
"use_websearch", # Planning doesn't need web search
"images", # Planning doesn't use images
"files", # Planning doesn't use files
]
return WorkflowSchemaBuilder.build_schema(
tool_specific_fields=self.get_tool_fields(),
required_fields=[], # No additional required fields beyond workflow defaults
model_field_schema=self.get_model_field_schema(),
auto_mode=self.is_effective_auto_mode(),
tool_name=self.get_name(),
excluded_workflow_fields=excluded_workflow_fields,
excluded_common_fields=excluded_common_fields,
)
# ================================================================================
# Abstract Methods - Required Implementation from BaseWorkflowMixin
# ================================================================================
def get_required_actions(self, step_number: int, confidence: str, findings: str, total_steps: int) -> list[str]:
"""Define required actions for each planning phase."""
if step_number == 1:
# Initial planning tasks
return [
"Think deeply about the complete scope and complexity of what needs to be planned",
"Consider multiple approaches and their trade-offs",
"Identify key constraints, dependencies, and potential challenges",
"Think about stakeholders, success criteria, and critical requirements",
]
elif step_number <= 3 and total_steps >= 5:
# Complex plan early stages - force deep thinking
if step_number == 2:
return [
"Evaluate the approach from step 1 - are there better alternatives?",
"Break down the major phases and identify critical decision points",
"Consider resource requirements and potential bottlenecks",
"Think about how different parts interconnect and affect each other",
]
else: # step_number == 3
return [
"Validate that the emerging plan addresses the original requirements",
"Identify any gaps or assumptions that need clarification",
"Consider how to validate progress and adjust course if needed",
"Think about what the first concrete steps should be",
]
else:
# Later steps or simple plans
return [
"Continue developing the plan with concrete, actionable steps",
"Consider implementation details and practical considerations",
"Think about how to sequence and coordinate different activities",
"Prepare for execution planning and resource allocation",
]
def should_call_expert_analysis(self, consolidated_findings, request=None) -> bool:
"""Planner is self-contained and doesn't need expert analysis."""
return False
def prepare_expert_analysis_context(self, consolidated_findings) -> str:
"""Planner doesn't use expert analysis."""
return ""
def requires_expert_analysis(self) -> bool:
"""Planner is self-contained like the original planner tool."""
return False
# ================================================================================
# Workflow Customization - Match Original Planner Behavior
# ================================================================================
def prepare_step_data(self, request) -> dict:
"""
Override execute to work like original TypeScript tool - no AI calls, just data processing.
This method implements the core continuation logic that enables multi-session planning:
CONTINUATION LOGIC:
1. If no continuation_id + step_number=1: Create new planning thread
2. If continuation_id + step_number=1: Load previous complete plan as context for NEW planning
3. If continuation_id + step_number>1: Continue current plan (no context loading)
4. If next_step_required=false: Mark complete and store plan summary for future use
CONVERSATION MEMORY INTEGRATION:
- Each step is stored in conversation memory for cross-tool continuation
- Final steps store COMPLETE PLAN summaries that can be loaded as context
- Only step 1 with continuation_id loads previous context (new planning session)
- Steps 2+ with continuation_id continue current session without context interference
Prepare step data from request with planner-specific fields.
"""
from mcp.types import TextContent
step_data = {
"step": request.step,
"step_number": request.step_number,
"findings": f"Planning step {request.step_number}: {request.step}", # Use step content as findings
"files_checked": [], # Planner doesn't check files
"relevant_files": [], # Planner doesn't use files
"relevant_context": [], # Planner doesn't track context like debug
"issues_found": [], # Planner doesn't track issues
"confidence": "planning", # Planning confidence is different from investigation
"hypothesis": None, # Planner doesn't use hypothesis
"images": [], # Planner doesn't use images
# Planner-specific fields
"is_step_revision": request.is_step_revision or False,
"revises_step_number": request.revises_step_number,
"is_branch_point": request.is_branch_point or False,
"branch_from_step": request.branch_from_step,
"branch_id": request.branch_id,
"more_steps_needed": request.more_steps_needed or False,
}
return step_data
from utils.conversation_memory import add_turn, create_thread, get_thread
def build_base_response(self, request, continuation_id: str = None) -> dict:
"""
Build the base response structure with planner-specific fields.
"""
# Use work_history from workflow mixin for consistent step tracking
# Add 1 to account for current step being processed
current_step_count = len(self.work_history) + 1
try:
# Validate request like the original
request_model = self.get_request_model()
request = request_model(**arguments)
# Process step like original TypeScript tool
if request.step_number > request.total_steps:
request.total_steps = request.step_number
# === CONTINUATION LOGIC IMPLEMENTATION ===
# This implements the 4 rules documented in the module docstring
continuation_id = request.continuation_id
previous_plan_context = ""
# RULE 1: No continuation_id + step_number=1 → Create NEW planning thread
if not continuation_id and request.step_number == 1:
# Filter arguments to only include serializable data for conversation memory
serializable_args = {
k: v
for k, v in arguments.items()
if not hasattr(v, "__class__") or v.__class__.__module__ != "utils.model_context"
}
continuation_id = create_thread("planner", serializable_args)
# Result: New thread created, no previous context, returns continuation_id
# RULE 2: continuation_id + step_number=1 → Load PREVIOUS COMPLETE PLAN as context
elif continuation_id and request.step_number == 1:
thread = get_thread(continuation_id)
if thread:
# Search for most recent COMPLETE PLAN from previous planning sessions
for turn in reversed(thread.turns): # Newest first
if turn.tool_name == "planner" and turn.role == "assistant":
# Try to parse as JSON first (new format)
try:
turn_data = json.loads(turn.content)
if isinstance(turn_data, dict) and turn_data.get("planning_complete"):
# New JSON format
plan_summary = turn_data.get("plan_summary", "")
if plan_summary:
previous_plan_context = plan_summary[:500]
break
except (json.JSONDecodeError, ValueError):
# Fallback to old text format
if "planning_complete" in turn.content:
try:
if "COMPLETE PLAN:" in turn.content:
plan_start = turn.content.find("COMPLETE PLAN:")
previous_plan_context = turn.content[plan_start : plan_start + 500] + "..."
else:
previous_plan_context = turn.content[:300] + "..."
break
except Exception:
pass
if previous_plan_context:
previous_plan_context = f"\\n\\n=== PREVIOUS COMPLETE PLAN CONTEXT ===\\n{previous_plan_context}\\n=== END CONTEXT ===\\n"
# Result: NEW planning session with previous complete plan as context
# RULE 3: continuation_id + step_number>1 → Continue current plan (no context loading)
# This case is handled by doing nothing - we're in the middle of current planning
# Result: Current planning continues without historical interference
step_data = {
"step": request.step,
"step_number": request.step_number,
"total_steps": request.total_steps,
"next_step_required": request.next_step_required,
"is_step_revision": request.is_step_revision,
response_data = {
"status": f"{self.get_name()}_in_progress",
"step_number": request.step_number,
"total_steps": request.total_steps,
"next_step_required": request.next_step_required,
"step_content": request.step,
f"{self.get_name()}_status": {
"files_checked": len(self.consolidated_findings.files_checked),
"relevant_files": len(self.consolidated_findings.relevant_files),
"relevant_context": len(self.consolidated_findings.relevant_context),
"issues_found": len(self.consolidated_findings.issues_found),
"images_collected": len(self.consolidated_findings.images),
"current_confidence": self.get_request_confidence(request),
"step_history_length": current_step_count, # Use work_history + current step
},
"metadata": {
"branches": list(self.branches.keys()),
"step_history_length": current_step_count, # Use work_history + current step
"is_step_revision": request.is_step_revision or False,
"revises_step_number": request.revises_step_number,
"is_branch_point": request.is_branch_point,
"is_branch_point": request.is_branch_point or False,
"branch_from_step": request.branch_from_step,
"branch_id": request.branch_id,
"more_steps_needed": request.more_steps_needed,
"continuation_id": request.continuation_id,
}
"more_steps_needed": request.more_steps_needed or False,
},
}
# Store in local history like original
self.step_history.append(step_data)
if continuation_id:
response_data["continuation_id"] = continuation_id
# Handle branching like original
if request.is_branch_point and request.branch_from_step and request.branch_id:
if request.branch_id not in self.branches:
self.branches[request.branch_id] = []
self.branches[request.branch_id].append(step_data)
return response_data
# Build structured JSON response like other tools (consensus, refactor)
response_data = {
"status": "planning_success",
"step_number": request.step_number,
"total_steps": request.total_steps,
"next_step_required": request.next_step_required,
"step_content": request.step,
"metadata": {
"branches": list(self.branches.keys()),
"step_history_length": len(self.step_history),
"is_step_revision": request.is_step_revision or False,
"revises_step_number": request.revises_step_number,
"is_branch_point": request.is_branch_point or False,
"branch_from_step": request.branch_from_step,
"branch_id": request.branch_id,
"more_steps_needed": request.more_steps_needed or False,
},
"output": {
"instructions": "This is a structured planning response. Present the step_content as the main planning analysis. If next_step_required is true, continue with the next step. If planning_complete is true, present the complete plan in a well-structured format with clear sections, headings, numbered steps, and visual elements like ASCII charts for phases/dependencies. Use bullet points, sub-steps, sequences, and visual organization to make complex plans easy to understand and follow. IMPORTANT: Do NOT use emojis - use clear text formatting and ASCII characters only. Do NOT mention time estimates or costs unless explicitly requested.",
"format": "step_by_step_planning",
"presentation_guidelines": {
"completed_plans": "Use clear headings, numbered phases, ASCII diagrams for workflows/dependencies, bullet points for sub-tasks, and visual sequences where helpful. No emojis. No time/cost estimates unless requested.",
"step_content": "Present as main analysis with clear structure and actionable insights. No emojis. No time/cost estimates unless requested.",
"continuation": "Use continuation_id for related planning sessions or implementation planning",
},
},
}
def handle_work_continuation(self, response_data: dict, request) -> dict:
"""
Handle work continuation with planner-specific deep thinking pauses.
"""
response_data["status"] = f"pause_for_{self.get_name()}"
response_data[f"{self.get_name()}_required"] = True
# Always include continuation_id if we have one (enables step chaining within session)
if continuation_id:
response_data["continuation_id"] = continuation_id
# Get planner-specific required actions
required_actions = self.get_required_actions(request.step_number, "planning", request.step, request.total_steps)
response_data["required_actions"] = required_actions
# Add previous plan context if available
if previous_plan_context:
response_data["previous_plan_context"] = previous_plan_context.strip()
# Enhanced deep thinking pauses for complex plans
if request.total_steps >= 5 and request.step_number <= 3:
response_data["status"] = "pause_for_deep_thinking"
response_data["thinking_required"] = True
response_data["required_thinking"] = required_actions
# RULE 4: next_step_required=false → Mark complete and store plan summary
if not request.next_step_required:
response_data["planning_complete"] = True
response_data["plan_summary"] = (
f"COMPLETE PLAN: {request.step} (Total {request.total_steps} steps completed)"
)
if request.step_number == 1:
response_data["next_steps"] = (
"Planning complete. Present the complete plan to the user in a well-structured format with clear sections, "
"numbered steps, visual elements (ASCII charts/diagrams where helpful), sub-step breakdowns, and implementation guidance. "
"Use headings, bullet points, and visual organization to make the plan easy to follow. "
"If there are phases, dependencies, or parallel tracks, show these relationships visually. "
"IMPORTANT: Do NOT use emojis - use clear text formatting and ASCII characters only. "
"Do NOT mention time estimates or costs unless explicitly requested. "
"After presenting the plan, offer to either help implement specific parts or use the continuation_id to start related planning sessions."
f"MANDATORY: DO NOT call the {self.get_name()} tool again immediately. This is a complex plan ({request.total_steps} steps) "
f"that requires deep thinking. You MUST first spend time reflecting on the planning challenge:\n\n"
f"REQUIRED DEEP THINKING before calling {self.get_name()} step {request.step_number + 1}:\n"
f"1. Analyze the FULL SCOPE: What exactly needs to be accomplished?\n"
f"2. Consider MULTIPLE APPROACHES: What are 2-3 different ways to tackle this?\n"
f"3. Identify CONSTRAINTS & DEPENDENCIES: What limits our options?\n"
f"4. Think about SUCCESS CRITERIA: How will we know we've succeeded?\n"
f"5. Consider RISKS & MITIGATION: What could go wrong early vs late?\n\n"
f"Only call {self.get_name()} again with step_number: {request.step_number + 1} AFTER this deep analysis."
)
# Result: Planning marked complete, summary stored for future context loading
else:
response_data["planning_complete"] = False
remaining_steps = request.total_steps - request.step_number
# ENHANCED: Add deep thinking pauses for complex plans in early stages
# Only for complex plans (>=5 steps) and first 3 steps - force deep reflection
if request.total_steps >= 5 and request.step_number <= 3:
response_data["status"] = "pause_for_deep_thinking"
response_data["thinking_required"] = True
if request.step_number == 1:
# Initial deep thinking - understand the full scope
response_data["required_thinking"] = [
"Analyze the complete scope and complexity of what needs to be planned",
"Consider multiple approaches and their trade-offs",
"Identify key constraints, dependencies, and potential challenges",
"Think about stakeholders, success criteria, and critical requirements",
"Consider what could go wrong and how to mitigate risks early",
]
response_data["next_steps"] = (
f"MANDATORY: DO NOT call the planner tool again immediately. This is a complex plan ({request.total_steps} steps) "
f"that requires deep thinking. You MUST first spend time reflecting on the planning challenge:\n\n"
f"REQUIRED DEEP THINKING before calling planner step {request.step_number + 1}:\n"
f"1. Analyze the FULL SCOPE: What exactly needs to be accomplished?\n"
f"2. Consider MULTIPLE APPROACHES: What are 2-3 different ways to tackle this?\n"
f"3. Identify CONSTRAINTS & DEPENDENCIES: What limits our options?\n"
f"4. Think about SUCCESS CRITERIA: How will we know we've succeeded?\n"
f"5. Consider RISKS & MITIGATION: What could go wrong early vs late?\n\n"
f"Only call planner again with step_number: {request.step_number + 1} AFTER this deep analysis."
)
elif request.step_number == 2:
# Refine approach - dig deeper into the chosen direction
response_data["required_thinking"] = [
"Evaluate the approach from step 1 - are there better alternatives?",
"Break down the major phases and identify critical decision points",
"Consider resource requirements and potential bottlenecks",
"Think about how different parts interconnect and affect each other",
"Identify areas that need the most careful planning vs quick wins",
]
response_data["next_steps"] = (
f"STOP! Complex planning requires reflection between steps. DO NOT call planner immediately.\n\n"
f"MANDATORY REFLECTION before planner step {request.step_number + 1}:\n"
f"1. EVALUATE YOUR APPROACH: Is the direction from step 1 still the best?\n"
f"2. IDENTIFY MAJOR PHASES: What are the 3-5 main chunks of work?\n"
f"3. SPOT DEPENDENCIES: What must happen before what?\n"
f"4. CONSIDER RESOURCES: What skills, tools, or access do we need?\n"
f"5. FIND CRITICAL PATHS: Where could delays hurt the most?\n\n"
f"Think deeply about these aspects, then call planner with step_number: {request.step_number + 1}."
)
elif request.step_number == 3:
# Final deep thinking - validate and prepare for execution planning
response_data["required_thinking"] = [
"Validate that the emerging plan addresses the original requirements",
"Identify any gaps or assumptions that need clarification",
"Consider how to validate progress and adjust course if needed",
"Think about what the first concrete steps should be",
"Prepare for transition from strategic to tactical planning",
]
response_data["next_steps"] = (
f"PAUSE for final strategic reflection. DO NOT call planner yet.\n\n"
f"FINAL DEEP THINKING before planner step {request.step_number + 1}:\n"
f"1. VALIDATE COMPLETENESS: Does this plan address all original requirements?\n"
f"2. CHECK FOR GAPS: What assumptions need validation? What's unclear?\n"
f"3. PLAN FOR ADAPTATION: How will we know if we need to change course?\n"
f"4. DEFINE FIRST STEPS: What are the first 2-3 concrete actions?\n"
f"5. TRANSITION MINDSET: Ready to shift from strategic to tactical planning?\n\n"
f"After this reflection, call planner with step_number: {request.step_number + 1} to continue with tactical details."
)
else:
# Normal flow for simple plans or later steps of complex plans
response_data["next_steps"] = (
f"Continue with step {request.step_number + 1}. Approximately {remaining_steps} steps remaining."
)
# Result: Intermediate step, planning continues (with optional deep thinking pause)
# Convert to clean JSON response
response_content = json.dumps(response_data, indent=2)
# Store this step in conversation memory
if continuation_id:
add_turn(
thread_id=continuation_id,
role="assistant",
content=response_content,
tool_name="planner",
model_name="claude-planner",
elif request.step_number == 2:
response_data["next_steps"] = (
f"STOP! Complex planning requires reflection between steps. DO NOT call {self.get_name()} immediately.\n\n"
f"MANDATORY REFLECTION before {self.get_name()} step {request.step_number + 1}:\n"
f"1. EVALUATE YOUR APPROACH: Is the direction from step 1 still the best?\n"
f"2. IDENTIFY MAJOR PHASES: What are the 3-5 main chunks of work?\n"
f"3. SPOT DEPENDENCIES: What must happen before what?\n"
f"4. CONSIDER RESOURCES: What skills, tools, or access do we need?\n"
f"5. FIND CRITICAL PATHS: Where could delays hurt the most?\n\n"
f"Think deeply about these aspects, then call {self.get_name()} with step_number: {request.step_number + 1}."
)
elif request.step_number == 3:
response_data["next_steps"] = (
f"PAUSE for final strategic reflection. DO NOT call {self.get_name()} yet.\n\n"
f"FINAL DEEP THINKING before {self.get_name()} step {request.step_number + 1}:\n"
f"1. VALIDATE COMPLETENESS: Does this plan address all original requirements?\n"
f"2. CHECK FOR GAPS: What assumptions need validation? What's unclear?\n"
f"3. PLAN FOR ADAPTATION: How will we know if we need to change course?\n"
f"4. DEFINE FIRST STEPS: What are the first 2-3 concrete actions?\n"
f"5. TRANSITION MINDSET: Ready to shift from strategic to tactical planning?\n\n"
f"After this reflection, call {self.get_name()} with step_number: {request.step_number + 1} to continue with tactical details."
)
else:
# Normal flow for simple plans or later steps
remaining_steps = request.total_steps - request.step_number
response_data["next_steps"] = (
f"Continue with step {request.step_number + 1}. Approximately {remaining_steps} steps remaining."
)
# Return the JSON response directly as text content, like consensus tool
return [TextContent(type="text", text=response_content)]
return response_data
except Exception as e:
# Error handling - return JSON directly like consensus tool
error_data = {"error": str(e), "status": "planning_failed"}
return [TextContent(type="text", text=json.dumps(error_data, indent=2))]
def customize_workflow_response(self, response_data: dict, request) -> dict:
"""
Customize response to match original planner tool format.
"""
# No need to append to step_history since workflow mixin already manages work_history
# and we calculate step counts from work_history
# Stub implementations for abstract methods (not used since we override execute)
async def prepare_prompt(self, request: PlannerRequest) -> str:
return "" # Not used - execute() is overridden
# Handle branching like original planner
if request.is_branch_point and request.branch_from_step and request.branch_id:
if request.branch_id not in self.branches:
self.branches[request.branch_id] = []
step_data = self.prepare_step_data(request)
self.branches[request.branch_id].append(step_data)
def format_response(self, response: str, request: PlannerRequest, model_info: dict = None) -> str:
return response # Not used - execute() is overridden
# Update metadata to reflect the new branch
if "metadata" in response_data:
response_data["metadata"]["branches"] = list(self.branches.keys())
# Add planner-specific output instructions for final steps
if not request.next_step_required:
response_data["planning_complete"] = True
response_data["plan_summary"] = (
f"COMPLETE PLAN: {request.step} (Total {request.total_steps} steps completed)"
)
response_data["output"] = {
"instructions": "This is a structured planning response. Present the step_content as the main planning analysis. If next_step_required is true, continue with the next step. If planning_complete is true, present the complete plan in a well-structured format with clear sections, headings, numbered steps, and visual elements like ASCII charts for phases/dependencies. Use bullet points, sub-steps, sequences, and visual organization to make complex plans easy to understand and follow. IMPORTANT: Do NOT use emojis - use clear text formatting and ASCII characters only. Do NOT mention time estimates or costs unless explicitly requested.",
"format": "step_by_step_planning",
"presentation_guidelines": {
"completed_plans": "Use clear headings, numbered phases, ASCII diagrams for workflows/dependencies, bullet points for sub-tasks, and visual sequences where helpful. No emojis. No time/cost estimates unless requested.",
"step_content": "Present as main analysis with clear structure and actionable insights. No emojis. No time/cost estimates unless requested.",
"continuation": "Use continuation_id for related planning sessions or implementation planning",
},
}
response_data["next_steps"] = (
"Planning complete. Present the complete plan to the user in a well-structured format with clear sections, "
"numbered steps, visual elements (ASCII charts/diagrams where helpful), sub-step breakdowns, and implementation guidance. "
"Use headings, bullet points, and visual organization to make the plan easy to follow. "
"If there are phases, dependencies, or parallel tracks, show these relationships visually. "
"IMPORTANT: Do NOT use emojis - use clear text formatting and ASCII characters only. "
"Do NOT mention time estimates or costs unless explicitly requested. "
"After presenting the plan, offer to either help implement specific parts or use the continuation_id to start related planning sessions."
)
# Convert generic status names to planner-specific ones
tool_name = self.get_name()
status_mapping = {
f"{tool_name}_in_progress": "planning_success",
f"pause_for_{tool_name}": f"pause_for_{tool_name}", # Keep the full tool name for workflow consistency
f"{tool_name}_required": f"{tool_name}_required", # Keep the full tool name for workflow consistency
f"{tool_name}_complete": f"{tool_name}_complete", # Keep the full tool name for workflow consistency
}
if response_data["status"] in status_mapping:
response_data["status"] = status_mapping[response_data["status"]]
return response_data
# ================================================================================
# Hook Method Overrides for Planner-Specific Behavior
# ================================================================================
def get_completion_status(self) -> str:
"""Planner uses planning-specific status."""
return "planning_complete"
def get_completion_data_key(self) -> str:
"""Planner uses 'complete_planning' key."""
return "complete_planning"
def get_completion_message(self) -> str:
"""Planner-specific completion message."""
return (
"Planning complete. Present the complete plan to the user in a well-structured format "
"and offer to help implement specific parts or start related planning sessions."
)
def get_skip_reason(self) -> str:
"""Planner-specific skip reason."""
return "Planner is self-contained and completes planning without external analysis"
def get_skip_expert_analysis_status(self) -> str:
"""Planner-specific expert analysis skip status."""
return "skipped_by_tool_design"
def store_initial_issue(self, step_description: str):
"""Store initial planning description."""
self.initial_planning_description = step_description
def get_initial_request(self, fallback_step: str) -> str:
"""Get initial planning description."""
try:
return self.initial_planning_description
except AttributeError:
return fallback_step
# Required abstract methods from BaseTool
def get_request_model(self):
"""Return the planner-specific request model."""
return PlannerRequest
async def prepare_prompt(self, request) -> str:
"""Not used - workflow tools use execute_workflow()."""
return "" # Workflow tools use execute_workflow() directly