SevyDevy

Guide · AI and GenAI

Plan-and-Execute vs ReAct Agents: Plan First or Step by Step

23 min readUpdated By Saket Bhatnagar

Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.

In short

  • A plan and execute agent asks the model (the large language model behind the agent) for the whole plan first, then runs the steps in code. A ReAct agent decides one step at a time.
  • ReAct stands for reasoning plus acting: the model writes a short thought, calls one tool (a function such as a flight search), reads the result and decides again.
  • Planning up front needs fewer model calls, lets independent steps run at the same time, and gives you a plan stored as data that code can check before anything runs.
  • Step by step wins when each result changes what you do next. When a plan breaks, a good design keeps finished results, rewrites only the rest, caps the rewrites and calls a person.
  • Beginners can stop after the section on when to use each style. Engineers then get a runnable executor with parallel waves and capped replanning, the token maths of ReAct, and what the papers' headline numbers measured.

What goes wrong when an AI agent decides every step as it goes?

An agent that decides every step as it goes asks the model what to do after every single result. On a long task that means dozens of model calls, a growing bill and choices made too early. A plan and execute agent fixes this by asking for the whole plan first.

Picture a common setup. A travel company builds an AI trip planner for multi-city holidays in India. You type your cities, dates and budget, and it returns a day-by-day plan with flights, buses and hotels. The setup is made up, but the problem is real.

The team's first version decides one step at a time. They test it with a typical request: a family of four, Delhi to Jaipur, Udaipur and Jodhpur, seven days, Rs 1.2 lakh (120,000) in total. The answer is usable, but the test notes list four problems.

  • It searched Jaipur hotels before checking any flight. The only cheap flight landed a day later, so it had to search hotels all over again.
  • It made 26 model calls for one trip and took about 50 seconds to reply (both made up).
  • Twice it spent most of the budget on the first city, leaving too little for Jodhpur at the end.
  • Each run took a different path, so the team could not tell users what the planner would do next.

The obvious question is why not decide the whole route first, the way a good travel agent would. That is the plan and execute pattern. This guide shows what it buys, what it costs, and where the team kept the step-by-step style anyway.

What is a ReAct agent?

A ReAct agent is an agent where the model writes a short thought, picks one action, reads the result, and repeats. The name means reasoning plus acting. It comes from a 2022 paper by Yao and others.

An agent here means a program that lets a model call tools in a loop until the task is done. In ReAct, the thought is a note the model writes for itself. The action is one tool call, such as search_flights. The observation is the tool's reply, which goes back into the history.

The paper tested this on question answering, fact checking, a text-based household game and a shopping website. On the game and the shopping site it beat methods trained on many examples or by trial and reward. The margins were 34 and 10 points of success rate, with only one or two examples in the prompt.

The team's first version is exactly this loop, the naive version, and it is worth seeing why it struggles. Follow the loop from the round limit through the thought, the tool and the result.

The trip planner's first version as a ReAct agent

Notice the loop: one tool call per round, and the model is asked again after every result. Only the round limit stops it.

Diagram connections:

  • Family's trip request to Under 30 rounds?
  • Under 30 rounds? to Model writes a thought (Yes)
  • Under 30 rounds? to Travel desk takes over (No)
  • Model writes a thought to Code runs one tool (Needs a tool)
  • Model writes a thought to Itinerary for the family (Done)
  • Code runs one tool to Result joins the history
  • Result joins the history to Under 30 rounds? (Next round)
  • Family's trip request: Delhi plus 3 cities, 7 days, Rs 1.2 lakh
  • Under 30 rounds?: A limit set in the team's code
  • Model writes a thought: Decides the very next step only
  • Code runs one tool: A flight, bus or hotel search
  • Result joins the history: The history grows every round
  • Itinerary for the family: Written when the model says done
  • Travel desk takes over: A person finishes the plan

The strength is flexibility. Every step sees the latest result, so the agent can react to a surprise straight away. The weakness is that it never looks far ahead, which is exactly what went wrong with the Jaipur hotels.

What is a plan and execute agent?

A plan and execute agent splits the work into two jobs: a planner writes the full list of steps first, and an executor runs them. The model plans once, then your code works through the list. The model comes back only if something breaks, and once more to write the answer.

The parts, with one everyday picture

