Skip to content

Planner-Executor-Replanner: Let Plans Change

The travel assistant makes a plan: West Lake in the morning, Tea Museum in the afternoon, Hefang Street at night.

Then weather changes, or the museum is closed. The old plan is stale. Plan & Solve keeps running. A free-form agent loop may drift too much. Planner-Executor-Replanner sits between them: plan, execute one step, then decide whether to continue, revise, or finish.

One Sentence

Planner-Executor-Replanner turns plan-once execution into plan → execute → check/replan, so long tasks can correct course.

What Breaks Without It

Problem What it looks like Risk
One plan runs to the end Organized Stale plan keeps running
Every step is free-form Flexible Goal drift and cost growth
No replan point Simple Failures propagate

What This Pattern Changes

Who Owns
Planner Creates initial plan
Executor Executes current step
Replanner Decides continue, replan, or final
Python Stores plan, limits steps, traces

Walk Through One Trace

Round Current step Execution result Replanner decision
1 Step A did A continue
2 Step B did B final: All done

In travel, the replanner may replace outdoor afternoon stops after a rain observation.

Flow

flowchart TD
  T["Task"] --> P["Planner creates plan"]
  P --> E["Executor runs current step"]
  E --> R["Replanner checks result"]
  R -->|continue| E
  R -->|replan| P2["Update remaining plan"]
  P2 --> E
  R -->|final| O["Final answer"]

Code Walk

The example has three model roles:

planner = MockLLM(['{"plan":["Step A","Step B"]}'])
executor = MockLLM(["did A", "did B"])
replanner = MockLLM(
    [
        '{"action":"continue","plan":[],"answer":""}',
        '{"action":"final","plan":[],"answer":"All done"}',
    ]
)

Full example:

from __future__ import annotations

from pathlib import Path

from agent_patterns_lab.patterns.planner_executor_replanner import planner_executor_replanner
from agent_patterns_lab.runtime import MockLLM, RunLimits, Tracer


def main() -> None:
    tracer = Tracer()

    planner = MockLLM(['{"plan":["Step A","Step B"]}'])
    executor = MockLLM(["did A", "did B"])
    replanner = MockLLM(
        [
            '{"action":"continue","plan":[],"answer":""}',
            '{"action":"final","plan":[],"answer":"All done"}',
        ]
    )

    result = planner_executor_replanner(
        planner,
        executor,
        replanner,
        task="Do A then B.",
        limits=RunLimits(max_steps=5),
        tracer=tracer,
    )

    print(result.answer)
    trace_path = tracer.export_jsonl(Path(".traces") / "51_planner_executor_replanner.jsonl")
    print(f"[trace] {trace_path}")


if __name__ == "__main__":
    main()

Run:

UV_CACHE_DIR=.uv_cache PYTHONPATH=src uv run --no-sync python examples/51_planner_executor_replanner.py

Nearby Patterns

Pattern Who decides next Use when
Plan & Solve Execute plan in order Plan is unlikely to change
PER Replanner revises mid-run Execution may invalidate plan
ReAct Model chooses each action No explicit long plan needed
LLM Compiler Plan becomes dependency graph Tasks can be parallelized

When To Use It

  • The task is long enough for plans to become stale.
  • Each step produces observations.
  • You want a plan plus controlled revision.
  • continue/replan/final actions can be structured.

When Not To Use It

  • The task is short.
  • Steps are fixed; use workflow.
  • There are no observations to replan from.
  • The replanner has no criteria.

Costs And Common Failures

Failure Symptom Fix
Constant replanning Every step discards plan Limit replan count
Plan drift User goal disappears Keep original goal and constraints
Vague replanner No reason for changes Structured action + reason
Long loop Keeps continuing Add max_steps

PER fits tasks where plans help but may go stale.

If the task can become a dependency graph, read LLM Compiler. If you need to search over plans, read LATS.

References