Core Concepts

Agents and the ReAct loop

An agent is an LLM in a loop over tools and state — what it is, how products use it, architecture, how to build one, today’s stacks, and failure modes.

75 min

What an agent is (plain English)

A chat completion answers once: you send messages, the model returns text (or a single tool call you execute yourself), and you are done.

An agent is different. It is a program that wraps an LLM in a loop: the model proposes the next action, your runtime executes tools or side effects, observations come back into the conversation, and the loop continues until a stop condition fires (final answer, max steps, budget, human deny, policy violation).

while not stopped:
    model decides: tool_call(s) OR final_answer
    if tool_call: execute → append observation
    if final_answer: return to user

That loop — often called ReAct (Reason + Act) when the model interleaves short reasoning with tool use — is the mental model behind almost every “AI agent” product in 2025–2026: coding assistants, support bots that open tickets, research copilots that search then cite, ops bots that page on-call.

Analogy: a chat model is a consultant who answers from memory. An agent is that consultant with a phone, a browser, and a badge to your internal systems — and a manager (your code) who decides which calls are allowed and when to hang up.

Ship rule: the model proposes; your driver disposes. Never let the model run shell, payments, or email without your executor validating schemas, auth, and bounds.

One-sentence definition you can defend in an interview

An agent is an LLM-driven control loop over tools and state, with explicit stop and safety policy, whose runs are recorded as trajectories you can evaluate.

If any of those five words is missing (loop, tools, state, stop, trajectories), you probably have a chatbot with marketing, not an agent.

LLM vs agent vs workflow (do not confuse them)

Pattern Control flow Who chooses the next step When it fits
Single LLM call One shot N/A Summarize, rewrite, classify with a schema
Prompt chain / DAG You decide the steps Your code / graph Fixed pipeline: extract → validate → format
Agent (ReAct-style) Model chooses tools each step The model (bounded by policy) Open-ended goals with a small tool set
Multi-agent Several loops + a router/orchestrator Router + specialists Specialized roles (planner / researcher / critic) — see Multi-agent orchestration

If the steps are known and stable, prefer a workflow (deterministic graph). Reach for an agent when the path is unknown — “figure out why this order failed” needs search, DB, and judgment, not a fixed three-step script.

flowchart TD
  Goal[User goal] --> Choose{Path known?}
  Choose -->|Yes| WF[Deterministic workflow / DAG]
  Choose -->|No| Ag[Agent loop]
  WF --> Out1[Result]
  Ag --> Tools[Tool runtime]
  Tools --> Ag
  Ag --> Out2[Result or escalate]

Common mistake: calling a LangGraph workflow with fixed nodes an “agent” because it uses an LLM. Fixed graphs are valuable — they are just not ReAct. Name the control-flow honestly.

Mental model: five pieces every agent has

  1. Model — chat API (or local OpenAI-compatible server) that can emit text and/or structured tool calls.
  2. Tool registry — name → JSON schema → callable (search, SQL read, calendar, code runner, MCP tools).
  3. Loop driveryour code: call model → branch on tool vs final → execute → append → repeat.
  4. State / memory — at minimum the message list; later: durable session store, summaries, retrieval.
  5. Stop + safety policy — max steps, timeouts, cost budget, allow/deny lists, human-in-the-loop (HITL) for mutating tools.
flowchart TD
  U[User goal] --> D[Loop driver]
  D --> L[LLM]
  L -->|tool call| T[Tool runtime]
  T -->|observation| D
  L -->|final answer| Out[Result]
  D -->|max steps / deny / budget| Stop[Stop / escalate]
  D --> Mem[(State / trajectory log)]
  Mem --> D

Everything else — LangGraph graphs, Crew “crews,” OpenAI Assistants, Cursor-style coding agents — is packaging around these five pieces.

What the model sees each step

Typical context packed into one completion:

Slot Contents Risk if wrong
System Role, tool policy, output rules Soft policies get ignored
Tools JSON schemas for allowed tools Vague schemas → wrong calls
History Prior assistant/tool messages Unbounded growth → cost + confusion
User goal Current task Ambiguous goals → tool spam
Optional RAG Retrieved snippets Noise dilutes attention

