react-agent-loop

A minimal, bring-your-own-LLM ReAct (Reason + Act) agent loop for TypeScript

zero runtime deps Node ≥ 20 ESM strict TypeScript MIT
ReAct = Reasoning and Acting — the LLM agent pattern of Think → Act → Observe (Yao et al., 2022). This is NOT React.js, the UI library. No JSX, no components, no DOM — just an agent control loop.
🧠 Think 🛠️ Act 👁️ Observe ✅ task_done

react-agent-loop is a tiny, dependency-free control loop for building tool-using LLM agents. The model reasons, acts by calling tools, observes the results, and repeats until the task is done — while every external concern (which LLM, which tools, how to persist) stays yours.

Why

Bring your own LLM

Implement one method, complete(), over OpenAI, Anthropic, a local model, or a mock. No SDK is bundled.

Pluggable tools

Register tools with a name, description, JSON-schema parameters, and an async handler. That's it.

Guardrail hooks

beforeToolCall is the seam for a loop detector, policy engine, or budget guard — block or rewrite actions before they run.

Zero dependencies

No vendor lock-in, no hidden network calls, no global state. Strict TypeScript with full .d.ts types.

Install

npm i github:Princeu3/react-agent-loop

npm registry release coming soon.

Quick start

import { runReActLoop } from "react-agent-loop";
import type { LlmClient, Tool } from "react-agent-loop";

const getWeather: Tool = {
  name: "get_weather",
  description: "Get the current weather for a city.",
  parameters: {
    type: "object",
    properties: { city: { type: "string" } },
    required: ["city"],
  },
  handler: async (args) => `It is 22°C and sunny in ${args.city}.`,
};

const llm: LlmClient = {
  async complete({ system, messages, tools }) {
    const res = await callYourProvider({ system, messages, tools });
    return { text: res.text, toolCalls: res.toolCalls };
  },
};

const result = await runReActLoop({
  llm,
  tools: [getWeather],
  system: "You are a weather assistant. Call task_done when finished.",
  initialMessages: [{ role: "user", content: "Weather in Lisbon?" }],
  hooks: {
    beforeToolCall(call) {
      if (call.name === "delete_everything")
        return { block: true, reason: "Blocked by policy." };
    },
  },
});

console.log(result.stopped);      // "task_done" | "shouldStop" | "maxTurns"
console.log(result.finalSummary); // the agent's closing summary

Pairs with

Attribution

Distilled and authored by Prince Upadhyay. The control-flow architecture is inspired by the MIT-licensed Conway-Research/automaton agent loop, re-authored as a clean, framework-agnostic library with all project-specific coupling removed.

View on GitHub →