Multi-Step Planning
Have the model break a goal into ordered steps before it starts acting on any of them.
What you will be able to do
- Explain why a multi-step goal benefits from a plan made before acting
- Write a planner - one model call, no tools, output restricted to a numbered list
- Tell planning apart from ReAct, and connect the two
- Make a planner write steps the agent can actually carry out
- Pass each step the results of the steps before it
- Re-check the goal as you go, instead of following a plan blindly
The idea, in plain English
For anything bigger than one tool call, it helps to have the model plan before it acts: break the goal into an ordered list of concrete steps first, then work through them one at a time.
Two things improve. The agent follows a route instead of deciding afresh every turn. And you get something you can read and check before any action is taken - much easier than spotting a wrong turn halfway through a run.
Planning and ReAct do different jobs. The planner decides what to do: a numbered list, one model call, no tools. ReAct decides how to do one step: Thought, Action, Observation, repeat. Feed each planned step into the ReAct loop from Lesson 1.6 as its own small goal, and you have an agent that can take on multi-part tasks.
Then remember what a plan is: a guess written before anything has been tried. We ran this against a local model, and the results are the most useful part of the lesson - a planner that does not know the tools writes steps nobody can do, steps run in isolation lose what came before, and a plan can be out of date after its first step.
Worked example: A "plan a 3-step task" agent.
1 - Plan before acting
One call, no tools. The planner is told which tools exist, so it writes steps the agent can do: get Mumbai’s weather, get Delhi’s, extract the temperatures, calculate the difference, write the answer.
A real run of "Compare the weather in Mumbai and Delhi and tell me the temperature difference." The planner wrote five steps; the agent stopped after two.
The planner is one plain call
The planner needs no tools and no loop. A system message - break the goal into a short numbered list, output ONLY the list - and the goal as the user message. Keeping the plan as text means you can print it, read it, and feed each line onward.
"Output ONLY the numbered list" is doing real work. Without it, every one of our runs opened with "Here’s a short numbered list of concrete steps..." and one added 22 lines of sub-bullets. With it, four runs out of four were a clean list. "Short" was followed less well: the birthday party came back as seven or eight steps, not three.
Planning and ReAct do different jobs
The planner answers "what should happen, in what order?" It looks ahead once and writes the route. ReAct answers "how do I get this one step done?" It acts, looks at the result, and acts again.
PlannerGoal -> ordered steps. One call, no tools.Executor (ReAct)One step -> Thought, Action, Observation, repeat.TogetherPlan once, execute step by step, re-check as you go.A planner must know the tools
Asked to compare the weather in two cities without being told what the agent can do, the planner wrote steps like "Note down the temperature in Mumbai" - sensible for a person, meaningless to an agent whose only abilities are get_weather and calculate. Birthday-party steps like "book a venue" are the same problem: no tool can do them.
So tell the planner what the agent has, and ask for plain-language steps those tools can do. Ask for plain words, not code: one of our tool-aware prompts produced steps like calculate(substring(get_weather("Mumbai"), ...)) - nested calls to functions that do not exist.
Carry results forward
"Feed each step into the ReAct loop" hides a trap. Run each step on its own and the later steps have nothing to work with. We tried it: the two weather lookups worked, then "extract the temperature" and "calculate the difference" got stuck - they had never seen the weather.
So each step gets the overall goal and the results so far, and each result is added to that list when the step finishes. The step knows why it is running and what has already been found.
A plan is a guess - re-check it
A plan is written before anything has been tried. If step 1 turns up something unexpected - a closed restaurant, a missing file, an answer that arrives early - the rest of the list may already be wrong, and following it anyway wastes work or does harm.
The simplest re-check is one question after every step: do these results already achieve the goal? In one of our runs the answer was yes after step 2 of 5, and the agent stopped. Fuller designs re-plan when a step fails - static planning versus dynamic planning - and LangGraph in Module 3 gives that kind of branching a proper structure.
Watch out: Even with a plan, a small model drifts. In our runs the executor often did the whole goal inside one step, so later steps repeated the same tool calls - 7 to 14 calls where 3 would do. A plan adds structure; it does not add discipline to the model.
Step-by-step code
import ollama
def make_plan(goal):
response = ollama.chat(
model="llama3.1",
messages=[
{"role": "system", "content": "Break the user's goal into a short numbered list of concrete steps. Output ONLY the numbered list."},
{"role": "user", "content": goal}
]
)
return response["message"]["content"]
plan = make_plan("Organize a small birthday party for a friend")
print(plan)1. Decide on a date and time for the party
2. Choose a theme or color scheme for the party
3. Create a guest list and send out invitations
4. Plan the menu and order food and drinks
5. Plan games and activities for the party
6. Prepare decorations and party favors
7. Confirm the party details with the guest of honor
With "Output ONLY the numbered list": 4 of 4 runs a clean list, 7-8 steps
Without it: 4 of 4 runs began "Here's a short numbered list ..."Goal: Compare the weather in Mumbai and Delhi and tell me the temperature difference.
Planner told nothing about the agent:
1. Check the current weather in Mumbai and Delhi.
2. Note down the temperature in Mumbai. <- no tool does this
3. Note down the temperature in Delhi.
4. Calculate the temperature difference between Mumbai and Delhi.
5. Report the temperature difference.
Planner told the tools, asked for plain words:
1. Get the current weather for Mumbai using get_weather("Mumbai")
2. Get the current weather for Delhi using get_weather("Delhi")
3. Extract the temperature values from the weather data for both cities
4. Calculate the temperature difference between the two cities
5. Write the answerimport re
import ollama
MODEL = "llama3.1"
# Reuses get_weather, calculate, TOOLS and run_agent from Lesson 1.6
# (the version with the stop sequence and the Action-first check).
PLANNER_PROMPT = """Break the user's goal into a short numbered list of steps, in plain words.
The steps will be carried out by an agent whose only tools are:
- get_weather(city): the current weather for a city
- calculate(expression): evaluates a maths expression
Only include steps those tools can do, then one last step that writes the answer.
Output ONLY the numbered list."""
STEP = re.compile(r"^\s*\d+[.)]\s+(.+)$", re.MULTILINE)
def ask(system, user):
response = ollama.chat(
model=MODEL,
messages=[{"role": "system", "content": system}, {"role": "user", "content": user}],
options={"temperature": 0},
)
return response["message"]["content"].strip()
def make_plan(goal):
return STEP.findall(ask(PLANNER_PROMPT, goal))
def goal_is_met(goal, results):
verdict = ask(
"Answer only YES or NO.",
f"Goal: {goal}\nResults so far:\n{results}\n\nDo these results fully achieve the goal?",
)
return verdict.upper().startswith("YES")
def run_plan(goal):
steps = make_plan(goal)
print("Plan:", *steps, sep="\n ")
results = []
for number, step in enumerate(steps, start=1):
context = "\n".join(results) or "nothing yet"
answer = run_agent(
f"Overall goal: {goal}\nResults so far:\n{context}\n\nDo only this step: {step}"
)
results.append(f"Step {number}: {answer}")
print(f"Step {number} done: {answer}")
# Re-check after every step: the plan was a guess made before anything ran.
if goal_is_met(goal, "\n".join(results)):
print(f"Goal met after step {number} of {len(steps)} - stopping.")
break
return ask(
"Answer the user's goal in one or two sentences, using only these results.",
f"Goal: {goal}\nResults:\n" + "\n".join(results),
)Each step run on its own, no results carried:
Get the current weather for Mumbai -> rainy, 27°C
Get the current weather for Delhi -> sunny, 34°C
Extract the temperatures -> Agent got stuck - no action found.
Calculate the difference -> Agent got stuck - no action found.
run_plan, four runs across two goals:
"Compare the weather in Mumbai and Delhi ..."
run 1 all 5 steps 14 tool calls "the temperature difference ... is 7°C"
run 2 stopped after step 2 of 5 (goal met) "... is 7°C"
"What is 12 * 8, plus the temperature in Delhi?"
run 1 all 5 steps 12 tool calls "... you get a total of 130"
run 2 all 5 steps 9 tool calls "... the overall answer is 130"
Every final answer correct. Three tool calls would have been enough each time.Tip: Print the plan before you execute it. Reading five lines is the cheapest review an agent will ever get - and the place a human approval step naturally fits (Module 3, Lesson 3.7).
Watch out: A plan made before anything has been tried is a guess. If step 2 turns up something unexpected, the rest of the list may already be wrong - so re-check as you go rather than following it to the end.
Planning at a glance
make_plan(goal)One call, no tools, a numbered list.
Output ONLY the numbered list.
Parse the stepsTurn the list into Python strings.
re.findall(r"^\s*\d+[.)]\s+(.+)$", text, re.MULTILINE)
Tell it the toolsSo every step is something the agent can do.
- get_weather(city) - calculate(expression)
Results so farWhat each step receives from earlier steps.
Results so far:\n{context}Re-checkStop or re-plan when the plan is out of date.
goal_is_met(goal, results)
Try it yourself
The code does not change. Swap the content string and the program does something else entirely.
“Organize a small birthday party for a friend”
“Compare the weather in Mumbai and Delhi and tell me the temperature difference.”
“What is 12 * 8, plus the temperature in Delhi?”
“Book me a table at a restaurant in Mumbai tonight.”
What usually goes wrong
Without a strict format the plan arrives wrapped in introductions and sub-bullets.
✗ "Break the user's goal into a short numbered list of concrete steps."✓ "... Output ONLY the numbered list."It plans for a person, not for your agent - "note down the temperature", "book a venue".
Tool-call syntax in a plan invites functions that do not exist. Ask for plain-language steps and let ReAct choose the actual calls.
✗ calculate(substring(get_weather("Mumbai"), ...))✓ Get the current weather for MumbaiLater steps need earlier results. Pass the goal and the results so far into every step.
✗ for step in steps:
run_agent(step)✓ run_agent(f"Overall goal: {goal}\nResults so far:\n{context}\n\nDo only this step: {step}")The plan predates every result. Re-check after each step, and stop - or re-plan - when it no longer fits.
Key points
- Plan first for multi-step goals: structure, and something to check before anything runs.
- The planner is one call with no tools, restricted to a numbered list.
- Planning decides what to do; ReAct decides how to do each step.
- Tell the planner the agent’s tools, and ask for plain-language steps.
- Give every step the overall goal and the results so far.
- A plan is a guess - re-check after each step and stop when the goal is met.
- Planning adds structure, not discipline: a small model still repeats work.
Quick check before you move on
Quiz
- 1.
Why plan before acting instead of letting the agent improvise step by step?
- 2.
What is the risk of trusting a plan blindly without re-checking it mid-execution?
- 3.
How could you connect this plan to the ReAct loop from Lesson 1.6?
- 4.
Each step ran on its own. The first two worked; "calculate the difference" got stuck. Why?
Interview questions
What is the difference between planning and ReAct?
Planning produces an ordered list of high-level steps for a goal before acting. ReAct executes a step by repeatedly reasoning, choosing a tool, and observing the result. A practical agent uses a planner for the route and ReAct for each step.
What is the difference between static and dynamic planning?
Static planning writes the whole plan once and follows it. Dynamic planning re-evaluates after each step - continuing, stopping early, or re-planning when results make the original plan wrong. Real environments need the dynamic version, because a plan is made before any results exist.
What makes a plan executable by an agent?
Each step must be achievable with the agent’s actual tools, written in plain language rather than code, and able to see the results of earlier steps. A planner that is not told the tools plans for a person instead.
Comments
Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.
Loading comments...
AI
System Design
Backend
- GraphQL8 modules · 69 lessons planned
- Core Python13 modules · 75 lessons planned
- FastAPI5 sections · 20 lessons
- Node.js14 modules · 206 lessons planned
- Node.js Performance7 chapters · 36 topics
- Event Loop Lifecycle6 phases · 3 scenarios
- Docker & Containerization11 modules · 144 lessons planned
- AWS for Developers14 modules · 219 lessons planned
- CI/CD & DevOps Automation10 modules · 134 lessons planned