The Plan That Was True When It Was Written
A refund-queue agent gets a task with an obvious shape:
user: For order 88213, check refund eligibility, issue the refund if eligible,
then email the customer.
The first implementation asks the model for a plan up front and runs the steps in order. Step 1 returns: not eligible — the order shipped 40 days ago against a 30-day window. Steps 2 and 3 are now nonsense, but the executor runs them anyway: it refunds an ineligible order, then emails the customer to announce it.
Nothing crashed. The plan was true when it was written and false by the time it ran. That gap is the whole subject here: a plan is a prediction about observations you have not collected yet, and your strategy decides what happens when the prediction is wrong.
Three shapes dominate practice:
- ReAct — reason and act one step at a time; the next action is chosen only after the previous observation arrives.
- Plan-then-execute — draft the full step list first, then run it as written.
- Hierarchical — a planner delegates subtasks to worker agents and merges what they report.
Each is correct under a different assumption about how predictable the observation stream is. The machinery that runs any of them already exists — The Core Agent Loop gives you think, act, observe, and a step cap. This topic decides what the think phase actually does with that cycle.
Three Ways to Order a Task
Pick the strategy by asking one question: how much of the plan can be correct before I see any data?
| Strategy | Shape | Choose it when | Breaks when |
|---|---|---|---|
| ReAct | think → act → observe, one step per model call | the next action depends on data you have not seen yet | tasks need a global ordering of steps, and per-step model calls inflate latency |
| Plan-then-execute | draft full step list, then run it in order | structure is predictable and steps are mostly independent | an early observation invalidates a later step, as with the refund eligibility check |
| Hierarchical | planner hands subtasks to worker agents | subtasks are independent and each is deep enough to need its own tool loop | workers need each other's results mid-task, and merging partial reports loses context |
ReAct never acts on stale information because it has no information until it acts. Every step costs a model call, so latency scales with step count, and there is no artifact to review before money moves.
Plan-then-execute buys the opposite trade: a reviewable step list, predictable cost, and the freedom to run independent steps concurrently. It converts a wrong assumption directly into a wrong action — the refund failure above is exactly this strategy without a verification step.
Hierarchical planning pushes detail down: the parent writes check eligibility → issue refund → notify customer and each line becomes a worker's own bounded loop with its own tools. The parent's plan stays valid because workers absorb the messy part. The risk moves to merging — a worker's finding that should change a sibling's work arrives too late — which is why workers should report back before the parent commits the next step.
The table settles what to run. The harder decision is what to do when the world contradicts the plan.
Executing a Plan and Replanning When It Fails
The two executors below take the same goal and the same first plan:
def run_fixed(plan, act):
trace = []
for step in plan:
obs = act(step["tool"], step["args"])
trace.append((step["name"], obs))
return trace # steps 2 and 3 still run after step 1 disproved them
def run_replan(goal, plan, act, ask, verify):
done, i = [], 0
while i < len(plan):
step = plan[i]
obs = act(step["tool"], step["args"])
done.append((step["name"], obs))
if not verify(step, obs): # this observation invalidates what follows
plan = plan[:i + 1] + ask.replan(goal, done) # keep history, replace the tail
i += 1
return done
Executed against the refund request:
goal: check eligibility for order 88213, refund if eligible, email the customer
plan v1: [check_eligibility → issue_refund → email_customer]
run_fixed:
check_eligibility → {"eligible": false, "shipped_days": 40}
issue_refund → REFUNDED 62.40 # a step the observation just killed
email_customer → "Your refund is on the way."
run_replan:
check_eligibility → {"eligible": false, "shipped_days": 40}
verify failed → plan v2: [check_eligibility → flag_exception → email_customer]
flag_exception → {"case": "EX-2210", "owner": "policy-team"}
email_customer → "We could not refund order 88213 automatically."
plan[:i + 1] is the line to study: completed steps stay in the plan as evidence, and only the tail is replaced. Replanning from scratch discards observations the run already paid for, so the new plan repeats them and burns steps.
verify is a small predicate, not a second model — obs.get("eligible") is not False, a schema check, a status code. It runs after every step because any observation can invalidate any later step.
How it goes wrong:
- Blind execution. The fixed executor above: a disproved step still runs, and the cost lands on the customer.
- Replan thrash. Rewriting the plan after every step means no plan is ever executed to completion. Replan only when
verifyfails. - A stale plan handed to workers. Hierarchical runs re-dispatch subtasks from a plan the parent has already replaced.
- Never replanning. Following a dead plan because the team agreed on it is the same bug as blind execution, one decision later.
Replanning is also a budget question — each revision is another model call against your per-task allowance, so bound it alongside the loop caps in Stopping Conditions and Budgets. Once the plan runs, the next topic grades what it produced: Reflection and Self-Critique.
Quiz
1. A task's next action depends on results you have not retrieved yet. Which strategy fits best?
2. The refund agent executed issue_refund after check_eligibility returned eligible: false. What failed?
3. In run_replan, why does the new plan start with plan[:i + 1] rather than a fresh list?
4. What is the main risk of hierarchical planning?
Flashcards
Question
What question picks between ReAct, plan-then-execute, and hierarchical planning?
Click to reveal answer
Answer
How much of the plan can be correct before you see any data? ReAct: none of it needs to be. Plan-then-execute: all of it. Hierarchical: the decomposition, while workers absorb the detail.
Question
When does plan-then-execute outperform ReAct?
Click to reveal answer
Answer
When structure is predictable and steps are independent: you get a reviewable plan, predictable cost, and concurrent execution instead of one model call per step.
Question
What is the replan trigger in iterative replanning?
Click to reveal answer
Answer
A verify predicate that fails after a step — the observation contradicts a later step's assumption. Replanning on every step causes thrash and no plan ever completes.
Question
How much of a plan does a replan replace?
Click to reveal answer
Answer
Only the tail after the failed step: plan[:i + 1] keeps completed steps as evidence so already-paid-for observations are not recomputed.
Question
What does hierarchical planning delegate, and what must the parent still do?
Click to reveal answer
Answer
Each subtask becomes a worker's own bounded tool loop; the parent keeps ordering, merges reports, and should re-dispatch when a report invalidates a sibling's work.
Revision Notes
Key Takeaways
- 1. A plan predicts observations you have not collected; strategy choice decides what happens when that prediction is wrong
- 2. ReAct never acts on stale data; plan-then-execute trades that safety for reviewability, cost predictability, and concurrency
- 3. Replan only when a verify predicate fails, and keep completed steps so their observations are not recomputed
- 4. Hierarchical planning moves detail into workers but moves risk into merging their reports
Interview Tips
- • State the decision rule — predictability of the observation stream — before comparing the three strategies
- • Walk the refund example end to end: where verify fires, what the new plan keeps, what it drops
- • Name the difference between replanning (reactive, verify-triggered) and re-planning on a schedule (thrash)
- • Say how you bound replan revisions, since each revision is another model call
Cheat Sheet
Planning Strategies — Cheat Sheet
Decision rule: how much of the plan can be correct before any data is seen?
| Strategy | Shape | Wins | Fails |
|---|---|---|---|
| ReAct | think→act→observe per step | exploratory tasks, data-dependent next action | no upfront artifact; latency scales with steps |
| Plan-then-execute | draft list, run in order | predictable structure, independent steps | early observation disproves a later step |
| Hierarchical | planner → worker agents → merge | deep independent subtasks | cross-subtask findings arrive at merge time |
Replanning loop
while i < len(plan):
obs = act(plan[i]["tool"], plan[i]["args"])
done.append((plan[i]["name"], obs))
if not verify(plan[i], obs): # observation breaks the tail
plan = plan[:i + 1] + ask.replan(goal, done)
i += 1
Rules
- verify = cheap predicate (field check, status code), run after every step
- Replace the tail only — completed steps are paid-for evidence
- Replan on verify failure, never on a timer or every step
- Cap revisions: each one is a model call against the task budget
Failure modes: blind execution · replan thrash · stale plan handed to workers · never replanning a dead plan
Continue Learning
Before You Start
- No prerequisites listed.