#AI Agent#TypeScript

Source Walkthrough: Build a Minimal ReAct Agent by Hand

The best way to understand an Agent is to build a minimal one yourself. This under-100-line TypeScript implementation explains the ReAct (Thought → Action → Observation) loop, tool calls, stopping conditions, and error recovery.

Key Points

  • Topic Focus: AI Agent、TypeScript
  • Suitable for: indie hackers, full-stack engineers, AI application developers
  • Core value: quickly understand background, practical paths, common pitfalls, and engineering advice
Author @admin·Published July 26, 2026·Updated July 26, 2026

Building Agents with frameworks such as LangChain, LangGraph, and AI SDK is convenient, but it can also leave you knowing how to use them without understanding them. The most thorough way to understand an Agent is to write a minimal one yourself from scratch—you will discover that the core of a ReAct Agent is simply a loop of fewer than 100 lines.

This article walks you through writing one and explains every step.

1. The Essence of ReAct: A Loop

The core of ReAct (Reason + Act) is just this:

text
循环 {
  向模型发送: system(规则+工具) + 历史(thought/action/observation)
  模型返回: 下一步的 thought 和 action(调哪个工具、传什么参数)
  如果模型说"完成了" → 结束
  否则: 执行 action(工具),把结果作为 observation 加进历史
  继续
}

That is it. Every Agent framework adds engineering capabilities around this loop, such as “state management, parallelism, human-in-the-loop, and observability.” The core loop itself is extremely simple.

2. Define the Tools

An Agent gets its capabilities from tools. Start by defining the two simplest ones:

ts
const tools = [
  {
    type: "function" as const,
    function: {
      name: "search",
      description: "Search the web for factual information. Use when you don't know something.",
      parameters: {
        type: "object",
        properties: { query: { type: "string" } },
        required: ["query"],
      },
    },
  },
  {
    type: "function" as const,
    function: {
      name: "calculate",
      description: "Evaluate a math expression, e.g. '12 * (3+4)'. Use for exact arithmetic.",
      parameters: {
        type: "object",
        properties: { expr: { type: "string" } },
        required: ["expr"],
      },
    },
  },
];

// 工具的实际实现
const handlers: Record<string, (args: any) => Promise<string>> = {
  search: async ({ query }) => `[模拟搜索结果] 关于 "${query}" 的信息...`,
  calculate: async ({ expr }) => String(Function(`return (${expr})`)()),
};

3. The Core Loop (This Is the Agent Itself)

ts
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();

async function reactAgent(userMessage: string, maxSteps = 10) {
  const messages: any[] = [{ role: "user", content: userMessage }];

  for (let step = 0; step < maxSteps; step++) {
    // 1) 让模型想下一步
    const response = await client.messages.create({
      model: "claude-sonnet-5",
      max_tokens: 1024,
      system: "你是一个会使用工具的助手。一步步推理。需要信息就 search,需要计算就 calculate。完成就给出最终答案。",
      tools,
      messages,
    });

    // 2) 把模型这轮的输出加入历史
    messages.push({ role: "assistant", content: response.content });

    // 3) 模型没调工具 → 它给出最终答案,结束
    if (response.stop_reason === "end_turn") {
      const text = response.content.find((c: any) => c.type === "text");
      return text?.text ?? "(no answer)";
    }

    // 4) 模型调了工具 → 执行,把结果作为 observation 回灌
    if (response.stop_reason === "tool_use") {
      const toolUses = response.content.filter((c: any) => c.type === "tool_use");
      const toolResults = [];
      for (const tu of toolUses) {
        const handler = handlers[tu.name];
        const result = handler
          ? await handler(tu.input).catch((e) => `tool error: ${e.message}`)
          : `unknown tool: ${tu.name}`;
        toolResults.push({ type: "tool_result", tool_use_id: tu.id, content: result });
      }
      messages.push({ role: "user", content: toolResults });
    }
  }

  return "(达到最大步数,未完成)";
}

This is a runnable ReAct Agent, with fewer than 50 lines at its core. Run it:

ts
await reactAgent("2024 年中国 GDP 总量是多少万亿美元?换算成人民币是多少(按 7.2 汇率)?");
// 模型会: search(GDP) → observation → calculate(换算) → observation → 给最终答案

4. Understand These Few Dozen Lines, and You Understand Every Agent Framework

Use this implementation to understand the frameworks:

  • LangGraph's StateGraph: it draws this loop as a graph. The nodes are the “model step,” the edge loops back, and the state is messages.
  • AI SDK's generateText({ tools, maxSteps }): it simply wraps this loop in a maxSteps parameter.
  • All kinds of Agent classes: at their core, they are this loop plus different degrees of state, parallelism, and observability packaging.

The value of frameworks lies in the surrounding engineering—parallelism, human-in-the-loop, traces, and state persistence—not in the core loop. Once you see that “an Agent is just a loop with tool calls,” frameworks are no longer black boxes. You can bypass them and write your own implementation when it makes sense.

5. Engineering Guardrails You Must Add Yourself

This minimal implementation is for explaining the principle. For production, it still needs several safeguards:

  1. maxSteps (already included): prevents infinite loops. An Agent without an upper limit is a money-burning bomb.
  2. Error recovery: when a tool fails, catch feeds a readable error back into the context (tool error: ...) so the model can choose another path instead of throwing an exception and stopping.
  3. Tool parameter validation: validate tu.input with a schema before execution. Feed invalid input errors back so the model can retry.
  4. Timeouts: set a timeout for every tool call so the loop cannot get stuck.
  5. Trace: log each step's thought/action/observation. Both debugging and production depend on it.

Add these five things, and this 50-line Agent has the beginnings of a production-ready foundation.

6. Extending from This Starting Point

Once you understand the core, every extension is an addition on top:

  • Multiple tools / parallel calls: the model can return multiple tool_use calls at once; execute them in parallel within the loop and feed all results back together (the code already supports this).
  • Memory: add long-term memory outside messages, retrieve it, and inject it on every turn.
  • RAG: add a retrieve_docs tool and let the Agent decide when to retrieve.
  • Multi-Agent: replace a tool with “call another Agent,” and you have multi-Agent collaboration.
  • Plan-Execute: have the model produce a plan before the loop and execute according to it, and you have the Plan-Execute pattern.

All of these extensions are still additions to the same core loop.

Summary

The core of a ReAct Agent is a loop of “the model decides the next step → execute the tool → feed the result back,” with fewer than 50 lines for the Agent itself. All Agent framework complexity is peripheral: state, parallelism, observability, and guardrails. The core is extraordinarily simple.

Writing this minimal implementation once is the fastest path to understanding Agents. You will see that Agents are not mysterious: they are simply “an LLM + a loop + tools.” With that understanding, when you use LangGraph or AI SDK, you are no longer led around by the framework. You know what every step does, when to use a framework, and when writing it yourself is cleaner.

Related

Related Content