The first tool-calling loop most engineers build works — until it does not, and the way it fails is instructive. You have the parts already: Tool Use, Code Execution & Sandboxing gave you safe, well-described tools, and LLM Engineering: APIs, Inference & Structured Output gave you the API mechanics to wire them to a model. Put them together, hand the loop a question that needs three lookups, and watch: it sometimes calls the right tools in the wrong order, sometimes forgets a result it already fetched, sometimes calls the same tool five times with slightly different arguments like a person patting their pockets for keys they are holding. The loop is sound; what is missing is deliberation. The model is acting without visibly thinking, and an agent that does not reason on the record cannot course-correct, because it has no record of its own reasoning to correct against.
This course builds a complete agent from raw model calls — no frameworks, no orchestration library, nothing you did not write yourself. By the end you will have a working loop, a planner, a reflection layer, memory, persistence, and human approval gates, each added because a measured failure demanded it. When you later take Agent Frameworks in Depth, the frameworks will read as conveniences wrapped around code you have personally written — not magic.
The founding pattern of that whole stack is ReAct — Reasoning and Acting interleaved — introduced by Yao et al. in the 2022 paper ReAct: Synergizing Reasoning and Acting in Language Models. It is a small change to the bare loop with a large effect on reliability, and nearly every agent shipped since, from coding assistants to research agents, is a descendant of it. This lesson covers the pattern, why grounding actions in observations beats planning everything upfront, and the anatomy of the architectures the rest of the course assembles.
Chain-of-thought prompting established that making a model reason in writing before answering buys accuracy — every reasoning token is another forward pass, and the written chain is scratch memory. ReAct applies that insight to tool use: before each action, the model writes a Thought (what do I know, what do I need next); it then emits an Action (a tool call); it reads the Observation (the result); and it thinks again. The trajectory becomes a legible chain of reasoning punctuated by real-world lookups:
Question: Is the current Kyiv temperature above this year's average high?
Thought: I need two facts — the current temperature and the
historical average. Start with current.
Action: get_weather("Kyiv")
Observation: 24°C, clear
Thought: Now the historical average high for this date.
Action: search("Kyiv average high temperature late July")
Observation: ~26°C
Thought: 24 is below 26. I can answer.
Answer: No — at 24°C it's about 2°C below the late-July average of 26°C.
The Thought lines are not decoration; they are the mechanism. They force the model to commit its plan to the context before acting, which means a wrong step is visible — to the model on its next turn, and to you in the logs. When a ReAct agent misfires, the transcript shows exactly which Thought went wrong, turning debugging from archaeology into reading. Here is the pattern as a working loop on the Anthropic Messages API — about forty lines, the reasoning discipline living entirely in the system prompt:
import anthropic
client = anthropic.Anthropic()
REACT_SYSTEM = (
"You solve tasks by interleaving reasoning and tool use. "
"Before each tool call, state one short Thought explaining why. "
"After observing a result, either call another tool or give the "
"final answer. Never repeat a call you have already made with "
"the same arguments. Never do arithmetic yourself — use the tool."
)
TOOLS = [
{"name": "get_weather",
"description": "Current weather for a city.",
"input_schema": {"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]}},
{"name": "calculate",
"description": "Evaluate an arithmetic expression.",
"input_schema": {"type": "object",
"properties": {"expression": {"type": "string"}},
"required": ["expression"]}},
]
def get_weather(city: str) -> str:
return {"Kyiv": "24C clear", "Lviv": "19C rain"}.get(city, "unknown")
def calculate(expression: str) -> str:
# Toy only — you built real sandboxed executors in
# Tool Use, Code Execution & Sandboxing. Use those.
return str(eval(expression, {"__builtins__": {}}, {}))
FUNCS = {"get_weather": get_weather, "calculate": calculate}
def react_agent(goal: str, max_steps: int = 6) -> str:
messages = [{"role": "user", "content": goal}]
for _ in range(max_steps):
r = client.messages.create(
model="claude-sonnet-4-6", max_tokens=1024,
system=REACT_SYSTEM, tools=TOOLS, messages=messages)
if r.stop_reason != "tool_use":
return "".join(b.text for b in r.content if b.type == "text")
messages.append({"role": "assistant", "content": r.content})
results = []
for block in r.content:
if block.type == "tool_use":
out = FUNCS[block.name](**block.input)
results.append({"type": "tool_result",
"tool_use_id": block.id, "content": out})
messages.append({"role": "user", "content": results})
return "Step budget exhausted."
print(react_agent("Is Kyiv or Lviv warmer, and by how many degrees?"))
Kyiv is warmer than Lviv by 5 degrees (24°C vs 19°C).
The obvious alternative to ReAct is to have the model write a complete plan first and then execute it mechanically. It feels more disciplined. It is usually worse, and the reason is the defining property of language models: they will happily produce a confident, detailed plan built on facts they invented. A plan written before any observation encodes the model's assumptions about the world — the shape of an API response, the name of a config file, the content of a search result. The moment reality disagrees, a plan-first executor either plows on executing nonsense or halts. A ReAct agent, deciding one step at a time, folds each observation into its next Thought: the wrong assumption dies at step two instead of poisoning steps two through nine.
The original paper measured exactly this. On knowledge tasks (HotpotQA, FEVER), chain-of-thought alone hallucinated facts mid-reasoning; ReAct grounded each claim in a Wikipedia lookup and cut those hallucinations sharply, with the best results coming from combining the two. On interactive environments the gap was wider still: on ALFWorld and WebShop, ReAct beat imitation- and reinforcement-learning baselines by 34 and 10 absolute points of success rate — from one or two in-context examples, no training. Acting without reasoning wandered; reasoning without acting fabricated; interleaving them fixed both.
One update for 2026: you will rarely write literal
Thought: lines anymore. Reasoning models — Claude
with extended thinking, DeepSeek-R1, the o-series — internalize
the Thought step as native chain-of-thought before every tool
call, trained in rather than prompted in. What survives, and what
you are really building in this course, is the loop shape
ReAct defined: reason, act, observe, repeat, with every action
grounded in the freshest observation. The pattern moved from
prompt engineering into the model; the architecture around it is
still your job.
def guarded_agent(goal: str, *, max_steps=8, max_tokens=50_000,
max_calls_per_tool=3) -> str:
messages, tool_counts, tokens_used = [{"role": "user", "content": goal}], {}, 0
for step in range(max_steps): # 1. step budget
r = client.messages.create(...) # (as before)
tokens_used += r.usage.input_tokens + r.usage.output_tokens
if tokens_used > max_tokens: # 2. token budget
return "Token budget exceeded; returning best effort."
if r.stop_reason != "tool_use":
return final_text(r)
for block in tool_calls(r):
tool_counts[block.name] = tool_counts.get(block.name, 0) + 1
if tool_counts[block.name] > max_calls_per_tool: # 3. per-tool cap
result = (f"error: {block.name} called too many times; "
"stop retrying and use what you have.")
else:
result = FUNCS[block.name](**block.input)
... # feed result back
return "Step budget exhausted."
Notice the per-tool cap does not just abort — it feeds the model an observation ("you have called this too many times, stop retrying"), giving it a chance to conclude gracefully. That is the error-as-observation principle from your tool-design work, applied to the agent's own bad habits. Re-injecting the goal near the end of a long context is the standard antidote to drift. Lesson 7 treats all of this — budgets, stop conditions, loop detection, the kill switch — as first-class architecture.
You now have the course's vocabulary. ReAct is the founding pattern: reason, act, observe, repeat, with every action grounded in the latest observation rather than in upfront guesses. Around that loop, production agents add a planner, a critic, memory, persistence, and human gates — each a couple of hundred lines you will write yourself. And every loop ships inside budgets, because the two canonical failures — spinning forever and spending without bound — are properties of the architecture, not of any particular model.
Next lesson sharpens the most consequential decision you will make on any agent project — whether to build an agent at all — using Anthropic's workflow patterns as a spectrum from fixed pipeline to full autonomy. Then, in Lesson 3, you write the complete loop from scratch and it becomes the substrate for everything that follows.
Check: Your plan-first agent wrote a six-step plan assuming the
billing API returns JSON with a customer_id field. At
step 2 the observation shows the field is actually
account_ref. What does the ReAct loop shape buy you
here that plan-first execution does not?
An agent in production is never just the loop. Around it sits a small set of components, each existing because a specific failure mode demands it. This table is the map of the course: every row is a thing you will build by hand.
| Component | Question it answers | Failure it prevents | Where |
|---|---|---|---|
| The loop | How do model, tools, and results cycle? | No agent at all | Lessons 1-4 |
| Planner | What is the route through a long task? | Wandering, forgotten subgoals, runaway cost | Lessons 5-7 |
| Reflection | Was the last attempt actually good? | Confidently wrong output shipped on try one | Lesson 6 |
| Memory | What should survive beyond the context window? | Amnesia between steps, sessions, and tasks | Later in this course |
| Persistence | Can the agent stop, crash, and resume? | Losing an hour of work to a network blip | Later in this course |
| Human gates | Which actions need a person to say yes? | Autonomous irreversible mistakes | Later in this course |
Notice what is not on the list: a framework. Every component above is under two hundred lines of ordinary Python when you strip away generality you do not need. Writing them yourself is not an academic exercise — it is how you learn where the sharp edges are, so that when a framework hides one from you, you know it is there.
ReAct decides the next step at every step — flexible, but on long tasks it can wander, because it never committed to a whole route. Plan-and-execute splits the roles: a planner drafts the sequence of steps first, then an executor carries them out (often each step a small ReAct loop of its own), with a re-plan when reality diverges. Lesson 5 builds it, including the cost arithmetic of when planning pays for itself.
A third family adds a critic. Reflexion (Shinn et al., 2023) has the agent attempt, evaluate the attempt against the goal, write down what went wrong, and retry with that critique in context — a self-correction loop that catches errors a single forward pass misses. It is expensive (every cycle is more model calls) and most valuable where correctness is checkable: code that must pass tests, plans that must satisfy constraints. Lesson 6 builds it. Beyond that lie tree-search agents like LATS, which explore multiple branches and backtrack — the most capable and by far the most expensive shape.
| Architecture | Shape | Best for | Cost |
|---|---|---|---|
| ReAct | Think, act, observe — step by step | Most agents; interactive tool use | Low — start here |
| Plan-and-execute | Plan the route, then run it | Stable multi-step tasks; less wandering | Medium |
| Reflexion | Attempt, critique, revise | Checkable, high-stakes outputs | High — multiplies calls |
| LATS / tree search | Explore branches, backtrack | Hard problems where one path rarely suffices | Highest |
Every architecture above shares two failure modes, and they are the ones that reach production incident channels. First, infinite loops: the model calls a tool, dislikes the result, and calls it again, forever — a search that keeps returning nothing, a fix that keeps not fixing. Without a step cap this runs until something external kills it, burning tokens the whole time. Second, runaway cost: each step re-sends the entire growing trajectory, so a ten-step agent's last call carries nine steps of history and cost grows roughly quadratically in steps. A looping agent's cost grows without bound. A quieter third, goal drift: on long trajectories the original objective scrolls toward the back of the context and the agent starts optimizing the last thing it read.
The containment is layered, and none of it is optional in production: