Planning Fundamentals¶
Overview¶
Planning is the process of breaking down complex goals into manageable sub-goals and determining the sequence of actions needed to achieve them.
Without planning, agents become reactive (responding to immediate situations). With planning, agents become goal-directed (working systematically toward objectives).
The Planning Problem¶
Without Planning¶
User: "Write a comprehensive report on AI trends"
Agent (reactive):
→ Generates random paragraphs about AI
→ No structure, no coherence
→ Misses key points
→ Quality: Poor
With Planning¶
User: "Write a comprehensive report on AI trends"
Agent (planned):
1. Define report structure
- Introduction (1 page)
- Current trends (3 pages)
- Impact analysis (2 pages)
- Future predictions (1 page)
- Conclusion (1 page)
2. Research each section
3. Write each section
4. Integrate into coherent report
5. Review and edit
Quality: Excellent
Core Concepts¶
Goal Hierarchy¶
Breaking one large goal into smaller sub-goals:
Top-Level Goal: "Analyze market for Product X"
│
- Sub-Goal 1: "Research competitive landscape"
- Find competitors
- Analyze their products
- Compare features
│
- Sub-Goal 2: "Analyze customer needs"
- Survey existing customers
- Analyze feedback
- Identify gaps
│
- Sub-Goal 3: "Assess financial viability"
- Estimate development cost
- Project revenue
- Calculate ROI
Ordering & Dependencies¶
Some tasks must happen in order, others can be parallel:
Sequential (one then another):
Research → Analysis → Report
[Can't analyze before researching]
Parallel (simultaneously):
- Competitive Analysis
- Customer Research
- Market Research
[All can happen at same time]
Hybrid:
Research (parallel) → Analysis (sequential) → Report
Planning Strategies¶
Strategy 1: Hierarchical Planning (Top-Down)¶
Break large goal into progressively smaller sub-goals.
class HierarchicalPlanner:
def plan(self, goal: str) -> List[str]:
"""Break goal into hierarchical sub-goals"""
# Level 1: Main phases
phases = self.llm.decompose_into_phases(goal)
# ["Research phase", "Analysis phase", "Report phase"]
# Level 2: Sub-tasks per phase
all_tasks = []
for phase in phases:
subtasks = self.llm.decompose_into_tasks(phase)
all_tasks.extend(subtasks)
# Level 3: Specific actions
all_actions = []
for task in all_tasks:
actions = self.llm.decompose_into_actions(task)
all_actions.extend(actions)
return all_actions
# Example execution
planner = HierarchicalPlanner()
plan = planner.plan("Write comprehensive market analysis report")
# Output:
# 1. Research
# 2. Research
# 3. Research
# 4. Analysis
# 5. Analysis
# 6. Report
# 7. Report
#... etc
Pros:
- Systematic and organized
- Easy to understand
- Good for structured tasks
Cons:
- May miss dependencies
- Rigid structure
- Doesn't handle surprises well
Strategy 2: Dependency-Based Planning¶
Identify dependencies between tasks, then order them:
class DependencyPlanner:
def plan(self, goal: str) -> List[str]:
"""Plan by identifying dependencies"""
# Get all tasks
tasks = self.llm.list_tasks(goal)
# ["research", "analysis", "writing", "editing"]
# Identify dependencies
dependencies = {}
for task in tasks:
deps = self.llm.identify_dependencies(task)
dependencies[task] = deps
# Topological sort (order by dependencies)
ordered_tasks = self.topological_sort(dependencies)
return ordered_tasks
def topological_sort(self, dependencies):
"""Order tasks respecting dependencies"""
result = []
visited = set()
def visit(node):
if node in visited:
return
visited.add(node)
for dep in dependencies.get(node, []):
visit(dep)
result.append(node)
for task in dependencies:
visit(task)
return result
# Example
planner = DependencyPlanner()
plan = planner.plan("Launch new product")
# Automatically handles:
# - Design must come before manufacturing
# - Marketing must come before launch
# - But testing can happen in parallel
Strategy 3: Constraint-Based Planning¶
Plan while respecting constraints (time, budget, resources):
class ConstraintPlanner:
def plan(self, goal: str, constraints: dict) -> List[str]:
"""Plan respecting constraints"""
max_time_hours = constraints.get('time', float('inf'))
max_budget = constraints.get('budget', float('inf'))
available_resources = constraints.get('resources', [])
# Generate candidate tasks
all_tasks = self.llm.list_all_tasks(goal)
# Estimate time and cost for each
task_metrics = {}
for task in all_tasks:
task_metrics[task] = {
'time': self.estimate_time(task),
'cost': self.estimate_cost(task),
'resources_needed': self.identify_resources(task)
}
# Select tasks that fit constraints
selected_tasks = []
total_time = 0
total_cost = 0
for task in sorted(task_metrics.keys(),
key=lambda t: task_metrics[t]['cost']):
metrics = task_metrics[task]
# Check if fits
if (total_time + metrics['time'] <= max_time_hours and
total_cost + metrics['cost'] <= max_budget and
self.resources_available(metrics['resources_needed'])):
selected_tasks.append(task)
total_time += metrics['time']
total_cost += metrics['cost']
return selected_tasks
# Usage
planner = ConstraintPlanner()
plan = planner.plan(
goal="Analyze market",
constraints={
'time': 10, # Max 10 hours
'budget': 5000, # Max $5000
'resources': ['researcher', 'analyst']
}
)
# Automatically selects high-value tasks that fit constraints
Planning Techniques¶
Technique 1: Goal Regression (Backward Planning)¶
Start from desired goal, work backward to current state:
class BackwardPlanner:
def plan(self, current_state: dict, goal_state: dict) -> List[str]:
"""Plan backward from goal to current state"""
plan = []
state = goal_state
while state != current_state:
# What action brings us closer?
action = self.find_regressive_action(state, current_state)
if not action:
# Can't reach goal
return None
plan.insert(0, action) # Add to front of plan
state = self.apply_inverse(state, action)
return plan
# Example
backward_plan = BackwardPlanner()
current = {"location": "home", "time": "9:00am"}
goal = {"location": "meeting", "time": "10:00am"}
plan = backward_plan.plan(current, goal)
# Backward reasoning:
# To be at meeting at 10:00am:
# → Need to leave office at 9:50am
# → Need to be at office by 9:40am
# → Need to leave home by 9:15am (with buffer)
# → Need to prepare by 9:00am
Technique 2: Abstraction¶
Plan at high level first, then fill in details:
class AbstractionPlanner:
def plan(self, goal: str) -> dict:
"""Plan using abstraction levels"""
# Level 1: Abstract plan (high-level milestones)
abstract_plan = [
"Prepare",
"Execute",
"Finalize"
]
# Level 2: Concrete plan (specific tasks)
concrete_plan = {}
for phase in abstract_plan:
concrete_plan[phase] = self.llm.expand_phase(phase, goal)
# Level 3: Action plan (specific actions)
action_plan = {}
for phase, tasks in concrete_plan.items():
action_plan[phase] = []
for task in tasks:
actions = self.llm.expand_task(task)
action_plan[phase].extend(actions)
return action_plan
# Example
planner = AbstractionPlanner()
plan = planner.plan("Organize company event")
# Result:
# Prepare:
# - Reserve venue
# - Send invitations
# - Plan agenda
# Execute:
# - Set up venue
# - Greet attendees
# - Run agenda
# Finalize:
# - Cleanup
# - Send thank-you notes
-
Planning in Production¶
Complete Planning System¶
class ProductionPlanner:
def __init__(self):
self.llm = LLM()
self.memory = MemorySystem()
self.validator = PlanValidator()
def plan(self, goal: str, context: dict = None) -> dict:
"""Generate complete plan"""
# Step 1: Understand goal
goal_analysis = self.analyze_goal(goal)
# {constraints, scope, dependencies, resources_needed}
# Step 2: Generate plan options
plan_options = self.generate_plan_options(goal, goal_analysis)
# [option1, option2, option3]
# Step 3: Evaluate options
ranked_options = self.rank_options(plan_options)
# Step 4: Validate best option
best_plan = ranked_options[0]
is_valid = self.validator.validate(best_plan)
if not is_valid:
# Try next option or replan
best_plan = self.handle_invalid_plan(ranked_options)
# Step 5: Store for learning
self.memory.store_plan(goal, best_plan)
return {
'goal': goal,
'plan': best_plan['steps'],
'estimated_time': best_plan['time'],
'estimated_cost': best_plan['cost'],
'confidence': best_plan['confidence'],
'dependencies': best_plan['dependencies']
}
def analyze_goal(self, goal: str) -> dict:
"""Analyze goal to understand scope"""
analysis = self.llm.analyze(f"""
Analyze this goal and identify:
1. Key constraints (time, budget, resources)
2. Scope (what's included/excluded)
3. Dependencies (prerequisites)
4. Success criteria (how to know it's done)
Goal: {goal}
""")
return self.parse_analysis(analysis)
def generate_plan_options(self, goal: str, analysis: dict):
"""Generate multiple plan options"""
options = []
# Option 1: Fast (minimal steps)
fast_plan = self.llm.plan(f"""
Create a fast plan for: {goal}
Focus on: Most critical steps only
Time: Minimize
Quality: Acceptable minimum
""")
options.append({'name': 'fast', 'plan': fast_plan})
# Option 2: Quality (comprehensive)
quality_plan = self.llm.plan(f"""
Create a quality plan for: {goal}
Focus on: Comprehensive approach
Time: Allow sufficient time
Quality: Excellent
""")
options.append({'name': 'quality', 'plan': quality_plan})
# Option 3: Balanced (middle ground)
balanced_plan = self.llm.plan(f"""
Create a balanced plan for: {goal}
Focus on: Efficient and good quality
Time: Reasonable
Quality: Good
""")
options.append({'name': 'balanced', 'plan': balanced_plan})
return options
def rank_options(self, options: List) -> List:
"""Rank options by usefulness"""
ranked = []
for option in options:
score = self.score_plan(option['plan'])
ranked.append({
'name': option['name'],
'plan': option['plan'],
'score': score
})
return sorted(ranked, key=lambda x: x['score'], reverse=True)
def score_plan(self, plan: dict) -> float:
"""Score a plan's quality"""
score = 0
score += plan['expected_quality'] * 0.4 # Quality weight
score += (1 / plan['estimated_time']) * 0.3 # Speed bonus
score += (1 / plan['estimated_cost']) * 0.3 # Cost efficiency
return score
Planning Warnings¶
Warning 1: Over-Planning¶
# WRONG
plan = detailed_planner.plan("Write email")
# Output
# Result
# RIGHT
plan = simple_planner.plan("Write email")
# Output
# Result
Lesson: Plan depth should match task complexity.
Warning 2: Rigid Plans¶
# WRONG
if not task_complete:
force_complete(task) # Even if approach isn't working
# RIGHT
if not task_complete:
if retry_count < 3:
try_different_approach()
else:
replan()
Lesson: Plans are guides, not contracts.
Warning 3: Missing Dependencies¶
# WRONG
tasks = ["Design", "Code", "Test"] # No ordering
launch_all_parallel() # Crashes: can't code before design
# RIGHT
tasks = {
"Design": [],
"Code": ["Design"],
"Test": ["Code"]
}
execute_respecting_dependencies(tasks)
Lesson: Always identify task dependencies.
Best Practices¶
1. Validate Before Executing¶
# Good
plan = planner.plan(goal)
if validator.is_feasible(plan):
executor.execute(plan)
else:
planner.replan()
# Bad
plan = planner.plan(goal)
executor.execute(plan) # Hope for best
2. Monitor Execution¶
# Good
for step in plan:
result = execute_step(step)
if not result.success:
replanning_trigger = True
break
# Bad
for step in plan:
execute_step(step) # Blind execution
3. Keep Plans Flexible¶
# Good
def execute_plan(plan):
for step in plan:
result = execute(step)
if result.failed and is_recoverable(result):
alternative = find_alternative(step)
execute(alternative)
# Bad
def execute_plan(plan):
for step in plan:
execute(step) # Always same way
Key Takeaways¶
- Planning separates agents from chatbots - Systematic thinking
- Multiple strategies exist - Hierarchical, dependency-based, constraint-based
- Plan depth matches task complexity - Don't over-engineer simple tasks
- Plans are guides, not mandates - Adapt when needed
- Validation critical - Check feasibility before execution
- Monitor execution - Detect issues early
- Learn from plans - Improve planning over time
-
Next Steps¶
- Read Chain Of Thought - Visible step-by-step reasoning
- Read Tree Of Thought - Exploring multiple branches
-
Last Updated: August 9, 2026