Skip to content

Planning Fundamentals: Task Decomposition & Goal Hierarchies

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: Find competitor information
# 2. Research: Analyze competitor websites
# 3. Research: Read market research reports
# 4. Analysis: Compare competitor features
# 5. Analysis: Extract market gaps
# 6. Report: Write introduction
# 7. Report: Write competitor section
# ... 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: Planning a trip
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: Planning for every detail
plan = detailed_planner.plan("Write email")
# Output: 47 steps for a simple 5-minute task
# Result: Analysis paralysis, too much overhead

# ✅ RIGHT: Simple plan for simple tasks
plan = simple_planner.plan("Write email")
# Output: ["Research topic", "Draft", "Review", "Send"]
# Result: Efficient execution

Lesson: Plan depth should match task complexity.

Warning 2: Rigid Plans

# ❌ WRONG: Never deviate from plan
if not task_complete:
    force_complete(task)  # Even if approach isn't working

# ✅ RIGHT: Adapt when plan isn't working
if not task_complete:
    if retry_count < 3:
        try_different_approach()
    else:
        replan()

Lesson: Plans are guides, not contracts.

Warning 3: Missing Dependencies

# ❌ WRONG: Ignore dependencies
tasks = ["Design", "Code", "Test"]  # No ordering
launch_all_parallel()  # Crashes: can't code before design

# ✅ RIGHT: Respect dependencies
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

  1. Planning separates agents from chatbots - Systematic thinking
  2. Multiple strategies exist - Hierarchical, dependency-based, constraint-based
  3. Plan depth matches task complexity - Don't over-engineer simple tasks
  4. Plans are guides, not mandates - Adapt when needed
  5. Validation critical - Check feasibility before execution
  6. Monitor execution - Detect issues early
  7. Learn from plans - Improve planning over time

Next Steps


Last Updated: August 9, 2026