The Planner / Reasoner
Planner = Strategy
Reasoning = Thinking
Acting = Execution
When a user gives an agent a high-level task, they rarely specify the exact steps. Examples include:
- “Summarize the latest research on fusion energy.”
- “Find the cheapest flights to Tokyo next month.”
- “Compare the performance of RTX 4090 and H100 for machine learning.”
The planner / reasoner (or reasoning-driven planning) converts such vague goals into structured, executable sequences of actions. In modern systems, planning often emerges from structured reasoning produced by the LLM rather than from a separate module.
What the Planner Does
The reasoning + planning process acts as the agent’s decision engine:
Goal → Reasoning → Plan → ActionsExample:
Goal: Compare GPUs for machine learning
Plan:
- Search for recent benchmark data
- Extract key performance metrics (training/inference speed, memory, cost)
- Compare results across workloads
- Summarize findings and trade-offs
Reasoning vs Planning
| Concept | Description |
|---|---|
| Reasoning | LLM inference that generates thoughts and insights |
| Planning | Constructing a concrete sequence of actions (may be explicit or implicit) |
Reasoning produces understanding.
Planning produces structure and coordination.
In practice, planning is often interleaved with reasoning. This pattern is closely related to ReAct-style agents, where thinking and tool use alternate in a tight loop.
Where Planning Fits in the Agent Loop
A common idealized loop is observe → reason → plan → act → reflect.
In real systems it usually looks more like:
while not done: observe reason (LLM) decide / plan next action(s) act (tool calls) reflect & updateMost production agents use a hybrid approach: a high-level plan with reactive execution and dynamic re-planning.
Task Decomposition
Effective planning relies on task decomposition — breaking complex goals into smaller, actionable subtasks.
Example:
Goal: Research the economic impact of electric vehicles
Decomposed Plan:
- Gather recent EV market statistics and adoption trends
- Identify key economic indicators (jobs, investment, costs)
- Analyze industry and supply chain impacts
- Summarize findings with supporting data
What poor planning looks like:
Goal: Find the cheapest flights to Tokyo
Weak Plan:
- Search for flights
- Book the cheapest one
Problem: This ignores baggage fees, layover duration, airline reliability, and travel time — common real-world failure modes when planning doesn’t account for tool constraints and full requirements.
Plans are always constrained by the tools available to the agent and their interfaces (schemas, limitations, and reliability).
Planning Strategies & Trade-offs
| Planning Type | Description | Trade-off |
|---|---|---|
| Single-step / Reactive | Decide only the immediate next action | Fast, flexible, but prone to repetition |
| Linear planning | Fixed sequence of steps | Simple, but brittle |
| Hierarchical | Break into subtasks and sub-subtasks | Better for complex goals |
| Tree search / Multi-path | Explore multiple reasoning paths | More robust, higher compute cost |
Key real-world trade-off:
Deeper planning improves accuracy but increases latency, token usage, and cost. Many systems use selective or dynamic planning depth.
Execution Feedback & Re-Planning
Modern agents treat plans as provisional. After acting, the agent observes results, reflects, and re-plans if needed:
Plan → Act → Observe → Reflect → Update / Re-planThis feedback loop is essential for handling tool failures, missing data, ambiguous goals, and changing conditions.
Reactive vs Planning Agents
Reactive agents rely on local, step-by-step decisions.
Planning agents generate more global structure upfront.
Most production systems are hybrid: they begin with high-level reasoning/planning, execute reactively, and re-plan dynamically when necessary.
Implementing a Simple Planner
Here’s a minimal planner. In practice, plans are often returned as structured output (JSON, graphs, or tool-call sequences) rather than plain text.
from __future__ import annotationsfrom pydantic import BaseModelfrom typing import Listfrom ollama import chat
MODEL = "qwen3.5:9b"
class Step(BaseModel): number: int description: str
class Plan(BaseModel): steps: List[Step]
def generate_plan(goal: str) -> Plan: prompt = f"""You are an agent planner.Break the following goal into a clear, numbered sequence of steps.Consider tool constraints and potential uncertainties.
Goal: {goal}
Return ONLY valid JSON matching this schema:{{ "steps": [ {{"number": 1, "description": "..."}}, ... ]}}"""
response = chat( model=MODEL, messages=[{"role": "user", "content": prompt}], format=Plan.model_json_schema(), # forces structured JSON options={"temperature": 0.2}, )
return Plan.model_validate_json(response.message.content)
if __name__ == "__main__": goal = "Find the best laptop GPU choice for local ML and explain why." plan = generate_plan(goal)
print("\n=== GENERATED PLAN ===\n") for step in plan.steps: print(f"{step.number}. {step.description}") print("\n=== END OF PLAN ===\n")use ollama_rs::{ generation::chat::{request::ChatMessageRequest, ChatMessage}, generation::parameters::FormatType, Ollama,};use serde::{Deserialize, Serialize};
const MODEL: &str = "qwen3.5:9b";
#[derive(Debug, Serialize, Deserialize)]struct Step { number: usize, description: String,}
#[derive(Debug, Serialize, Deserialize)]struct Plan { steps: Vec<Step>,}
async fn generate_plan(goal: &str) -> Result<Plan, Box<dyn std::error::Error>> { let ollama = Ollama::default();
let prompt = format!( r#"You are an expert agent planner.Break the GOAL into a clear, logical, numbered sequence of steps.Consider available tools, constraints, and possible uncertainties.
Goal: {}
Return **ONLY** valid JSON in this exact format (no extra text):{{ "steps": [ {{"number": 1, "description": "First step description here"}}, {{"number": 2, "description": "Second step description here"}} ]}}"#, goal );
let request = ChatMessageRequest::new(MODEL.to_string(), vec![ChatMessage::user(prompt)]) .format(FormatType::Json);
let response = ollama.send_chat_messages(request).await?; let content = response.message.content.trim();
// Robust parsing let plan: Plan = serde_json::from_str(content) .map_err(|e| format!("Failed to parse plan JSON: {}. Raw output: {}", e, content))?;
Ok(plan)}
#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let goal = "Find the best laptop GPU choice for local ML and explain why.";
println!("Generating plan with Ollama (model: {})...\n", MODEL);
let plan = generate_plan(goal).await?;
println!("=== GENERATED PLAN ===\n"); for step in &plan.steps { println!("{}. {}", step.number, step.description); } println!("\n=== END OF PLAN ===");
Ok(())}The plan is stored and updated in the agent’s working memory (short-term scratchpad + long-term context), allowing progress tracking and adaptation over time.
Planning in Modern Agent Frameworks
| Framework | Planning Approach |
|---|---|
| LangChain | Abstractions for tool usage and chaining |
| LangGraph | Stateful graph-based planning and workflows |
| AutoGen | Conversational multi-agent planning |
| CrewAI | Role-based hierarchical planning |
Why Planning Matters
Good planning combined with feedback loops delivers:
- Better tool selection and sequencing
- Reduced impulsive or random actions
- Higher reliability on complex tasks through decomposition
- Easier debugging and control
Planning does not eliminate hallucination, but it structures thinking and reduces randomness during execution.
Looking Ahead
In this article we explored how the planner / reasoner turns high-level goals into actionable plans, the critical role of feedback-driven re-planning, tool constraints, and real-world trade-offs.
In the next article, we’ll examine the Tool Manager — how agents discover, select, and reliably use tools during execution.
→ Continue to 2.5 — The Tool Manager