Agentic AI · Module 7 of 41 · 10 concepts · about 17 min

Planning in Agents

How an agent breaks a goal into subtasks, orders them by dependency, checks the plan before acting, carries it out, and replans when the world disagrees; and the planner-executor design that separates thinking from doing.

At work: What separates an agent that finishes a twenty-step job from one that wanders, repeats itself and stops halfway.

Task decomposition and subtask generation

What it is

Task decomposition is splitting a goal into smaller pieces called subtasks. A good subtask is:

  • small enough to finish in a few steps;
  • checkable: there is a clear way to tell it is done;
  • self-contained: it states what it needs and what it produces;
  • necessary: removing it would leave the goal unmet.

Why it matters

Large goals overwhelm a model the way they overwhelm a person.

  • 'Plan a three-city trip under ₹60,000' is too big to act on. 'Find flights from Pune to Delhi on 4 March under ₹6,000' is not.
  • Small subtasks have a higher success rate each, and a failed one can be retried alone.
  • A list of subtasks is also something a person can read and correct before any money is spent.

In the real world: A research agent asked to 'assess a competitor' produced a rambling report until its goal was decomposed into five named questions, each with a 'done when' line.

from pydantic import BaseModel

class Subtask(BaseModel):
    id: str
    description: str               # what to do, in one sentence
    depends_on: list[str] = []     # ids that must finish first
    done_when: str                 # how to tell it is finished

class Plan(BaseModel):
    goal: str
    subtasks: list[Subtask]

PLANNER_PROMPT = """Break the goal into the fewest subtasks that fully achieve it.
Each subtask must be doable with the tools listed, checkable, and no larger than
about five tool calls. Give each a 'done_when'. List dependencies by id.
Do not add subtasks the goal did not ask for.

Tools available: {tools}
Goal: {goal}"""

Decomposing too finely. Twenty one-line subtasks cost twenty rounds of overhead and lose the thread between them. Split until each piece is doable, then stop.

Practise it: Design a Multi-Agent AI System

Dependency resolution

What it is

Subtasks often depend on each other. Dependency resolution is working out a valid order:

  • A subtask is ready when everything it depends on is done.
  • Subtasks that are ready at the same time are independent and can run in parallel.
  • The dependencies form a DAG (directed acyclic graph): arrows from each subtask to those that need it, with no loops.
  • A topological sort is the standard method for turning such a graph into an order that respects every arrow.

Why it matters

Order matters for correctness and for speed.

  • Booking a hotel before the flight dates are fixed produces a booking for the wrong nights.
  • Knowing which subtasks are independent lets you run them together, which can cut a long task's time by half or more.
  • A loop in the dependencies (A needs B, B needs A) is a broken plan, and can be detected before anything runs.

In the real world: A trip planner searches flights, hotels and weather for each city at once because the plan marks them independent, then builds the itinerary; the user waits twelve seconds instead of forty.

Visual example

Dependencies

Rows with the same 'wave' have no dependency on each other and run together.

A trip plan as subtasks and dependencies
idsubtaskdepends onwave
t1Fix travel dates from the user's calendarnone1
t2Find flights Pune to Delhit12
t3Find hotels in Delhit12
t4Check the weather forecastt12
t5Build a day-by-day itineraryt2, t3, t43
t6Ask the user to approve the bookingst54
def ready(plan: Plan, done: set[str]) -> list[Subtask]:
    """Subtasks whose dependencies are all finished."""
    return [t for t in plan.subtasks
            if t.id not in done and all(d in done for d in t.depends_on)]

def execute_plan(plan: Plan) -> dict[str, str]:
    done, results = set(), {}
    while len(done) < len(plan.subtasks):
        batch = ready(plan, done)
        if not batch:
            raise ValueError("Plan has a dependency cycle or a missing subtask")
        for task, result in run_in_parallel(batch, context=results):   # independent work
            results[task.id] = result
            done.add(task.id)
    return results

Running subtasks in the order the model happened to list them. Use the declared dependencies; the listed order is only a suggestion and is sometimes wrong.

Planning before execution and during execution

What it is

Two times at which the deciding can happen:

  • Planning before execution: write the whole plan first, then carry it out. Also called plan-and-execute.
  • Planning during execution: decide one step, see its result, decide the next. Also called reactive or interleaved planning. ReAct (module 9) is this style.

