Back to Blog
4 min read

Semantic Kernel Planners: Automatic AI Orchestration

Planners in Semantic Kernel automatically create execution plans from natural language goals. They analyze available skills and compose them into workflows without explicit programming.

Types of Planners

import semantic_kernel as sk
from semantic_kernel.planning import (
    SequentialPlanner,
    ActionPlanner,
    StepwisePlanner
)

# Sequential Planner - creates step-by-step plans
sequential = SequentialPlanner(kernel)

# Action Planner - selects single best action
action = ActionPlanner(kernel)

# Stepwise Planner - iteratively solves with reasoning
stepwise = StepwisePlanner(kernel)

Sequential Planner

Creates multi-step plans:

from semantic_kernel.planning import SequentialPlanner

async def use_sequential_planner(kernel: sk.Kernel, goal: str):
    """Create and execute a sequential plan."""

    planner = SequentialPlanner(kernel)

    # Create plan
    plan = await planner.create_plan_async(goal)

    # Inspect plan
    print(f"Plan for: {goal}")
    print(f"Steps: {len(plan._steps)}")
    for i, step in enumerate(plan._steps, 1):
        print(f"  {i}. {step.skill_name}.{step.name}")

    # Execute plan
    result = await plan.invoke_async()
    return str(result)

# Example
kernel = create_kernel_with_skills()

result = await use_sequential_planner(
    kernel,
    "Get customer data from database, analyze it, and create a summary report"
)

Action Planner

Selects the single best action:

from semantic_kernel.planning import ActionPlanner

async def use_action_planner(kernel: sk.Kernel, goal: str):
    """Use action planner for simple goals."""

    planner = ActionPlanner(kernel)

    # Create plan (single action)
    plan = await planner.create_plan_async(goal)

    print(f"Selected action: {plan.skill_name}.{plan.name}")
    print(f"Parameters: {plan.parameters}")

    # Execute
    result = await plan.invoke_async()
    return str(result)

# Good for:
# - Simple, direct requests
# - When you want quick responses
# - Reducing AI token usage

Stepwise Planner

Iteratively reasons through complex problems:

from semantic_kernel.planning import StepwisePlanner
from semantic_kernel.planning.stepwise_planner.stepwise_planner_config import StepwisePlannerConfig

async def use_stepwise_planner(kernel: sk.Kernel, goal: str):
    """Use stepwise planner for complex reasoning."""

    config = StepwisePlannerConfig(
        max_iterations=10,
        min_iteration_time_ms=0
    )

    planner = StepwisePlanner(kernel, config)

    # Create and execute plan
    plan = await planner.create_plan_async(goal)
    result = await plan.invoke_async()

    # Get reasoning trace
    print("Reasoning steps:")
    for step in plan.steps_taken:
        print(f"  - {step}")

    return str(result)

# Good for:
# - Complex multi-step problems
# - When intermediate reasoning matters
# - Research or analysis tasks

Custom Planner Configuration

from semantic_kernel.planning.sequential_planner.sequential_planner_config import SequentialPlannerConfig

# Configure sequential planner
config = SequentialPlannerConfig(
    relevancy_threshold=0.75,
    max_relevant_functions=20,
    included_skills=["DataAnalysis", "FileOperations"],
    excluded_skills=["DangerousOperations"],
    max_tokens=1024,
    allow_missing_functions=False
)

planner = SequentialPlanner(kernel, config)

# Configure action planner
from semantic_kernel.planning.action_planner.action_planner_config import ActionPlannerConfig

action_config = ActionPlannerConfig(
    included_skills=["QuickActions"],
    max_tokens=512
)

Handling Plan Failures

from semantic_kernel.planning.planning_exception import PlanningException

async def safe_plan_execution(kernel: sk.Kernel, goal: str) -> dict:
    """Execute plan with error handling."""

    planner = SequentialPlanner(kernel)

    try:
        # Create plan
        plan = await planner.create_plan_async(goal)

        if not plan._steps:
            return {
                "success": False,
                "error": "Could not create a plan for this goal",
                "suggestion": "Try rephrasing or breaking down the goal"
            }

        # Execute plan
        result = await plan.invoke_async()

        return {
            "success": True,
            "result": str(result),
            "steps_executed": len(plan._steps)
        }

    except PlanningException as e:
        return {
            "success": False,
            "error": f"Planning failed: {str(e)}",
            "suggestion": "Check if required skills are registered"
        }

    except Exception as e:
        return {
            "success": False,
            "error": f"Execution failed: {str(e)}",
            "step_failed": "Unknown"
        }

Plan Validation

class PlanValidator:
    """Validate plans before execution."""

    def __init__(self, kernel: sk.Kernel):
        self.kernel = kernel

    def validate(self, plan) -> dict:
        """Validate a plan."""
        issues = []

        # Check if all skills exist
        for step in plan._steps:
            try:
                self.kernel.skills.get_function(step.skill_name, step.name)
            except:
                issues.append(f"Missing function: {step.skill_name}.{step.name}")

        # Check step count
        if len(plan._steps) > 10:
            issues.append(f"Plan has {len(plan._steps)} steps - consider simplifying")

        # Check for dangerous operations
        dangerous = ["delete", "drop", "remove", "destroy"]
        for step in plan._steps:
            if any(d in step.name.lower() for d in dangerous):
                issues.append(f"Potentially dangerous operation: {step.name}")

        return {
            "valid": len(issues) == 0,
            "issues": issues,
            "step_count": len(plan._steps)
        }

# Usage
validator = PlanValidator(kernel)
plan = await planner.create_plan_async("Delete all old files")
validation = validator.validate(plan)

if not validation["valid"]:
    print("Plan issues:", validation["issues"])

Planner Selection Guide

def select_planner(goal: str, context: dict) -> str:
    """Select appropriate planner based on goal and context."""

    # Simple, direct goals -> Action Planner
    simple_keywords = ["what is", "tell me", "find", "get"]
    if any(kw in goal.lower() for kw in simple_keywords):
        if context.get("prefer_speed", False):
            return "action"

    # Complex analysis/research -> Stepwise Planner
    complex_keywords = ["analyze", "research", "investigate", "compare"]
    if any(kw in goal.lower() for kw in complex_keywords):
        return "stepwise"

    # Multi-step workflows -> Sequential Planner
    workflow_keywords = ["then", "after", "finally", "create report"]
    if any(kw in goal.lower() for kw in workflow_keywords):
        return "sequential"

    # Default to sequential
    return "sequential"

PLANNER_COMPARISON = {
    "action": {
        "best_for": "Simple, single-action goals",
        "speed": "Fast",
        "complexity": "Low",
        "tokens": "Low"
    },
    "sequential": {
        "best_for": "Multi-step workflows",
        "speed": "Medium",
        "complexity": "Medium",
        "tokens": "Medium"
    },
    "stepwise": {
        "best_for": "Complex reasoning tasks",
        "speed": "Slow",
        "complexity": "High",
        "tokens": "High"
    }
}

Best Practices

  1. Register clear skill descriptions: Planners rely on them
  2. Start with Action Planner: For simple use cases
  3. Validate before executing: Check plan makes sense
  4. Handle failures gracefully: Plans can fail
  5. Monitor token usage: Planners use tokens to plan
  6. Limit scope: Use included_skills to constrain planning

Resources

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.