learn.aathan.in

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

1. Reason 2. Act 3. Observe "what next?" call a tool read result done? → stop
Reason → Act → Observe, looping until the model judges the task complete. Each turn feeds the last observation back in as new context.

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:

  1. You send the model the conversation plus a list of available tools (each with a name, description, and JSON argument schema).
  2. The model responds either with a final answer (loop ends) or a tool-use request — the tool name and arguments it wants.
  3. Your code executes that tool and captures the result.
  4. You append the result to the conversation and send it back to the model.
  5. 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.