Picture a family shopping for a wedding in Old Delhi's crowded bazaar. One way is to decide shop by shop. Buy the lehenga, then choose bangles to match it, then think about sweets. That is ReAct.

The other way is the list written the night before. Lehenga from one lane, sherwani from another, then bangles, sweet boxes and wedding cards. Each cousin takes part of the list, and they all shop at once. That is plan and execute.

Each part of the agent maps to a part of that picture. The names below are the ones this guide uses from here on.

  • Planner: the person writing the list. One model call turns the request into numbered steps, each naming a tool and its inputs. Example: search_hotels for Udaipur, nights 3 to 5.
  • Plan: the list itself. It is plain data, such as a JSON list (a common text format for lists and fields), that your code can read, check and save. When the plan exists, nothing has run yet.
  • Executor: the cousins. Usually plain code that runs each step's tool, several at once where the steps do not depend on each other.
  • Replanner: the phone call home when a shop is shut. A model call that rewrites only the remaining steps. Finished steps stay finished.
  • Writer: the person who sums up the day. A final model call that turns all the results into the itinerary the family reads.

Where the picture breaks: a cousin can improvise sensibly when a shop is shut. The executor cannot judge anything; it runs only the steps on the list. And the planner can write a list that looks fine but breaks a rule, which is why code checks it.

Where the idea comes from

Plan-and-Solve prompting, a 2023 paper, asked the model to first plan subtasks and then carry them out. It was a prompting method for reasoning questions, not a tool-using agent. Its target was missing-step errors, where the model simply skips a needed step.

ReWOO, also from 2023, turned the idea into an agent with three parts it calls the Planner, Worker and Solver. Each planned step gets a placeholder, written like #E1, for the evidence it will produce. A later step can use #E1 as an input before it is filled.

LLMCompiler, a third 2023 paper, made the plan a graph of tasks with their dependencies, so ready tasks run in parallel. It can also send results back to its planner for a new set of tasks. That is replanning, built into the design.

Here is the trip planner's second version. Start at the request and follow the plan through the check box.

The trip planner as a plan and execute agent

Notice the check box between the planner and the executor. Every plan, first or rewritten, passes through plain code before any search runs.

Diagram connections:

  • Family's trip request to Planner writes the plan
  • Planner writes the plan to Code checks the plan
  • Code checks the plan to Executor runs the steps (Plan is valid)
  • Executor runs the steps to Flight, bus, hotel search (Run each step)
  • Executor runs the steps to Replanner fixes the rest (Step failed)
  • Replanner fixes the rest to Code checks the plan (New remaining steps)
  • Executor runs the steps to Writer builds the itinerary (All steps done)
  • Writer builds the itinerary to Day-by-day plan
  • Family's trip request: Cities, dates, budget, travellers
  • Planner writes the plan: One model call, about 8 steps
  • Code checks the plan: Known tools, inputs exist, dates in order
  • Executor runs the steps: Plain code, parallel where it can
  • Flight, bus, hotel search: Read-only partner searches
  • Replanner fixes the rest: Called only when a step fails
  • Writer builds the itinerary: Final model call
  • Day-by-day plan: Shown to the family to review
  1. The planner writes eight steps

    Flight Delhi to Jaipur on day 1, bus to Udaipur on day 3, bus to Jodhpur on day 6, flight home on day 7. Three hotel searches, then a budget total.

  2. Code checks the plan

    Every tool exists, every input comes from an earlier step, and the hotel nights add up to six. A plan that fails this goes back to the planner once.

  3. Independent steps run together

    The hotel searches do not depend on each other, so the executor runs them at the same time as the flight and bus searches.

  4. The budget step adds it up

    Flights, buses and the cheapest good hotels come to Rs 1.08 lakh (made up). That leaves Rs 12,000 for food and sightseeing.

  5. The writer replies

    One last model call turns the results into a day-by-day plan. In total the trip took 2 model calls and about 14 seconds (made up).

What just happened? The model was asked twice instead of 26 times, and it saw the whole route before choosing any hotel. The Jaipur mistake is much harder to make, because the planner fixes the flight day and the hotel nights together, and code checks that the dates line up. If no affordable flight fits that day, the step fails and the replanner handles it.

What is the difference between plan-and-execute and ReAct agents?

The main difference is when the model decides. ReAct decides each step after seeing the last result, while plan and execute decides most steps before running any of them. Almost every other difference follows from that one choice.