Treat that pack as a scarce budget — same lesson as context engineering and tokenization.

How ReAct works step by step

Classic ReAct papers interleaved natural-language “Thought” with “Action.” Modern APIs often replace free-form Action text with native tool / function calling (JSON args). The loop is the same:

  1. Assemble context — system instructions, user goal, tool schemas, prior messages, optional retrieved docs.
  2. Model step — one completion with tools enabled.
  3. Branch
    • Tool call(s) → validate args → execute with timeouts → append tool / tool_result messages.
    • Final text → return (optionally require an explicit final_answer tool).
  4. Record trajectory — step index, latency, tokens, tool name, success/fail — for debug and evals.
  5. Check stops — max steps, wall clock, consecutive tool errors, token/cost budget, policy.
sequenceDiagram
  participant U as User
  participant D as Driver
  participant M as Model
  participant T as Tools
  U->>D: Goal
  loop Until stop
    D->>M: messages + tool schemas
    alt Tool call
      M-->>D: tool_call name args
      D->>T: validate + execute
      T-->>D: observation
      D->>D: append + log trajectory
    else Final answer
      M-->>D: text
      D-->>U: result
    end
  end

Minimal hand-rolled loop (shape)

MAX_STEPS = 8
messages = [system, user_goal]

for step in range(MAX_STEPS):
    response = llm.chat(messages, tools=tool_schemas)
    if response.tool_calls:
        for call in response.tool_calls:
            ok, result = execute_tool(call.name, call.arguments)  # validate first
            messages.append(tool_result_message(call.id, result))
            log_trajectory(step, call, result, ok)
        continue
    if response.final_text:
        return response.final_text
    messages.append({"role": "user", "content": "Call a tool or give the final answer."})

raise MaxStepsExceeded("Agent hit MAX_STEPS without finishing")

Adapt message shapes to OpenAI tools vs Anthropic tool_use — keep driver logic identical.

execute_tool sketch (validate → policy → run)

def execute_tool(name: str, arguments: dict, ctx: RunContext) -> tuple[bool, dict]:
    spec = TOOL_REGISTRY.get(name)
    if spec is None:
        return False, {"error": "unknown_tool"}

    try:
        args = spec.schema.validate(arguments)
    except ValidationError as e:
        return False, {"error": "invalid_args", "detail": e.errors()}

    if not ctx.authz.allows(name, args):
        return False, {"error": "forbidden"}

    if spec.mutating and not ctx.hitl.approved(name, args):
        return False, {"error": "pending_human_approval"}

    try:
        result = spec.fn(args, timeout=spec.timeout_s)
        return True, truncate(result, max_bytes=spec.max_obs_bytes)
    except TimeoutError:
        return False, {"error": "timeout"}

The model never sees your credentials or raw exceptions you have not scrubbed.

Parallel tool calls

Many APIs allow multiple tool calls in one model step. Useful when the model can fetch order + shipment independently. Your driver must:

  • Execute with a concurrency limit
  • Timeout each call independently
  • Append results in a deterministic order (or keyed by tool_call_id)
  • Fail partial: decide whether one failed tool aborts the step or continues

Why require a final_answer tool sometimes

Free-form “I’m done” text is easy for the model to emit while still intending another tool. An explicit final_answer(summary) tool makes the stop condition machine-checkable — especially helpful for evals and for products that must never leave the loop hanging.

What agents are used for (concrete jobs)

Job Typical tools Stop when Watch out for
Support copilot Order DB, knowledge RAG, ticket API Answer + citation, or escalate Writing refunds without HITL
Coding agent Repo search, edit, terminal, tests Tests pass or step budget Unbounded shell; secret exfil
Research / briefing Web/search, PDF extract, notes Report with sources Hallucinated citations
Ops / SRE assistant Metrics, logs, runbooks (read-first) Diagnosis + proposed change Mutating prod without HITL
Internal workflow CRM, calendar, Slack Task completed or needs human Duplicate messages / double-sends
Data analyst assist SQL read, charting, warehouse catalog Query + explanation Unscoped SELECT * cost bombs

