The Agentic Loop (ReAct & Tool Use)
The reason–act–observe cycle that turns a text-predicting model into an agent that gets things done.
What actually makes an LLM an agent rather than a chatbot? A loop. Instead of producing one answer and stopping, an agent reasons about what to do, takes an action in the world, observes the result, and repeats — until the task is done. This reason–act–observe cycle is the beating heart of every agent framework, and once you see it you can’t unsee it.
The loop
ReAct: reasoning + acting, interleaved
The pattern has a name — ReAct (Yao et al., 2022), for Reasoning + Acting. The insight was that letting a model think out loud between actions makes it dramatically better at multi-step tasks than either pure reasoning (which can’t touch the world) or pure acting (which flails without a plan). A single step looks like:
Thought: I need the user's latest order to answer this.
Action: get_orders(user_id="u_123", limit=1)
Observation: { "id": "o_988", "status": "shipped", "eta": "2026-07-18" }
Thought: It shipped and arrives the 18th. I can answer now.
Answer: Your order shipped and should arrive July 18th.
The Thought lines are the model reasoning; Action is a tool call; Observation is the tool’s result, fed back so the next Thought can build on it.
How a turn actually works
Modern models implement this through tool calling (a.k.a. function calling), which the loop drives:
- You send the model the conversation plus a list of available tools (each with a name, description, and JSON argument schema).
- The model responds either with a final answer (loop ends) or a tool-use request — the tool name and arguments it wants.
- Your code executes that tool and captures the result.
- You append the result to the conversation and send it back to the model.
- Go to step 2.
run_agent(task, tools):
messages ← [system_prompt, task]
loop:
response ← model(messages, tools)
if response has no tool call:
return response.text # ← the agent is done
for each tool_call in response:
result ← execute(tool_call)
messages.append(tool_call, result)
That’s it. Everything else — planning, retries, sub-tasks — is elaboration on this loop.
What makes an agent good (or bad)
- Stopping. The hardest part is knowing when to stop. Give the model a clear “done” condition and a max-iteration cap so a confused agent can’t loop forever burning tokens.
- Error recovery. Tools fail. Feed the error back as an observation — a good agent reads “404: not found” and adjusts, rather than repeating the same call.
- Context growth. Every turn appends to the conversation, and long loops blow past the context window. This is why context engineering — compaction, summarization, scratchpads — matters so much for long tasks.
- Tool clarity. The model can only act as well as its tools are described. Vague tool descriptions produce wrong tool choices; this overlaps heavily with writing good MCP servers.
Beyond a single loop
- Reflection — add a step where the agent critiques its own output and retries, trading tokens for quality.
- Planning — have the agent draft a multi-step plan first, then execute steps, re-planning as observations come in.
- Delegation — a hard task can be split across multiple agents, each running its own loop on a sub-problem. But every one of them, underneath, is still reason → act → observe.