Most strong agents mix them: a rough plan at the start, revised as results arrive.

Why it matters

Each has a clear strength and a clear weakness:

  • A plan up front gives a view of the whole task, catches missing steps early, allows parallel work, and can be shown to a person. But it is written before anything is known, so it may be wrong.
  • Deciding as you go adapts to every surprise. But the agent can lose sight of the goal, loop, or do things in a wasteful order.

In the real world: A coding agent writes a short plan before touching any file, then works reactively inside each plan item, updating the plan when a test reveals something unexpected.

Visual example

Trade-off

Predictable task: plan first. Unpredictable task: decide as you go, around a light plan.

Planning up front versus deciding as you go
plan up frontdecide as you go
Sees the whole taskYesOnly the next step
Adapts to surprisesNeeds replanningNaturally
Parallel executionEasyHard
Reviewable by a person firstYesNo
Model callsFewer, largerMore, smaller
Typical failureFollows a stale planWanders or loops
Best forKnown shape, many stepsExploration, debugging

Choosing by fashion. Choose by how predictable the task is: if step three depends heavily on what step two finds, plan lightly and adapt; if the steps are largely known, plan fully and execute.

Practise it: Design a Multi-Agent AI System

Plan validation

What it is

Plan validation is checking a plan before acting on it. Four checks, all in code:

  • Feasible: every step uses a tool that exists and the agent is allowed to use.
  • Well-formed: dependencies point at real steps and contain no loops.
  • Complete: every part of the goal is covered by some step.
  • Within limits: the number of steps and the estimated cost fit the budget, and no step breaks a policy.

A plan that fails is sent back to the planner with the reason. A risky plan can also be shown to a person.

Why it matters

A bad plan is the cheapest thing to fix before execution and the dearest after.

  • A plan naming a tool that does not exist will fail at step four, after three steps were paid for.
  • Catching a forbidden action at plan time means it is never attempted.
  • Validation turns 'the model wrote a list' into 'the system accepted a plan'.

In the real world: An operations agent's planner once proposed 'restart the database' as step two; plan validation rejected it because a restart must be preceded by an approval step.

def validate(plan: Plan, tools: set[str], max_subtasks: int = 12) -> list[str]:
    """Return a list of problems. Empty means the plan may run."""
    problems, ids = [], {t.id for t in plan.subtasks}
    if len(plan.subtasks) > max_subtasks:
        problems.append(f"Too many subtasks ({len(plan.subtasks)} > {max_subtasks})")
    for task in plan.subtasks:
        for dep in task.depends_on:
            if dep not in ids:
                problems.append(f"{task.id} depends on unknown subtask {dep}")
        if not task.done_when.strip():
            problems.append(f"{task.id} has no completion check")
    if has_cycle(plan):
        problems.append("Dependencies contain a cycle")
    problems += policy_violations(plan, tools)       # e.g. a write step with no approval step
    return problems

Asking the same model 'is this plan good?' and calling that validation. It nearly always says yes. Use code for what code can check, and a differently prompted reviewer or a person for the rest.

Plan execution and progress tracking

What it is

Plan execution is carrying the plan out while keeping track. The plan becomes a living to-do list in which each item is:

  • pending, in progress, done or failed;
  • linked to its result, so later steps can use it;
  • re-shown to the model at each step, so the goal stays in view.

Why it matters

A plan written once and never looked at again does little.

  • Showing the current list each turn tells the model what is done and what is next, which prevents repeated and skipped steps.
  • Recorded statuses make progress measurable: 'four of six done' is real information for a user and for a budget check.
  • If the run is interrupted, the list is exactly what is needed to resume.

In the real world: A long-running coding agent re-reads its to-do list every turn; before this was added, it finished four of six requested changes and announced success.

def render_plan(plan: Plan, status: dict[str, str]) -> str:
    marks = {"done": "[x]", "in_progress": "[>]", "failed": "[!]", "pending": "[ ]"}
    lines = [f"{marks[status[t.id]]} {t.id}: {t.description}" for t in plan.subtasks]
    return "Current plan:\n" + "\n".join(lines)

# Sent near the END of the context on every turn, where the model attends best:
#
# Current plan:
# [x] t1: Fix travel dates from the user's calendar
# [x] t2: Find flights Pune to Delhi
# [>] t3: Find hotels in Delhi
# [ ] t5: Build a day-by-day itinerary