Walkthrough: support “where is my order?”

  1. User asks about order #A-1042.
  2. Agent calls get_order(id) → status shipped, tracking 1Z….
  3. Agent calls search_docs("shipping delays") → policy snippet.
  4. Agent returns a cited answer; no ticket write.
  5. Trajectory shows 2 tools, 1.8s, $0.01 — evals can replay.

If the product only needs “answer questions about our docs,” start with RAG (see RAG building blocks) — not a free-roaming agent.

Walkthrough: coding agent (shape only)

  1. Goal: “fix the flaky test in checkout_test.py.”
  2. Tools: grep, read_file, edit_file, run_tests (sandboxed).
  3. Loop: locate failure → edit → run tests → maybe revert → stop on green or budget.
  4. Policy: no network; no secrets files; max 20 edits; HITL before git push.

Same five pieces. Higher blast radius → tighter sandbox and stop policy.

End-to-end mini design (support agent)

Goal: Answer “Where is order A-1042?” with a citation; escalate when unsure.

Piece Choice
Model Hosted chat model with native tool calling
Tools get_order, search_policy, create_ticket, final_answer
Policy create_ticket requires HITL; others read-only
Stop final_answer or max 6 steps or $0.05 budget
State Thread id → message list in Redis (24h TTL)
Eval 30 gold goals; must call get_order when an order id appears

Happy path: user → get_ordersearch_policyfinal_answer with tracking + citation → trajectory logged. Failure path: missing order → create_ticket → HITL card → ticket id observation → final answer with ticket link.

That single sketch forces every production layer: schemas, policy, stop, memory TTL, and eval invariants. If the product only needs “answer questions about our docs,” start with RAG (RAG building blocks) — not a free-roaming agent.

Architecture that survives production

Demos hide four hard layers. Production-shaped agents make them explicit:

flowchart TB
  subgraph Client
    UI[Chat / ticket UI]
  end
  subgraph ControlPlane
    GW[API gateway]
    Auth[Auth + tenant]
    Bud[Budgets + rate limits]
  end
  subgraph AgentRuntime
    Driver[Loop driver]
    Policy[Tool policy / HITL]
    Mem[(Session + memory)]
    Trace[Trajectory + OTel]
  end
  subgraph Tools
    Search[Search / RAG]
    DB[(App DB)]
    MCP[MCP servers]
    Side[Mutating APIs]
  end
  UI --> GW --> Auth --> Driver
  Bud --> Driver
  Driver --> LLM[Model provider]
  Driver --> Policy
  Policy --> Search
  Policy --> DB
  Policy --> MCP
  Policy --> Side
  Driver --> Mem
  Driver --> Trace
Layer Demo Production-shaped
Tools Fake stubs Schema + validation + idempotency + side-effect policy
Stop Hope the model says “done” Max steps, budgets, HITL gates, deny lists
Memory Chat buffer in RAM Durable store + summarization + retrieval
Debug print Trajectory traces, replay, per-hop latency
Multitenancy None Tenant isolation on tools, logs, and memory
Cost Ignore Per-run token + tool budget with hard kill

Memory: three levels (do not skip straight to “vector memory”)

Level What When
Working memory Current message list / scratchpad Every run
Session memory Durable thread state across turns Multi-turn products
Long-term memory Summaries, preferences, retrieved facts Only with retention + ACL policy

Ship rule: do not invent a second brain before you can log and replay a single trajectory. Most “memory bugs” are context packing bugs.

Tool policy as a real component

flowchart LR
  Call[Tool call] --> Schema[JSON Schema validate]
  Schema --> AuthZ[AuthZ / tenant scope]
  AuthZ --> Risk{Mutating?}
  Risk -->|No| Exec[Execute + timeout]
  Risk -->|Yes| HITL{Approved?}
  HITL -->|Yes| Exec
  HITL -->|No| Deny[Deny observation]
  Exec --> Obs[Observation to model]
  Deny --> Obs

Put policy outside the model. Prompt text like “never refund” is not a control.

  1. Pick a single goal — e.g. “answer order status with citation.”
  2. Define 2–3 tools with tight JSON schemas (read-only first).
  3. Hand-roll the loop with MAX_STEPS and trajectory logging — no framework yet.
  4. Add stop + policy — deny write tools until HITL; timeout every tool.
  5. Golden-set evals — expected tool sequences + final answer rubrics (see Evals fundamentals).
  6. Only then adopt a framework if graphs, checkpoints, or multi-actor routing earn their complexity.