ReAct and plan and execute compared on the trip planner (calls and times made up)
ReAct agentPlan and execute agent
When the model decidesBefore every stepOnce at the start, again only on a replan
Model calls per trip20 to 302 to 4
Reply time40 to 60 seconds12 to 20 seconds
Steps at the same timeNot in the classic loop, one action per roundNatural for independent steps
Handling surprisesAt every stepOnly through the replanner
Check before runningNo, steps appear one by oneYes, the whole plan is data
TestingRead whole runsTest plans and each step separately
Best fitOpen-ended exploringTasks whose shape is known

When should you use ReAct instead of plan and execute?

Use ReAct when you cannot know the steps until you see results, such as exploring or debugging. Use plan and execute when the shape of the task is known before you start. These signs point toward ReAct:

  • The next step truly depends on the last result, like following clues in a search.
  • Tasks are short, so a few rounds cost little anyway.
  • Surprises are common, so a plan would be rewritten most of the time.
  • You are still learning what the task needs, and want to read how the model explores.

These signs point toward plan and execute. The more of them your task shows, the more a plan up front pays off:

  • The task has a known shape, like transport, then hotels, then a budget total.
  • Many steps are independent and could run at the same time.
  • You want to check, save or show the plan before anything runs.
  • Cost and reply time matter, and a ReAct history would keep growing.

A 2025 paper on securing agents, covered later in this guide, makes the same point. In its operating-system assistant case study, it notes that a fixed set of commands is hard to commit to when each command depends on earlier results.

Do not expect any planner to be perfect, either. TravelPlanner, a 2024 benchmark (a fixed set of test tasks for comparing systems), gave agents 1,225 travel requests and six tools over nearly four million records. Its abstract reports that even a leading large model of the time met every constraint in only 0.6% of its plans.

That result is why the team checks every plan in code. Dates in order, nights adding up and a budget total are rules code can test exactly. The model's job is to propose; the code's job is to refuse what breaks the rules.

Mixing both in one design

You rarely have to choose one style for everything. The trip planner's final design uses a plan for the overall trip. One step, finding family-friendly things to do in each city, runs as a small ReAct loop of at most 5 rounds.

That step needs exploring. A palace may be closed on Mondays, or a boat ride may be too long for small children. The rest of the trip has a known shape, so it stays a plan.

Why is planning up front often faster and cheaper?

Planning up front is often cheaper because the model is called a few times instead of once per step. It is often faster because steps that do not depend on each other can run at the same time. The cost gap grows faster than the number of steps.

Why ReAct's bill grows faster than its steps

Model services usually charge by tokens, small pieces of text that are often a word or part of one. In a ReAct loop, every round sends the request, the tool descriptions and the whole history so far. So round 20 sends rounds 1 to 19 again.

Pause and predict. A 13-round run sends 49,400 input tokens in the sizes below. Does a 26-round run send double that? Work it out before reading the table.

Input tokens per trip with made-up sizes: 2,000 tokens of request and tool descriptions, 300 tokens added per round
Agent runModel callsTokens sent in the last callInput tokens for the whole trip
ReAct, 13 rounds135,60049,400
ReAct, 26 rounds269,500149,500
ReAct, 52 rounds5217,300501,800
Plan and execute, planner plus writer24,4006,400

Doubling the rounds from 13 to 26 tripled the input, from 49,400 to 149,500 tokens. Each round pays for every round before it, so the total grows roughly with the square of the rounds. The writer's 4,400 tokens assume eight step results of 300 tokens each. That row is the pure pattern, with no replan and no activity loop, so a real run sends more.

At 4,000 trips a day, 149,500 tokens a trip is about 59.8 crore (598 million) input tokens a day. The plan and execute version sends about 2.56 crore (25.6 million). You can rerun the maths with your own sizes in a few lines of Python.

One caveat for engineers. If your provider offers prompt caching, which reuses an unchanged start of a prompt at a lower price, the repeated history can cost less. It still counts toward the model's context limit, the most text it can read in one call, and still adds reading time.

What the research numbers actually measured

ReWOO's abstract headlines 5 times the token efficiency and a 4% accuracy gain on HotpotQA, a benchmark of questions that need several lookups. Its main results table shows the detail. ReWOO used about 1,986 tokens per question against 9,795 for ReAct.

