Agent patternsPatternValidated

Plan-and-execute

Also called: plan-then-act, plan mode

Plan-and-execute writes the whole plan first, then carries out the steps. Separating the two makes the plan reviewable before any work happens, which is the entire point.

James Phoenix
Understanding Data Updated July 26, 2026

ReAct decides the next step after seeing the last result, which is powerful and means you cannot see where it is going until it gets there. Plan-and-execute inverts that: produce the whole plan first, then execute it.

TypeScript
// 1. plan, schema'd so steps are inspectable, not prose
const { object: plan } = await generateObject({
  model,
  schema: z.object({
    steps: z.array(z.object({
      description: z.string(),
      files: z.array(z.string()),
      risk: z.enum(['low', 'medium', 'high']),
    })),
  }),
  prompt: `Plan how to: ${task}. Do not make any changes yet.`,
})

// 2. the gap where a human reads it. most of the value is here
const risky = plan.steps.some((s) => s.risk === 'high')
if (risky) await approve(plan)

// 3. execute, one step at a time
for (const step of plan.steps) {
  await execute(step)
  await checkpoint(step)     // resumable if step 4 of 9 fails
}

That gap between step 1 and step 3 is the pattern. A bad plan is cheap to reject and expensive to discover halfway through.

What it buys

  • Review before cost. You see the shape of the work while changing it still costs nothing.
  • A visible scope. "Nine steps touching four files" is something you can disagree with. A ReAct agent gives you that information only in retrospect.
  • Natural [checkpoints](/context-engineering-dictionary/checkpoint). Steps are boundaries, so a failure at step six resumes at six.

What it costs

The plan is written with the least information the agent will ever have: before any file is read, any test run, any error seen. When reality diverges, a rigid plan marches on regardless.

The usual fix is to re-plan rather than abandon planning:

TypeScript
for (const step of plan.steps) {
  const result = await execute(step)
  if (result.surprising) {
    plan = await replan(task, { done: completed, discovered: result.details })
  }
}

Which is really a hybrid, and hybrids are what most good agents are: plan for scope and reviewability, ReAct within a step where the path is genuinely unknown.

Tip
The highest-value moment is reading the plan, not the diff. A plan that says "refactor the auth module" when you asked for a bug fix has told you the request was misread, and it told you before an hour of work went into it.

Related terms

Engineering context for real systems?

Getting the right information into the window at the right time is most of the job. If you want that thinking applied to your product, that is what I do.

See how I can help