Tool design rules

  • Prefer small, composable tools over one mega-“do_anything” tool.
  • Return structured observations (JSON), not giant HTML dumps — protect the context window.
  • Separate read vs write tools; require confirmation for writes.
  • Treat tool output as untrusted data in the next prompt — delimit it; never let it override system policy (prompt injection via tool results is real).
  • Cap observation size (truncate + “use a finer tool” hint).
  • Make writes idempotent where possible (Idempotency-Key, upsert by natural key).

Example tool schemas (tight vs vague)

{
  "name": "get_order",
  "description": "Fetch one order by exact id for the authenticated tenant.",
  "parameters": {
    "type": "object",
    "required": ["order_id"],
    "properties": {
      "order_id": { "type": "string", "pattern": "^A-[0-9]{4,}$" }
    },
    "additionalProperties": false
  }
}

Vague alternative to avoid: "get_stuff": { "query": "string" } that can hit DB, Slack, and email.

Hand-rolled vs framework decision

Stay hand-rolled when… Adopt a framework when…
≤5 tools, one loop Need durable checkpoints / resumes
Teaching / interview clarity Complex branching graphs
You own every stop policy line Multi-actor routing is product-critical
Latency must stay minimal Team already standardized on one stack

Frameworks are accelerators, not architecture. If you cannot redraw the five pieces without the brand name, you do not understand your agent yet.

Framework map (what each actually buys)

Stack You still own It helps with
Plain SDK loop Everything Clarity, interviews, tiny products
LangGraph Tool policy, evals, tenancy Graphs, checkpoints, durable state
LlamaIndex Workflows Same Event/workflow style orchestration
Crew / multi-actor kits Same + role design Role templates (easy to overuse)
MCP host/client Loop + policy Portable tool servers across apps
Product coding agents Product UX around them IDE/repo workflows (not your SaaS brain)

Read Key Tech pages before adopting — LangGraph / LangChain, MCP.

Planner vs reactive (two control styles)

Style Behavior Pros Cons
Reactive ReAct Choose next tool each step Flexible; simple driver Can wander; harder to budget
Plan-then-act Model writes a plan, then executes Clearer for long tasks Stale plans; plan hallucinations
Hybrid Short plan + reactive repair Often best in practice More moving parts

Guided Build real AI agents walks planner vs reactive with code. Browse rule: start reactive with hard caps; add planning when trajectories show thrashing.

Agentic RAG (retrieval inside the loop)

Classic RAG: retrieve once → generate. Agentic RAG: retrieval is a tool the model may call zero or many times (rewrite query, fetch more, give up).

flowchart TD
  Q[Question] --> A[Agent]
  A -->|search_docs| R[Retriever]
  R --> A
  A -->|maybe search again| R
  A -->|final_answer| Out[Cited answer]

Use when one-shot retrieval fails often (ambiguous queries, multi-hop facts). Do not start here — get one-shot RAG eval-gated first (RAG building blocks).

How to evaluate agents (not just answers)

Chatbots can be scored on final text. Agents need trajectory-aware evals:

Signal What it catches
Exact / rubric final answer Outcome quality
Expected tool sequence (soft) Wrong tool habits
Illegal tool calls Policy holes
Step count / cost Loops and waste
Faithfulness to tool observations Ignoring evidence
Human preference on hard cases UX / tone

Build a golden set of goals → allowed tools → acceptable trajectories → answer rubrics. Wire it in CI the same way you would for prompts. Details: Evals fundamentals and guided Evals, guardrails, safety.

Soft vs hard trajectory checks

Check type Example Notes
Hard Must not call refund without HITL CI should fail the build
Hard Must call get_order before answering status Structural
Soft Prefer ≤4 steps Warn; tune over time
Soft Tool order may vary Allow multiple valid paths

Do not require a single exact tool sequence for every gold case — models are stochastic. Require invariants (must/must-not) plus answer quality.