On accuracy, the same table shows 42.4 for ReWOO against 40.8 for ReAct, a 1.6-point gap, graded by a large model acting as a scorer. So the token saving is large and the accuracy gain on HotpotQA is small. The paper also reports that ReWOO held up better when a tool failed.

LLMCompiler reports up to 3.7 times lower latency (waiting time), up to 6.7 times lower cost and up to about 9% better accuracy than ReAct. Each "up to" figure comes from the paper's most favourable benchmark for that measure, and the gains varied by model. On HotpotQA its speed-up was about 1.8 times. On a shopping benchmark it was slightly slower than ReAct, though it succeeded on many more tasks.

The lesson is to measure on your own tasks. Your speed-up depends on how many of your steps are independent, and your saving depends on how long your ReAct runs would have been.

How do you run independent steps at the same time?

You run independent steps at the same time by treating the plan as a dependency graph. Each round, run every step whose inputs are ready, wait for that wave to finish, then repeat.

The trip plan as a dependency graph

Notice that every search hangs off the plan directly, so all seven searches, drawn as five boxes, start together. Only the budget step waits, because it needs every price.

Diagram connections:

  • Checked plan to Flight searches
  • Checked plan to Bus searches
  • Checked plan to Jaipur hotels
  • Checked plan to Udaipur hotels
  • Checked plan to Jodhpur hotels
  • Flight searches to Budget total
  • Bus searches to Budget total
  • Jaipur hotels to Budget total
  • Udaipur hotels to Budget total
  • Jodhpur hotels to Budget total
  • Budget total to Writer builds the itinerary
  • Checked plan: Dates and cities fixed by the planner
  • Flight searches: Delhi to Jaipur, Jodhpur to Delhi
  • Bus searches: Jaipur to Udaipur, Udaipur to Jodhpur
  • Jaipur hotels: Nights 1 and 2
  • Udaipur hotels: Nights 3 to 5
  • Jodhpur hotels: Night 6
  • Budget total: Needs every search result first
  • Writer builds the itinerary: Final model call

The code below is a cut-down trip with four searches and a budget step. The planner's output is hard-coded so it runs without a model. Placeholders like #h1 mean the result of step h1, the same idea as #E1 in ReWOO. It needs only Python 3.

python
import time
from concurrent.futures import ThreadPoolExecutor

# The planner's output is plain data. "#h1" means "the result of step h1".
plan = [
    {"id": "f1", "tool": "flight", "args": {"origin": "Delhi", "to": "Jaipur"}},
    {"id": "h1", "tool": "hotel", "args": {"city": "Jaipur", "nights": 2}},
    {"id": "h2", "tool": "hotel", "args": {"city": "Udaipur", "nights": 3}},
    {"id": "h3", "tool": "hotel", "args": {"city": "Jodhpur", "nights": 1}},
    {"id": "b1", "tool": "budget", "args": {"items": ["#f1", "#h1", "#h2", "#h3"]}},
]

RATES = {"Jaipur": 5000, "Udaipur": 6000, "Jodhpur": 4500}  # rupees a night

def flight(origin, to):
    time.sleep(0.5)  # stands in for a slow partner search
    return 4200

def hotel(city, nights):
    time.sleep(0.5)
    return RATES[city] * nights

def budget(items):
    return sum(items)

TOOLS = {"flight": flight, "hotel": hotel, "budget": budget}

def refs(step):
    out = []
    for v in step["args"].values():
        for x in (v if isinstance(v, list) else [v]):
            if isinstance(x, str) and x.startswith("#"):
                out.append(x[1:])
    return out

def check_plan(plan, done=()):
    seen = set(done)
    for step in plan:
        if step["id"] in seen:
            raise ValueError(f"step id {step['id']} is used twice")
        if step["tool"] not in TOOLS:
            raise ValueError(f"unknown tool {step['tool']}")
        missing = [r for r in refs(step) if r not in seen]
        if missing:
            raise ValueError(f"{step['id']} uses {missing} before they exist")
        seen.add(step["id"])

def fill(v, done):
    if isinstance(v, list):
        return [fill(x, done) for x in v]
    if isinstance(v, str) and v.startswith("#"):
        return done[v[1:]]
    return v

def call(step, done):
    args = {k: fill(v, done) for k, v in step["args"].items()}
    return TOOLS[step["tool"]](**args)

