Skip to main content

Module 5 — Planning and task decomposition

A ReAct loop plans one step at a time. That is enough for short tasks — three or four tool calls — but breaks on questions that require six or more, because the model loses sight of the overall goal by iteration five. The remedy is an explicit plan: a short numbered list written once, updated when new evidence contradicts it, and used by the loop as a compass.

Plan-and-execute, in one figure and one prompt

The pattern splits the run into two phases.

Plan. Given the question, produce a short list of steps — three to seven, each phrased as a subgoal a tool call can accomplish.

Execute. Run the ReAct loop from module 2, but with the plan visible in the system prompt, and require each thought to say which step it is working on.

The plan is generated by the same model, in a separate call, with a dedicated prompt:

PLAN_SYSTEM = """You break a question into an ordered list of subgoals.
Each subgoal must be doable with one tool call from: {tool_names}.
Keep between 3 and 7 steps. Return JSON: {{"steps": ["...", "..."]}}"""

def plan(question: str, tools: dict) -> list[str]:
reply = call_model([
{"role": "system", "content": PLAN_SYSTEM.format(tool_names=list(tools))},
{"role": "user", "content": question},
])
return json.loads(reply.text)["steps"]

On the running example, plan("Has Postgres 17 shipped a native CDC module?") returns something like:

  1. Search the web for "Postgres 17 CDC release notes"
  2. Read the top result from postgresql.org
  3. Cross-check the internal knowledge base for any decision already recorded
  4. Compare the two sources and finish with a sourced answer

The executor then loops through those steps. It knows which subgoal is active because the system prompt tracks a current_step counter, incremented when the model writes "step 2 done" in its thought.

When planning is worth its cost

Planning is not free. It adds one call before the loop and a small planning-vocabulary overhead on every subsequent call. Three heuristics decide whether it earns that cost.

Length. Below four expected tool calls, do not plan. The ReAct loop plans one step ahead just fine over three iterations.

Branching. If the natural way to describe the task uses "first, then, and depending on…", plan. The plan captures the branches the loop would otherwise re-discover at every step.

Reproducibility. When you need to compare two runs of the "same" question, planning helps. The plan is the thing you can diff between runs; the raw transcripts are too noisy.

The break-even we measure on the watch agent: planning adds roughly 8% to the token cost of short runs and removes about 22% from long runs, because it prevents the "lost thought at iteration seven" pattern where the model re-searches instead of finishing.

Replanning: the plan is a hypothesis

Plans made from the question alone are hypotheses about what the tools will return. Reality contradicts them. Two contradictions require replanning.

A step's subgoal is impossible. Step 3 was "compare to internal knowledge base"; the base returned zero relevant hits. Continuing to step 4 pretends a comparison happened.

A new fact reshapes the goal. Step 1's search revealed that "CDC" is not the term used in the ecosystem, and the actual term is "logical replication". The remaining steps are searching for the wrong thing.

The trigger is a thought that starts with "replan". The executor detects the token and calls replan(question, transcript_so_far). The new plan replaces the tail of the old one, keeping the completed steps intact.

def replan(question: str, transcript: list) -> list[str]:
reply = call_model([
{"role": "system", "content": PLAN_SYSTEM.format(tool_names=list(TOOLS))},
{"role": "user", "content": question},
{"role": "system", "content": f"Progress so far:\n{summarise(transcript)}"},
])
return json.loads(reply.text)["steps"]

Two safety belts belong in production. A replan counter — no more than two replans per run — prevents the "endless replanning" failure of module 8. And the replanning prompt must be told not to redo completed subgoals, otherwise the new plan overlaps with the old and doubles cost.

Sub-agents by role, not by cleverness

Some tasks are better decomposed into distinct agents with distinct toolsets.

A researcher sub-agent, with web_search, read_page and internal_kb. Its output is a bulleted list of sourced facts.

A writer sub-agent, with only a finish tool. Its input is the researcher's bullets; its output is prose.

The advantages are concrete. The researcher's tool descriptions do not confuse the writer, because the writer never sees them — this alone pushes past the seven-tool ceiling from module 3. The writer's prompt can be much stricter on style, tone and citation format, because it does not need to explain the search tools. And costs are separable: you can log the researcher's token bill separately from the writer's, and optimize them independently.

The trap: sub-agents that call sub-agents that call sub-agents. Every layer adds latency, adds surface for prompt injection, and makes tracing a nightmare. The rule that survives contact with production is two levels of sub-agents, at most. Beyond that, the abstraction eats you.

Task decomposition without an agent

Not every decomposition needs a plan-and-execute agent. Two lightweight alternatives cover many cases.

Static decomposition. Write the steps in Python. Call search, then read, then compare. This is a chain, not an agent, and if the steps do not vary between questions, that is the right tool. Module 1 warned about the reflex to reach for autonomy first.

Model-generated code. For questions that reduce to structured computation — "for each of these 50 rows, look up the URL and count the mentions of 'CDC'" — the correct decomposition is to have the model write a short Python script, review it, and execute it in a sandbox. The plan is the script. This is what the OpenAI code-interpreter model does under the hood, and it is cheaper than an agent that loops fifty times.

Read your plan before you read your transcript

When the run fails, look at the plan first. Most bad runs have a bad plan: a subgoal too vague ("investigate"), a subgoal impossible for the available tools ("count all mentions in the wild web"), or a subgoal duplicated between two steps. Fix the plan prompt and the transcript will usually stop looking so mysterious.

Summary

  • The plan-and-execute pattern splits the run into a planning call and a ReAct execution loop that tracks a current_step counter.
  • Planning is worth the cost past four tool calls, on branching tasks, or when you need to diff runs.
  • Replanning is triggered by a "replan" thought and is capped at two per run; the new plan does not redo completed subgoals.
  • Sub-agents by role (researcher, writer) beat sub-agents by cleverness; two levels at most.

Next module: self-critique and verification — how to catch the model's own mistakes before they become the user's answer.