Build a Multi-Agent Research Assistant
Lesson 4The Planner
Break complex questions into targeted sub-questions first
The Planner, decompose before you search
Lesson 3 revealed the structural gap in ReAct: the agent improvises its search sequence. It searches for what it thinks of at each step, answers when it feels ready, and has no guarantee of covering everything the question asked.
The fix is a Planner, a dedicated step that reads the full question and produces an explicit numbered list of sub-questions before any searching begins. The agent then executes each sub-question with a focused ReAct loop. Coverage is guaranteed by the plan, not by the LLM's step-by-step intuition.
What you're building
ResearchAgent, v0.4
Three phases: Plan (decompose query into sub-questions) → Execute (run a focused ReAct loop per sub-question) → Synthesise (combine all findings into a final answer).
See it first: a research team
One query becomes a plan, the plan fans out to executors, their findings converge in a synthesizer. This is the jump from one agent to a team.
Until now you had one brain. Now a planner breaks the question into sub-questions, executors each research one, and a synthesizer combines the findings. Same idea humans use: divide the work, then merge it.
How one model becomes three roles
One model. Three prompts. Three roles. "Multi-agent" here means different prompts, not different models.
There is still only one LLM. The planner, executor, and synthesizer are the same model given different prompts. "Multi-agent" mostly means different prompts and tools, not different models.
Coverage by construction
The planner writes the checklist before any searching, so nothing is skipped. ReAct never had a list to check against.
The planner writes the checklist before any searching, so every sub-question gets answered. That is how planning guarantees the coverage ReAct could only hope for.
And the new limit you will hit
Coverage is solved, but the executors run one after another. Running them at the same time is Lesson 5.
Coverage is solved, but the executors run one after another, so five sub-questions take five times as long. Running them at the same time is Lesson 5.
You are here
You are at L4. Each step adds one capability the step below it lacked.
The new architecture
Lesson 3 (ReAct only):
query ──→ ReAct loop (improvised) ──→ answer
Lesson 4 (Planner + ReAct):
query ──→ Planner ──→ [sq₁, sq₂, sq₃, …, sqₙ]
│
┌───────────────┼───────────────┐
▼ ▼ ▼
ReAct(sq₁) ReAct(sq₂) … ReAct(sqₙ)
│ │ │
└───────────────┴───────────────┘
│
Synthesise
│
answerEach sub-question gets its own focused ReAct loop. The loops are independent, they share no state. That independence is important for Lesson 5.
Three LLM modes, one interface
The same LLMClient interface handles three different call types. The system prompt determines the mode:
| System prompt says | LLM role | Output format |
|---|---|---|
| "research planner" | Planner | Numbered list of sub-questions |
| "research assistant … step by step" | ReAct executor | SEARCH: or ANSWER: |
| "research synthesiser" | Synthesiser | Prose answer combining all findings |
The agent routes to the right mode by using the right system prompt. The LLM never needs to know which role it is playing across the conversation.
New prompts (given)
PLANNER_PROMPT = """You are a research planner. Given a research question, decompose it
into a numbered list of focused sub-questions that together fully answer the original.
Rules:
- Each sub-question must be independently answerable by a single search.
- Cover every distinct aspect of the original question.
- Output ONLY the numbered list, no preamble, no explanation."""
SYNTHESIS_PROMPT = """You are a research synthesiser. Given an original question and a
set of findings for each sub-question, write a single coherent answer that addresses
the original question completely. Be concise and factual. Acknowledge gaps where
findings were unavailable."""Your tasks
- Write
Planner.plan(query): call the LLM withPLANNER_PROMPT, pass the query as the user message, then use the givenparse_plan()helper to extract the list of sub-questions. - Write
ResearchAgent.run(query): call the planner, loop through sub-questions callingself._react(sq)for each, collect findings, then callself._synthesise(). Print the plan and each executor step whenself.verboseis True.
Three sub-questions, three executors, three findings, all covered. Compare that to Lesson 3 where only two were searched and training data was never touched.
Now scale it up
The new wall, count the steps
The planner produces 5 sub-questions. Each needs 1-2 ReAct steps. Run the cell and read the trace: how many total steps execute, and in what order?
What you built. What's still slow.
The Planner solved the coverage problem. Every sub-question in the plan gets answered, not because the LLM was lucky mid-search, but because the plan was explicit from the start.
But look at the execution trace: Executor 2 waited for Executor 1. Executor 3 waited for Executor 2. The sub-questions are independent, none of them need results from the others. There is no reason to run them sequentially except that the loop goes one at a time.
The bottleneck
Sequential execution means total time = sum of all executor times. With N sub-questions and k steps each, that is N × k steps. If the sub-questions are truly independent, and for a research planner, they usually are, the wall-clock time could be just k steps: the depth of one executor, run in parallel with all others.
Parallel execution is Lesson 5.
Practice it yourself
Build the planner pipeline — parse the plan, route by role, orchestrate the phases. Real Python, hidden tests, no setup.
Numbered text → a clean checklist of sub-questions.
Route the system prompt to plan / react / synthesize.
The orchestration that makes coverage structural.
Where you are
| Capability | Status |
|---|---|
| LLM call | ✓ Lesson 1 |
| Tool use + RAG | ✓ Lesson 2 |
| Multi-step adaptive search | ✓ Lesson 3 |
| Upfront decomposition + guaranteed coverage | ✓ This lesson |
| Parallel sub-question execution | ✗ Lesson 5 |
Quiz
Check your understanding
1 / 7What does the Planner add over ReAct, and why does it 'guarantee coverage'?
Practice it yourself
A planner produces a structure of tasks; the executor layer schedules it. Build the parse-and-schedule pieces, in the browser with hidden tests, no setup:
Turn the LLM's numbered plan into a clean list of sub-questions, exactly this lesson's parse_plan().
When sub-tasks have dependencies, detect a cycle, the safety check before you schedule a plan.
Schedule independent tasks to run concurrently, the exact fix for the sequential wall (Lesson 5).