def run(plan, done):
    todo = [s for s in plan if s["id"] not in done]
    wave = 0
    while todo:
        ready = [s for s in todo if all(r in done for r in refs(s))]
        if not ready:
            raise ValueError("no step can run")
        wave += 1
        start = time.time()
        with ThreadPoolExecutor() as pool:
            futures = {s["id"]: pool.submit(call, s, done) for s in ready}
        errors = []
        for sid, fut in futures.items():
            try:
                done[sid] = fut.result()
            except Exception as err:
                errors.append(err)
        ids = " ".join(futures)
        print(f"wave {wave}: {ids} ({time.time() - start:.1f} s)")
        if errors:
            raise errors[0]
        todo = [s for s in todo if s["id"] not in done]
    return done

check_plan(plan)
done = run(plan, {})
print(f"total: Rs {done['b1']:,}")

Save it as plan_run.py and run python3 plan_run.py. On Python 3.13 it printed the three lines below. Each search sleeps for 0.5 seconds, so four searches one after another would take 2 seconds.

text
wave 1: f1 h1 h2 h3 (0.5 s)
wave 2: b1 (0.0 s)
total: Rs 36,700

What just happened? check_plan refused nothing, so the plan ran. The four searches ran together as wave 1 and took about 0.5 seconds in total. The budget step waited for wave 2, because its inputs were placeholders that only exist after wave 1.

To see check_plan earn its place, add a step with the tool send_email. It raises unknown tool send_email before any search runs. A step that uses #h9, which no step produces, is refused the same way.

What happens when a plan breaks halfway?

When a step fails, a plan and execute agent calls a replanner, which keeps the finished results and rewrites only the remaining steps. Without one, the executor would follow a plan that no longer fits.

Here is a realistic case for the trip planner. The family's Udaipur dates fall on a festival weekend, and every hotel within budget is full for nights 3 to 5. Back in the bazaar, this is the shop on the list that turns out to be shut. A sensible cousin phones home and agrees a change to the list.

The replanner does the phoning home. It gets the request, the finished results and the error, and returns new steps. Here it flips the route: Jodhpur for two nights, then Udaipur for two.

Paste the block below under the first one and run the file again. The new hotel function fails for Udaipur on three nights. The replanner is a fixed answer standing in for a model call, and MAX_REPLANS caps the rewrites.

python
class StepFailed(Exception):
    pass

def hotel(city, nights):  # this weekend, Udaipur is full for 3 nights
    time.sleep(0.5)
    if city == "Udaipur" and nights == 3:
        raise StepFailed("no Udaipur rooms for nights 3 to 5")
    return RATES[city] * nights

TOOLS["hotel"] = hotel
MAX_REPLANS = 2

def replanner(done, error):
    # In a real system this is a model call. Here it returns a fixed answer.
    return [
        {"id": "h3b", "tool": "hotel", "args": {"city": "Jodhpur", "nights": 2}},
        {"id": "h2b", "tool": "hotel", "args": {"city": "Udaipur", "nights": 2}},
        {"id": "b2", "tool": "budget", "args": {"items": ["#f1", "#h1", "#h3b", "#h2b"]}},
    ]

def run_with_replans(plan):
    done, replans = {}, 0
    while True:
        try:
            return run(plan, done)
        except StepFailed as err:
            if replans == MAX_REPLANS:
                return {"handed_to_person": str(err), **done}
            replans += 1
            print(f"replan {replans} after: {err}; kept {' '.join(done)}")
            plan = replanner(done, str(err))
            check_plan(plan, done)

result = run_with_replans(plan)
print(f"total: Rs {result['b2']:,}")

The first three lines of output repeat from the first block. Then it printed these five lines.

text
wave 1: f1 h1 h2 h3 (0.5 s)
replan 1 after: no Udaipur rooms for nights 3 to 5; kept f1 h1 h3
wave 1: h3b h2b (0.5 s)
wave 2: b2 (0.0 s)
total: Rs 35,200

What just happened? The flight and the Jaipur hotel were kept and never searched again. The new steps went through check_plan, just like the first plan, because a rewritten plan is still model output. The old one-night Jodhpur result h3 was also kept, but the new plan does not use it.

That last detail matters in real systems. Keeping finished results is only useful if they still fit the new route. Give the replanner the list of finished steps, and let it say which ones still apply.

