Skip to content
AI360Xpert

Planning in Agents

Planning is how an agent decides what sequence of actions will achieve a goal — from generating a task decomposition upfront to adaptive replanning mid-task when the world doesn't cooperate with the original plan.

Planning is how an agent decides what sequence of actions will achieve a goal — from generating a task decomposition upfront to adaptive replanning mid-task when the world doesn't cooperate with the original plan.
Planning is how an agent decides what sequence of actions will achieve a goal — from generating a task decomposition upfront to adaptive replanning mid-task when the world doesn't cooperate with the original plan.

Why Does This Exist?

Complex goals cannot be achieved in a single LLM call. "Book me flights, hotels, and activities for a 10-day Japan trip within $5,000" requires dozens of sub-steps, dependencies between them, and adaptation when, say, the preferred hotel is sold out. Planning is the component that bridges the gap between a high-level goal and a sequence of concrete executable actions.

Without planning, an agent is reactive — responding to each observation independently without a coherent strategy. With planning, it is proactive — decomposing goals, sequencing steps, and adapting when the world doesn't cooperate.

Think of It Like This

A chess player thinking several moves ahead

A weak chess player reacts to the current position — "my queen is threatened, I'll move it." A strong player plans: "If I move my knight here, my opponent has three responses. For each response, I have a follow-up that leads to a winning position in 7 moves." Agents with strong planning capabilities similarly think ahead — they don't just react to the most recent observation but reason about sequences of consequences.

How It Actually Works

Task decomposition

The most common planning primitive: break a complex goal into smaller, manageable sub-tasks.

Hierarchical decomposition:

Goal: Write a research report on transformer models  ├── Gather sources  │     ├── Search academic databases  │     └── Search technical blogs  ├── Synthesize findings  │     ├── Summarize each source  │     └── Identify common themes  └── Write report        ├── Draft outline        ├── Write sections        └── Edit and format

A planner LLM generates this tree given the goal; an executor works through it, calling tools at leaf nodes.

Chain-of-Thought (CoT) planning

Simply prompting the LLM to reason step-by-step before acting dramatically improves plan quality:

"Let's think step by step:1. First, I need to understand what information is required.2. Then, I should search for the relevant facts.3. Next, I'll synthesize the findings.4. Finally, I'll produce the answer."

CoT improves reasoning accuracy on complex tasks by 10–40% compared to direct prompting.

Tree of Thoughts (ToT)

Extends CoT to maintain and evaluate multiple reasoning branches simultaneously. Instead of committing to a single chain, the agent:

  1. Generates kk possible next steps (thoughts) at each decision point.
  2. Evaluates each thought (via self-evaluation or a critic model).
  3. Pursues the most promising branch (beam search or MCTS).
  4. Backtracks if a branch leads to a dead end.

ToT significantly outperforms CoT on tasks requiring search and backtracking (puzzles, planning problems).

Plan-Execute-Replan

The production-grade pattern for long-horizon tasks:

PLAN:    Given goal G, generate plan [step_1, step_2, ..., step_n]EXECUTE: Run step_1 using available tools, observe result_1EVALUATE: Did result_1 match expectations?  IF YES: proceed to step_2  IF NO:  REPLAN given G and {step_1, result_1, failure_reason}

Replanning makes agents robust to partial failures and unexpected observations.

Code

from langchain_openai import ChatOpenAIfrom langchain.schema import SystemMessage, HumanMessage
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# ── Task decomposition via LLM ────────────────────────────────────────────────def generate_plan(goal: str) -> list[str]:    """Use LLM to decompose a goal into ordered sub-tasks."""    messages = [        SystemMessage(content="""You are a planning assistant. Given a goal, decompose it into a numbered list of concrete, executable sub-tasks. Be specific. Output only the numbered list, nothing else."""),        HumanMessage(content=f"Goal: {goal}"),    ]    response = llm.invoke(messages)    lines = response.content.strip().split("\n")    steps = [line.split(". ", 1)[-1].strip() for line in lines if line.strip()]    return steps
# ── Plan-Execute-Replan loop ──────────────────────────────────────────────────def execute_step(step: str, tools: dict) -> tuple[str, bool]:    """    Mock executor: run a step using available tools.    Returns (result, success).    """    # In a real system, this routes to appropriate tools    if "search" in step.lower():        return f"[search result for: {step}]", True    elif "write" in step.lower() or "draft" in step.lower():        return f"[drafted content for: {step}]", True    else:        return f"[completed: {step}]", True

def plan_execute_replan(goal: str, tools: dict, max_replans: int = 3):    """Production-grade planning loop with replanning on failure."""    plan = generate_plan(goal)    print(f"Initial plan ({len(plan)} steps):")    for i, step in enumerate(plan, 1):        print(f"  {i}. {step}")        results = []    i = 0    replan_count = 0        while i < len(plan):        step = plan[i]        result, success = execute_step(step, tools)                if success:            results.append({"step": step, "result": result})            print(f"\n✓ Step {i+1}: {step[:50]}...")            i += 1        else:            print(f"\n✗ Step {i+1} failed: {step[:50]}...")            if replan_count < max_replans:                # Replan from current state                context = f"Completed: {results}\nFailed: {step}\nError: {result}"                new_remaining = generate_plan(f"Continue goal: {goal}\nContext: {context}")                plan = plan[:i] + new_remaining                replan_count += 1                print(f"  → Replanned with {len(new_remaining)} new steps")            else:                print(f"  → Max replans reached. Aborting.")                break        return results
# Example usagegoal = "Research and summarize the top 3 use cases for RAG systems in enterprise settings"tools = {"search": lambda q: f"results for {q}", "write": lambda t: f"text about {t}"}results = plan_execute_replan(goal, tools)print(f"\nCompleted {len(results)} steps")

Watch Out For

Over-planning for simple tasks

Generating a 20-step plan for a task that needs 3 steps wastes tokens, introduces more failure points, and can confuse the executor. Calibrate plan granularity to task complexity. Use heuristics or a lightweight classifier to decide whether to decompose at all before calling the planner.

Plans that don't account for tool constraints

An LLM planner generates steps based on what seems logically correct, not what's actually feasible with the available tools. A step like "access the user's email history" will fail if the agent doesn't have an email tool. During plan generation, inject the tool list into the system prompt so the planner generates grounded, executable steps.

The Quick Version

  • Planning converts a high-level goal into a sequence of concrete executable actions.
  • Task decomposition: break the goal into a hierarchy of sub-tasks — hierarchical or flat.
  • Chain-of-Thought: step-by-step reasoning before acting improves plan quality by 10–40%.
  • Tree of Thoughts: explore multiple reasoning branches simultaneously; backtrack on failure.
  • Plan-Execute-Replan: generate an initial plan, execute step-by-step, and replan when steps fail.