The Simple Loop Behind Every Fancy Agent

Mar 9, 2026 2 min read
tl;dr - drafted by claude, reviewed by me

Strip away the branding and every agent framework is the same small loop: call the model, run the tool it asks for, feed the result back, and repeat until it replies normally. I show the whole thing in one short TypeScript file, and the embedded widget lets you step through a full turn to watch the messages move.

Okay so every fancy agent framework still runs the same tiny loop. The model decides what to do, you execute a tool if it asks, you feed the result back, and you repeat until it responds normally.

Press step → to walk one full cycle. Notice every arrow either starts or ends at the harness - model and tool never talk directly.

harness orchestrates · model returns text · tool runs in your code
←/→ step · 0 reset

↳ live from the agents lesson - step through one agent turn

import OpenAI from "openai";

const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

// Tool definition the model can call
const tools = [
  {
    name: "getTime",
    description: "Returns the current time in ISO format",
    parameters: { type: "object", properties: {}, required: [] },
  },
];

// Actual tool implementation
function getTime() {
  return { now: new Date().toISOString() };
}

async function runTool(name: string, args: Record<string, any>) {
  if (name === "getTime") return getTime();
  throw new Error(`Unknown tool: ${name}`);
}

export async function agentLoop(userMessage: string) {
  const messages: OpenAI.Chat.ChatCompletionMessageParam[] = [
    { role: "system", content: "You are a helpful assistant." },
    { role: "user", content: userMessage },
  ];

  while (true) {
    const res = await client.chat.completions.create({
      model: "gpt-4o-mini",
      messages,
      tools,
      tool_choice: "auto",
    });

    const msg = res.choices[0].message;

    if (msg.tool_calls?.length) {
      messages.push(msg);

      for (const call of msg.tool_calls) {
        const result = await runTool(
          call.function.name,
          JSON.parse(call.function.arguments || "{}")
        );

        messages.push({
          role: "tool",
          tool_call_id: call.id,
          content: JSON.stringify(result),
        });
      }

      continue;
    }

    return msg.content;
  }
}

(async () => {
  const answer = await agentLoop("What time is it right now?");
  console.log(answer);
})();

What is happening

1. The model decides the next step. Each iteration either returns a normal reply or a tool call.

2. If it asks for a tool, you run it. You execute the tool and capture the output.

3. You push the tool output back in. That gives the model the data it needs to finish.

4. You loop until it replies normally. That final reply is your answer.

That is the core of agents. You can add more tools, a step limit, or memory later, but this loop is the foundation.

ciao

related
Feb 01 26
How OpenClaw Gives AI Agents Persistent Memory With Plain Markdown
A deep dive into how OpenClaw's file-based memory system gives AI assistants genuine continuity between sessions using Markdown files, semantic search, and automatic memory flush.
Oct 29 25
The 5 MCP Servers Every Developer Needs in 2025
The 5 MCP servers that actually matter for your AI-powered dev workflow - semantic code nav, GitHub automation, browser testing, live docs, and persistent memory.
esc