Debugging playbook (first hour of an incident)

  1. Pull run_id trajectory; plot tools vs time.
  2. Check stop reason: max steps, deny, timeout, final answer, user cancel.
  3. Diff schemas if you recently added overlapping tools.
  4. Inspect observation sizes — context blow-ups hide as “model got dumb.”
  5. Replay locally with temperature 0 for reproducibility.
  6. Add a golden case that would have caught this before closing the incident.

Tools and stacks today (2025–2026)

You do not need all of these. Learn the pattern first; pick one stack when shipping.

Layer Common choices Role
Model APIs OpenAI, Anthropic, Google, open-weight via vLLM/Ollama Brain + native tool calling
Orchestration LangGraph, LlamaIndex Workflows, Microsoft Agent Framework / Semantic Kernel, CrewAI, Autogen-style multi-agent Graphs, state, multi-actor
Lightweight smolagents, plain SDKs Teaching / thin wrappers
Tool protocol MCP (Model Context Protocol) servers/clients Standardize tool I/O across apps
Product agents Cursor / Claude Code / OpenAI Agents-style runtimes Coding & computer-use patterns
Observability OpenTelemetry, LangSmith, Helicone, custom trajectory stores Debug loops and cost
Structured I/O JSON Schema / Zod / Pydantic validators Tool args + final objects

Browse deeper in Key Tech: LangGraph / LangChain, MCP, OpenAI / Anthropic APIs. Advanced: Multi-agent orchestration, Guardrails.

MCP in one paragraph

Model Context Protocol standardizes how hosts discover and call tools/resources from servers (filesystem, DB, SaaS). Your agent driver still owns the loop; MCP is a tool transport and schema convention, not a replacement for stop policy. See Model Context Protocol and Skills, MCP, context engineering.

Failure modes (why agents feel “broken”)

Symptom Likely cause Fix direction
Infinite search / tool spam No max steps / weak stop Hard caps + final_answer tool
Wrong tool, right vibe Vague schemas / overlapping tools Tighten schemas; fewer tools
Hallucinated args Model invents IDs Validate; fetch IDs via read tools first
Context blow-up Huge tool dumps Truncate, summarize, retrieve selectively
Surprise side effects Writes without HITL Policy gate; idempotency keys
Non-deterministic flakiness Unstable planning Lower temperature for tools; eval trajectories
Cost spike Loops × expensive model Step + token budgets; cheaper model for routing
Prompt injection via tools Untrusted web/DB text treated as instructions Delimit; strip; never elevate tool text to system
“Agent” that is just a chatbot No tools / no loop Either add tools or drop the label
Perfect demo, bad prod No golden trajectories Eval gates before launch
flowchart TD
  Fail[User says agent is broken] --> Trace[Open trajectory]
  Trace --> Q1{Hit max steps?}
  Q1 -->|Yes| Cap[Tune tools or raise cap with budget]
  Q1 -->|No| Q2{Wrong tool?}
  Q2 -->|Yes| Schema[Fix schemas / reduce tools]
  Q2 -->|No| Q3{Bad observation?}
  Q3 -->|Yes| Trunc[Truncate + structured returns]
  Q3 -->|No| Eval[Add golden case + rubric]

Security and blast radius (non-optional)

Agents amplify whatever tools you expose.

  • Least privilege — read-only credentials for research tools; separate write principals.
  • Sandbox — code runners without network or secrets by default.
  • Egress allowlists — especially for computer-use / browsing agents.
  • Secret hygiene — never put API keys in tool observations or prompts.
  • Audit — every mutating call attributable to user + agent run id.
  • Human gates — refunds, deletes, prod deploys, outbound email.

Pair with Guardrails and safety and Privacy and data for AI.

Computer use and browsing agents (special case)

Some products give the model a desktop or browser: screenshots in, click/type actions out. Same five pieces — the “tools” are UI actions.

Extra constraints that ordinary tool agents need less of:

Constraint Why
Virtual display / sandbox VM Contain malware and data exfil
Allowlisted domains Stop unexpected navigations
Action rate limits Prevent click storms
Screenshot redaction Hide secrets on screen before model sees them
Stronger HITL Purchases, posts, admin consoles