Letting the agent mark its own subtask 'done' with no check. Use the subtask's done_when and, where possible, verify it in code: the file exists, the test passes, the booking id is returned.

Replanning

What it is

Replanning (or dynamic replanning) is changing the plan part-way through. It is triggered when:

  • a step fails and a retry will not help;
  • a result contradicts an assumption the plan was built on;
  • the user changes the goal;
  • progress stalls: several steps with nothing new learned.

The planner is given the goal, the original plan, what has been done and what went wrong, and writes a new plan for the remaining work only.

Why it matters

A plan is a guess made before the facts were in.

  • An agent that cannot replan either fails at the first surprise or presses on with steps that no longer make sense.
  • Replanning only the remainder keeps the work already done.
  • A cap on replans is needed, or an impossible goal becomes an endless planning loop.

In the real world: A travel agent finds the planned flight sold out, keeps the hotel research, and replans only the travel legs; an earlier version restarted the whole trip and re-queried every hotel.

Visual example

Replanning

Only the unfinished part is replanned, and only a limited number of times.

Plan, execute, notice, replan

  1. PlanSix subtasks, validated
  2. Executet3 fails: no hotels under budget
  3. DiagnoseThe budget assumption is wrong
  4. Replan the restKeep t1, t2; new t3 and t3b
  5. Continue or stopAfter two replans, hand over
def run_with_replanning(goal: str, max_replans: int = 2) -> dict:
    plan = make_plan(goal)
    results, replans = {}, 0
    while True:
        outcome = execute_until_blocked(plan, results)     # runs ready subtasks in order
        if outcome.finished:
            return results
        if replans == max_replans:
            return hand_over(goal, plan, results, reason=outcome.problem)
        plan = make_plan(                                   # plan only what remains
            goal, completed=results, problem=outcome.problem,
            instruction="Keep completed work. Change only what the problem requires.",
        )
        replans += 1

Replanning from scratch on every hiccup. It discards finished work and can oscillate between two plans. Retry the step first; replan only when the plan itself is wrong.

Planner-executor architecture

What it is

The planner-executor architecture splits the work between two roles:

  • A planning agent reads the goal and writes the plan. It usually has no tools, or only read tools. It is often the largest model.
  • One or more executor agents each take one subtask and carry it out with tools. They can be smaller, cheaper models.
  • An orchestrator (your code) passes subtasks to executors, collects results, and calls the planner again when replanning is needed.

Why it matters

Separating thinking from doing brings three gains.

  • Cost: the expensive model is called once or twice to plan; cheap models do the many small steps.
  • Focus: each executor sees only its subtask, so its context stays short and clear.
  • Safety and review: the plan is an inspectable object that can be validated or approved before any tool runs.

In the real world: A data-migration agent plans with a large model once, then runs sixty table copies with a small one; the bill falls by four-fifths compared with using the large model throughout.

Visual example

Planner-executor

One model thinks once. Cheaper models do the many steps.

Who does what

  1. PlannerLarge model; no write tools
  2. ValidatorCode checks; a person if risky
  3. executors, in parallel
    ExecutorSmall model, one subtask
    ExecutorSmall model, one subtask
  4. OrchestratorTracks status; triggers replans
  5. SynthesiserCombines results

Giving executors too little context. An executor told only 'find hotels' does not know the dates, budget or city. Each subtask must carry everything needed to do it, including results from the steps it depends on.

Hierarchical planning and how much to plan

What it is

Hierarchical planning plans at more than one level:

  • a high-level plan of a few big stages;
  • each stage expanded into its own detailed plan only when it is reached.

This avoids planning details that an earlier stage may make irrelevant. The amount of planning should match the task:

Why it matters

Planning has a cost in money, time and rigidity.

  • A two-step task needs no plan at all.
  • A ten-step task needs a short list.
  • A task of hundreds of steps needs levels, so no single plan is too long to keep in view.
  • Expanding detail late means the detailed plan is written with real results in hand.

In the real world: A software agent plans 'design, implement, test, document' at the top level, and writes the file-by-file plan for 'implement' only after the design stage has settled the interfaces.

Visual example

How much to plan

More planning is not better. Enough planning is.

