JJoeven

Curriculum/Agent Architectures

ReAct: Thought, Action, Observation

The classic loop: reason, call a tool, read the result, repeat. Implemented here with a fake model.

intermediate22 min5 / 24

ReAct is the default costume of modern agents: Thought, then Action, then an Observation from the world, until a final answer.

It is a prompting pattern plus the six-part loop you already have. It is not a product. Your product is the tools, the stop conditions, and whether the observation actually came from the executor. Frameworks that say “ReAct agent” are still assembler, model, parser, executor, memory, stop. The costume is the grammar of each model turn.

The thought is not magic. It is extra tokens that often improve tool choice — and that you will later hide from the user, log for debugging, and sometimes strip to save money.

Why the three labels matter

  • Thought — private reasoning; no world change
  • Action — a tool name plus arguments; the only thing the executor runs
  • Observation — tool output, treated as untrusted data, not as new instructions
ReAct cycle
ThoughtActionObserve

Think. Call a tool. Read the result. The model does not write the observation.

ReAct cycle

If you blur the labels, you cannot parse. If the thought contains a fake observation, the model will believe it. If the action is a paragraph (“I will now check the weather”), the parser fails — or worse, a sloppy parser runs something. If the observation is written by the model, you have a story, not a loop.

You usually do not let the model generate the observation. If you do, it will invent search results. The executor writes the observation. The assembler stuffs it into the next context. That is the whole idea.

One action per turn

Classic ReAct is one action per turn. The model thinks, names one tool, the executor runs it, the observation comes back, repeat. Parallel tool calls (tools track) are an optimization on the same loop, not a new architecture. If you run two tools, you still parse, you still execute, you still append observations, you still stop. You just batch.

One action is easier to audit. Two reads in parallel are a speed hack. Two writes in parallel are how you double-refund. Stay serial on writes. ReAct does not require parallel. Do not start there.

Grammar: JSON is how you ship

Keep the grammar tiny. Tag soup (Thought: / Action: / Action Input:) is how 2022 demos worked. JSON is how you ship: {"name": "get_weather", "args": {"city": "Paris"}} plus an optional thought field.

This lesson’s live box still parses the classic tags so you can see the three labels. A production parser should prefer JSON (previous lesson). Either way: the observation is not in the model’s mouth.

Thoughts can be a separate field you log and hide. Do not show chain-of-thought to customers if your vendor or policy forbids it. Do not treat thoughts as a contract a human should approve. HITL later freezes args, not thoughts.

The scratchpad is memory

ReAct papers call the growing thought-action-observation list a scratchpad. That is the memory part of anatomy. Append every turn. Assemble a window of it. Do not grow it without a budget.

The next model call must include the latest observation. If you drop it, the model will finish from the thought it had before the tool ran. That bug gets its own lesson: observe before you finish.

Hide thoughts, keep traces

Users want the weather, not “I should check the weather before advising.” Operators want the thought when the action was wrong. Put thoughts in the operator trace. Put the final answer (and maybe a short why) in the customer UI.

Stripping thoughts from the assembled context on later turns can save money after the action is chosen. Do not strip them from the log.

What ReAct is bad at

Long multi-hop work with a stable path is often cheaper as plan-and-execute (next part). ReAct re-decides every turn. That is good when the world moves. It is wasteful when the next three tools are known.

ReAct is also bad when you needed a workflow: validate, tax, send. A checklist in code will not “forget to validate.” A ReAct thought might. When-not-to-agent is the last lesson in this track.

Fake model, real shape

The live box uses a fake model that returns tag-shaped text. A real model returns the same kind of string (or a structured tool call you still treat as an action). The executor still runs get_weather. The finish line still uses the observation. That split is the architecture.

Common mistakes

  • Letting the model write the observation.
  • Showing thoughts to customers.
  • Parsing tag soup with a hopeful regex and no fail-closed.
  • Many actions per turn including writes.
  • Calling ReAct a product instead of a costume on the six parts.
  • Skipping the observation and finishing from the thought.
Live PythonOpen full playgroundpython
Output
Run to execute this in your browser. Nothing is sent to a server.

The weather came from the tool. The finish line used that observation. The model never wrote the rain itself. Step 1 is get_weather. Step 2 is finish with an umbrella line that mentions 12C and rain — facts from the dict, not from training lore.

This parser is a teaching regex. It is not fail-closed production JSON. If Action is missing, TOOLS[None] would blow up. A real loop would treat a missing action as bad_parse and stop. We keep the happy path visible so the three labels stay obvious.

How agents use this

ReAct is a prompting pattern plus a loop. It is not a product. Your product is the tools, the stop conditions, and the traces. Hide thoughts from customers; keep them in the trace.

The next lesson hardens the rule this demo already follows: you do not finish until the executor has written an observation. After that, we split the costume: plan-and-execute when the path is stable, ReAct when the world moves.

Check your understanding

In ReAct, who should generate the Observation?