The agent loop
Model, tool call, result, model again: the loop a harness runs, and the context window filling as it goes.
Loading the animation…
Concept
A chat model does one thing: given a conversation, it writes the next message. It cannot read a file, run a test or look anything up. The harness is the program around it that makes it an agent. It sends the model the conversation, reads what the model wrote, and if the model asked for a tool, runs the tool and appends the result. Then it calls the model again, with the longer conversation. It stops when the model answers without asking for a tool, or when one of the harness's own limits is reached.
The animation runs that loop in the simulator this site is built on, on a small task: a repository whose tests fail because add() subtracts. With the scripted policy, the run takes 6 model calls and 5 tool calls: list the files, run the tests, read the code, fix the line, run the tests again, then answer.
Watch the bar underneath. Every turn, the harness sends the whole context again: the system prompt, the tool definitions, the task, and everything said so far. The first call is 505 tokens, of which the tool definitions alone are 424 tokens; by the last it is 939 tokens. Over the run the model is sent 4,342 tokens in total, against 197 tokens it writes. Re-reading the context is where an agent's tokens go, which is why the next chapters are about what goes into it.
Switch the run to one of the recorded traces. These are real runs of Qwen2.5-1.5B-Instruct, a small open-weights model, on a CPU, recorded once and replayed here token for token: the harness rebuilds every prompt and checks it matches the recording byte for byte. The small model fails, and the ways it fails are the reasons harnesses exist. On the same task it lists the files and then claims calc.py is missing, though the listing shows it. In ReAct style it runs the tests correctly, then wraps its tool call in a Markdown code fence that the parser rejects, twice, and finally reports success without having changed anything. Asked to look up two numbers and calculate, it searches well, two queries in one turn, but then does the arithmetic in prose instead of calling the calculator, gets it wrong and runs into its output limit. The traces page has each recording and its provenance.
Maths
Let be the system prompt, the tool definitions, the task, and and the model's message and the tool results of turn , all in tokens. Turn sends
If every turn adds about the same , the context grows linearly, , and the total sent over turns grows with the square:
The output, , grows only linearly. That is why long agent runs are dominated by input tokens, and why caching the repeated prefix (chapter 4) and keeping the context small (chapter 3) matter so much.
Code
The loop, cut from the vendored engine (src/lib/engine/vendor/harness.ts; its Python reference in Agent_Loop_Sim produces identical traces, event for event). The model's text is parsed; no tool call means a final answer:
const text = model.complete({ prompt: p.text, input_tokens: p.ids.length, style, last, turn });
const am = this.message("assistant", "assistant", text, turn);
const ev = this.modelCall(agent, turn, "act", p, text, am.ids.length);
used += ev.input_tokens + ev.output_tokens;
msgs.push(am);
const parsed = parse(text, style, toolNames);
if (parsed.kind === "final") {
this.checkpoint(agent, turn, msgs, system, toolNames, nCalls);
return ["done", parsed.final, used];
}
What the harness decides
The model only ever writes text. Everything else is the harness's choice, and the chapters take those choices one at a time:
| Choice | Chapter |
|---|---|
| How tools are described, and how calls are written and parsed | Tool calling |
| What stays in the context when it fills | The context window as a budget |
| How the prompt is laid out so the provider's cache stays warm | Prompt caching |
| Which calls run, which are refused, which wait for a person | Permissions |
The loop itself is the one the ReAct paper made popular: reason, act, observe, repeat (Yao et al., arXiv 2210.03629). Today's APIs return tool calls as structured blocks instead of text: on the Claude API a response that calls a tool ends with stop_reason: "tool_use" and holds tool_use blocks, and the harness answers with tool_result blocks (Anthropic, tool use, accessed 2026-10-07).