AI Agents · Ai

Planning in AI Agents — ReAct, Plan-and-Execute

Planning in AI Agents — ReAct, Plan-and-Execute is the work that defines the next phase of enterprise software.

John Kihiu12 min read

Planning is the difference between an agent that reacts one tool call at a time and one that decides up front what a multi-step task actually requires. For simple lookups you don't need it — call a tool, get an answer, done. For anything that requires gathering information from several places before acting, an explicit planning step usually beats letting the model improvise its way there one call at a time.

ReAct: plan as you go

The ReAct pattern (Reason, Act, Observe) interleaves reasoning and action: the model thinks about what to do next, takes one action, observes the result, and reasons again before the next action. It's the default behind most function-calling agent loops because it's simple and it adapts naturally when a tool result changes what's actually needed. The weakness shows up on longer tasks — with no forward plan, the model can wander, repeat a step it already did, or lose track of the overall goal by step six.

Plan-and-execute: decide first, then act

Plan-and-execute splits the work into two phases: first the model produces an explicit ordered plan (a list of steps, each naming what needs to happen), then a separate execution loop works through that plan, calling tools per step. This costs one extra LLM call up front but produces a plan you can log, show to a human before execution starts, and validate against business rules — "step 3 wants to release a payment over the auto-approval limit" is something you can catch before any tool actually runs, which ReAct's step-at-a-time model doesn't give you as cleanly.

PYTHON · PLAN-AND-EXECUTE SKELETON
def plan(task: str) -> list[dict]:
    response = call_model(
        f"Break this task into ordered steps, each naming a tool to call: {task}",
        response_format={"type": "json_schema", "schema": PLAN_SCHEMA},
    )
    return response.parsed["steps"]

def execute_plan(steps: list[dict]) -> list[dict]:
    results = []
    for step in steps:
        if requires_approval(step):
            approve_or_reject(step)  # human gate before execution
        results.append(execute_tool(step["tool"], step["arguments"]))
    return results

The real tradeoff is visibility vs adaptability

ReAct adapts well when tool results change what's needed mid-task — it naturally re-reasons after every observation. Plan-and-execute gives you a reviewable, loggable artifact before anything executes, at the cost of being slower to adapt when step 2's result invalidates the plan for step 4. A practical middle ground: plan up front, but re-plan (not just improvise) when an executed step's result materially contradicts an assumption the plan was built on, rather than silently pushing forward with a now-wrong plan.

A plan is worth writing down even when you don't formalize it

Even in a ReAct loop, prompting the model to state its overall goal and expected remaining steps before each action — not as a separate phase, just as part of its reasoning — measurably reduces the "wandered off task" failure mode, and it gives you something readable in the trace when you're debugging why the agent did what it did.

Bounding plans the same way you bound loops

A generated plan can be wrong — too many steps, a step that doesn't map to any real tool, a step that contradicts an earlier one. Validate the plan structurally before executing it: every step's tool name exists in your registry, the plan has a sane step count (reject and re-plan if the model proposes 40 steps for a 3-step task), and any step touching a write action is flagged for the approval gate discussed in the oversight article on this blog. Don't execute a plan you haven't validated just because it parsed as valid JSON.

When neither pattern is worth it

Most single-tool-call tasks — "what's the status of order SO004821" — don't benefit from either pattern; the overhead of a planning phase adds latency for a task that never had ambiguity about what to do next. Reserve explicit planning for tasks that genuinely span multiple data sources or multiple write actions where the ordering and dependencies matter. If you can enumerate every possible plan for a task class in advance, you don't need the model to generate one at all — a deterministic workflow is more reliable and cheaper.

Task shapePattern
Single lookup, no ambiguityDirect tool call, no planning
Multi-step, adapts based on resultsReAct
Multi-step, needs review before executionPlan-and-execute
Fixed, enumerable sequenceDeterministic workflow, no LLM planning needed

Wrapping up

Use ReAct for adaptive multi-step tasks where you're comfortable letting the agent reason as it goes, and plan-and-execute when you need a reviewable artifact before anything with real consequences executes. Validate any generated plan structurally before running it, and don't reach for either pattern on tasks simple enough for a single, unambiguous tool call.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.