Do not start here. Learn ReAct on typed APIs first; computer use is the same loop with a wider blast radius.

Observability: what to log every run

If you cannot answer “what did the agent do?”, you cannot debug or eval.

Minimum trajectory record (JSONL is fine):

{
  "run_id": "r_01",
  "step": 2,
  "model": "",
  "tool": "get_order",
  "args_digest": "sha256:…",
  "ok": true,
  "latency_ms": 120,
  "tokens_in": 1800,
  "tokens_out": 40,
  "cost_usd": 0.012
}

Add OpenTelemetry spans for gateway → model → each tool (OpenTelemetry for LLMs when you go deeper). Redact PII before export.

Cost model (back-of-envelope)

[ \text{cost} \approx \sum_{\text{steps}} (\text{prompt tokens} + \text{completion tokens})\cdot $/\text{token} + \sum_{\text{tools}} \text{tool cost} ]

Agent products die from steps × fat contexts, not from one clever prompt. Cap steps, truncate observations, and route easy turns to cheaper models.

From one agent to many (preview)

When one loop thrashes on huge tool sets, split roles:

flowchart TD
  User --> Router
  Router --> Support[Support agent]
  Router --> Research[Research agent]
  Support --> State[(Shared state)]
  Research --> State
  State --> User

Patterns (router, supervisor–worker, handoff) live in Multi-agent orchestration and guided Agentic workflows & multi-agent. Ship rule: earn multi-agent by failing single-agent with evidence from traces — do not start there for a portfolio demo.

Production readiness checklist

  • Hard MAX_STEPS and wall-clock deadline
  • Per-tool timeouts + schema validation
  • Read vs write tools separated; HITL on writes
  • Trajectory logging with replay
  • Golden set with illegal-tool cases
  • Token/cost budget kill switch
  • Tenant isolation on tools and memory
  • Prompt-injection handling for tool/web text
  • User-visible escalate / “I don’t know” path
  • Runbook: how to disable the agent without taking down the app

Tradeoffs and when to use an agent

Use an agent when:

  • The goal is open-ended and tool choice depends on intermediate results.
  • You can bound tools, steps, and blast radius.
  • You will invest in trajectories + evals (not just a demo prompt).

Prefer a simpler system when:

  • Steps are fixed → workflow / DAG.
  • Need grounded Q&A only → RAG + citations.
  • Need a machine-checkable object → structured outputs, not a 12-step loop.
  • Risk of autonomous writes is unacceptable without heavy HITL.
  • Latency budget cannot afford multi-hop tool RPCs (Networking for AI apps).

Interview / design cue: always name tools, state, stop conditions, and eval of trajectories — not just “we’ll add an agent.”

Interview prompts you should be able to answer

  1. Draw the agent loop and mark where authZ and HITL live.
  2. When is a deterministic workflow better than ReAct?
  3. How do you eval an agent differently from a chatbot?
  4. How do you prevent prompt injection via tool results?
  5. What do you log per step, and what do you redact?

Glossary

Term Meaning
ReAct Pattern of interleaving reasoning and acting (tools) in a loop
Trajectory Ordered record of thoughts/tool calls/observations for one run
Tool / function calling Model emits structured call; runtime executes
HITL Human-in-the-loop approval before sensitive actions
MCP Protocol for exposing tools/resources to model clients
Planner vs reactive Plan-all-then-act vs choose-next-action each step
Agentic RAG Retrieval as a tool inside an agent loop, not a one-shot retrieve
Driver / runtime Your code that owns the loop, policy, and logging
Observation Tool result appended back into model context
Stop condition Rule that ends the loop (final answer, max steps, deny, budget)

Micro-project

Hand-roll a 3-tool ReAct loop (e.g. get_order, search_docs, final_answer) with:

  1. Hard MAX_STEPS = 6
  2. Schema validation before execute
  3. Trajectory JSONL log you can replay
  4. A deliberate failure case that hits max steps cleanly
  5. One golden eval asserting “must call get_order before answering”

This browse article is the complete mental model. Build real AI agents is where you build the loop until frameworks stop feeling like magic.

Project checklist0/3 done