If the replanner keeps returning a failing step, the cap stops the loop. In a test where it always asked for Udaipur again, the run gave up after two replans and returned the error for a person to handle.

The team adds one product rule. When a replan changes the order of cities, the itinerary says so in its first line. A family that asked for Udaipur first should never discover the swap on day three.

Is a plan and execute agent safer against prompt injection?

It is safer in one specific way: if the plan is fixed before the agent reads untrusted text, that text cannot add new tool calls. It can still change what flows through the planned steps.

Prompt injection is text hidden in data that tries to give the model orders. On the trip planner, a hotel listing could carry a line telling the model to send the family's phone number to some website.

A 2025 paper on design patterns for securing agents describes a Plan-Then-Execute pattern. It calls the benefit a form of control flow integrity: tool outputs cannot change which actions the agent takes.

The same paper is clear about the limits. Injected text can still alter the arguments of a planned action, such as the body of a planned email. The pattern also does not stop an injection in the user's own request.

There is one more gap, and this one is our reading of the design rather than a claim from the paper. A replanner reads tool results, including hotel listings. So each replan is a moment when untrusted text can reach the part that chooses actions.

  • Make the planner work only from the user's request and the tool list, before any search result exists.
  • Run every plan, first or replanned, through the same code checks on tools and arguments.
  • Give the replanner short error messages written by your code, not raw listing text.
  • Keep every search tool read-only, and treat everything it returns as data, never as orders.

How do you explain plan-and-execute vs ReAct in a system design interview?

Explain both as answers to one question, when the model decides, and then justify each part of your design with a failure it prevents. The steps below give you an order to say it in.

  1. Define both in one line each

    ReAct decides after every observation. Plan and execute writes the plan first, runs it in code, and replans only when something breaks.

  2. Ask about the task's shape

    Ask whether the steps are known in advance and how often results surprise you. That answer picks the style, or a mix.

  3. Draw planner, check, executor, writer

    Show the plan as data, checked in code before anything runs. Mark which steps can run at the same time.

  4. Add replanning with a cap

    Keep finished results, rewrite only the rest, cap the replans and hand over to a person when the cap is hit.

  5. Cover safety

    Plan before reading untrusted data, check every plan, keep tools read-only, and put a person before anything that spends money.

  6. Estimate the cost out loud

    Show that ReAct input grows roughly with the square of the rounds. Then name the sample tasks with known good answers that you rerun before every change.

Questions people ask

Is plan and execute better than ReAct?
Neither is better everywhere. Plan and execute usually needs fewer model calls and lets you check the plan, so it suits tasks with a known shape. ReAct handles surprises at every step, so it suits exploring. A common middle path plans the big steps and runs a short, capped ReAct loop inside the few steps that need it.
Is ReAct still used in AI agents?
Yes, as a loop shape. An agent that calls a tool, reads the result and decides again follows the same pattern, even when the model does not write its thought out. What you control is how long that loop may run. Keep it capped, or run it inside one step of a larger plan.
What is ReWOO?
ReWOO is a 2023 research method whose name stands for reasoning without observation. A Planner writes every step first, with placeholders like #E1 for results. Workers fill in those results, and a Solver writes the final answer. On HotpotQA it used about a fifth of ReAct's tokens, with a small accuracy gain.
Can the executor in a plan and execute agent be a ReAct agent?
Yes, and that is a common hybrid. The plan sets the steps, and one step that needs exploring runs as a small ReAct loop with its own round limit. Keep that loop's tools narrow, and return only its final result to the main plan.
How many times should an agent replan?
Set a small cap, such as one or two replans, and then measure. Look at how often your sample tasks need a replan and why. If most runs replan, your planner is missing information, or the task really needs a step-by-step style. When the cap is hit, hand the work to a person.
Should the planner use a bigger model than the executor?
Often, yes. Planning is one call that shapes the whole run, so a stronger model can be worth its price there. Many executor steps are plain tool calls that need no model at all. Measure plan quality on your own sample tasks before you decide.
Does plan and execute stop prompt injection?
No, but it narrows it. If the plan is fixed before untrusted data is read, injected text cannot add new actions. It can still change the arguments of planned steps, and a replanner that reads tool results reopens the door. Keep tools read-only and check every plan in code.

Sources and further reading

Practise this with Lala

Practise