Matching planning effort to the task
task sizeplanning styleexample
1 to 2 stepsNone: just actLook up an order
3 to 8 stepsDecide as you goResolve a refund
8 to 30 stepsPlan first, with a to-do list and replanningPlan a multi-city trip
Dozens to hundredsHierarchical: stages, then steps within eachMigrate a codebase

Requiring a full written plan for every request. A simple balance enquiry then costs three model calls instead of one. Let simple requests skip planning.

Practise it: Design a Multi-Agent AI System

Interview questions: planning in agents

What it is

The questions interviewers ask about planning in agents, from the first screening call to the senior round. Cover the answer, say yours aloud, then compare.

Why it matters

Reading an idea and explaining it are different skills.

  • Each answer below is the two or three sentences an interviewer is listening for.
  • The reasoning behind each one is in the cards above. If an answer surprises you, go back to that card.

In the real world: A candidate asked to design a travel agent draws a dependency graph and marks which nodes run in parallel; the interviewer skips the next three prepared questions.

Q: Why do agents need planning?

On long tasks an agent without a plan loses the goal, repeats work or stops early. A plan keeps the whole task in view, exposes independent steps for parallel work, and gives people something to review before actions happen.

Q: Plan up front or reason dynamically?

Plan up front when the task's shape is predictable: fewer calls, parallelism, reviewability. Decide step by step when each result changes what comes next. In practice, a light plan plus replanning.

Q: What is task decomposition?

Splitting a goal into subtasks that are small, checkable, self-contained and necessary, each with a completion condition and its dependencies.

Q: How do you order subtasks?

Treat dependencies as a directed acyclic graph. A subtask is ready when its dependencies are done; ready subtasks run in parallel. A cycle means the plan is invalid.

Q: How do you validate a plan?

In code: tools exist and are permitted, dependencies resolve with no cycles, the goal is covered, step count and cost are within budget, and policy holds. Risky plans go to a person.

Q: When should an agent replan?

When a step fails in a way retry cannot fix, a result contradicts an assumption, the goal changes, or progress stalls. Replan only the remaining work, and cap the number of replans.

Q: What is the planner-executor architecture?

A planner model writes the plan; cheaper executor models carry out subtasks with tools; an orchestrator in code tracks status and triggers replanning. It cuts cost, keeps contexts small and makes the plan inspectable.

Q: What goes wrong with planning?

Stale plans followed blindly, over-decomposition, executors missing context, self-declared completion, and replanning loops. Each has a counter: replanning triggers, size limits, explicit inputs per subtask, coded completion checks and a replan cap.

Saying 'the agent makes a plan' without saying who checks it, who tracks it, or what happens when it is wrong.

Practise it: Design a Multi-Agent AI System, Design an AI Customer Support Agent

Exercise: add planning to an agent

What it is

A short build that uses this module's ideas. Write it yourself before looking anything up; the checks at the end tell you whether it works.

Why it matters

An idea you have only read fades in a week.

  • Building it once, even in fifty lines, is what makes it yours.
  • Interviewers can tell the difference between someone who has read about a loop and someone who has debugged one.

In the real world: A logistics start-up's route-planning assistant is this exercise grown up: the same four parts, with real tools behind them.

Your task. Build a small plan-and-execute agent: a planner that returns a typed plan, a validator, an executor that respects dependencies, and one round of replanning.

Steps

  1. Define Subtask and Plan as in this module, and get a plan for 'plan a two-day trip to Jaipur under ₹15,000' as structured output.
  2. Write validate(plan) covering unknown dependencies, cycles, missing done_when and too many subtasks. Feed any problems back to the planner once.
  3. Write three fake tools: search_flights, search_hotels, get_weather. Make search_hotels return nothing under ₹2,000 a night.
  4. Write an executor that runs ready subtasks, passes each its dependencies' results, and records a status per subtask.
  5. When a subtask fails, call the planner again with the completed results and the problem, and execute the new plan. Allow one replan.

You are done when

  • The printed to-do list shows statuses changing as the run proceeds.
  • Independent subtasks are executed in the same wave.
  • After the hotel search fails, completed flight results are kept and only the remainder is replanned.
  • A plan with a deliberate cycle is rejected before anything runs.

Stretch. Use a large model for the planner and a small one for the executors, and compare the total cost with using the large model for everything.

Practise it: Design a Multi-Agent AI System, Design an Autonomous Research Agent