How an agent works

~12 min 7 beats

An agent is a model in a loop - and the loop is your code, not the model. Walk through the message flow, see what a tool actually is, see what the model receives on each turn, and watch what goes wrong when the loop misbehaves.

The whole lesson in one sentence

An agent is a model in a loop - and the loop is your code, not the model.

Most people imagine the model "calling tools" the way a function calls another function. It doesn't. The model is just a function over text: messages in, one message out. The orchestration code that wraps it - the harness - is what calls tools, manages state, decides when to stop. That harness is the agent. The model is one of its dependencies.

This lesson walks through the actual message flow, then drills into three pieces the picture leaves abstract: what a tool really is, what the model literally receives on each turn, and what goes wrong when the loop is the bug.

The message flow

Four actors: user, harness, model, tool. Watch a single cycle of "What's the weather in Tokyo?" play out as a sequence of messages. Every arrow either starts or ends at the harness - the model and the tool never speak to each other directly. That fact is the whole architecture.

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

The aha: there is no "agent API". The model is just POST /v1/messages in a while loop. The harness assembles the request, parses the response, runs any tool calls, and decides whether to call the model again. The model has no idea a "loop" exists.

What's a tool, really?

A "tool" isn't a runtime connection between the model and some external system. It's a JSON schema - name, description, parameter types - included in the prompt sent to the model. The model reads those descriptions and may emit a tool_call matching one of them. The harness reads the tool_call (it's just text!) and runs the actual function in your code.

tool definition 3 tools available
schema sent to the model in the prompt
when the model invokes it harness sees this text and dispatches

-

-

Why this matters: the model "knows about" a tool only because the harness told it (in the prompt) that the tool exists. Add a new tool by adding a new entry to the tool list - that's it. Remove a tool by removing the entry. There's no plugin system; there's just text.

What does the model actually see?

Models are stateless. There is no "memory" - only replayed history. Every API call sends the entire conversation so far, plus the system prompt, plus the tool definitions. Five turns in, the model receives all five turns, every time. Use the inspector below to scrub through one conversation and see what the harness actually sends on each call.

api request turn 1 / 5 · ~380 tokens
turn: -

-

Why this matters: "context window" is a count of tokens in this single request. Long conversations don't accumulate in the model - they accumulate in the messages array, which the harness re-sends in full every time. Compaction, summarization, sliding-window memory - those are all harness-side strategies for keeping that array small.

When the loop is the bug

Three canonical ways the loop misbehaves: a tool keeps erroring and the model can't fix it, the user's goal is too vague and the model wanders, or the loop has no max iteration count and runs forever. The harness's policy - retry caps, token budgets, halt conditions - is what protects you. Pick a mode and step through.

failure mode:

Press step → to watch the loop misbehave. Switch modes above to see different failure patterns.

iteration
0
tokens spent
0
state
idle
recent loop activity latest at top
    ←/→ step · 0 reset

    Why this matters: stop conditions are non-negotiable. A model that's been trained to be "helpful and thorough" will not stop on its own. Every production agent has a max-iter cap, a token budget, a per-tool retry limit, or all three. The model is the engine; the harness is the brakes.

    What you just saw

    The harness is the agent. The model is just a function over text. What makes the system "agentic" is the orchestration code - the while loop, the tool dispatcher, the stop conditions.
    Tools are JSON, not plugins. A tool definition is a schema in the prompt. The model emits a tool_call as text; your harness parses it and runs the function. No magic, no protocol.
    Every turn replays the whole history. The model has no state. The messages array is the conversation; it gets re-sent in full on every API call. That's why context windows matter.
    The harness owns the brakes. Without max-iter, retry caps, and token budgets, the loop is the bug. Stop conditions aren't safety theater - they're load-bearing.

    The whole orchestration in 15 lines

    Strip away the framework, and this is what every agent reduces to. The harness assembles the request, parses the response, runs tool_calls, and decides whether to loop. The model is one line of that code.

    const messages = [system, userMessage]
    
    while (iter < MAX_ITER) {                    // ← stop condition
      const resp = await model(messages, tools)  // ← the only place the model is called
      messages.push(resp)
    
      if (!resp.tool_calls) break                // ← model said it's done
    
      for (const call of resp.tool_calls) {
        const result = await runTool(call)       // ← harness runs the function
        messages.push({ role: "tool", content: result })
      }
      iter++
    }
    
    return last(messages).content                // ← the final answer

    The closing aha: the orchestration is trivial. The hard parts live in the prompt, the tool design, and what the harness does around the loop - compaction, permissions, sub-agents, parallel tool execution. That's the next lesson.

    esc