Your Agent Isn't Thinking, It's Looping — Read One Turn of the Trace

There's no reasoning engine and no memory — just a while-loop, a stateless model, and an array that grows. Unroll one turn of it and most agent mysteries stop being mysteries.

"The agent decided to check the calendar first." It didn't decide anything. A loop appended a message to an array, sent the array to a model, got back a request to call a function, ran the function, appended the result, and sent the array again. Everything an agent does is that, repeated until it stops asking for tools.

Most confusion about agents — why one forgets a constraint, why costs climb, why a tool failure derails the whole run — dissolves the moment you read a single turn of that loop line by line. Here is the whole thing, and what each part explains.

The loop, in five lines

messages = [system_prompt, user_message]

while True:
    reply = model(messages, tools=tool_specs)
    if not reply.tool_calls:
        return reply.text
    messages.append(reply)                  # what the model asked for
    for call in reply.tool_calls:
        messages.append(run(call))          # what came back

No memory, no state machine, no goal tracker. There is an array of messages, and it grows. The model is stateless — every iteration it re-reads the entire array from scratch, as if seeing it for the first time.

One turn, unrolled

Give a scheduling agent two tools, find_free_slots(person, week) and create_event(...), and this task: "Find a 30-minute slot with Priya next week and book it. Nothing before 10am."

Iteration 1 — the array holds the system prompt and the request. The model replies with no prose, just:

tool_call: find_free_slots(person="priya", week="2026-08-31")

The loop runs the function and appends the result:

tool_result: ["Mon 09:00-09:30", "Mon 14:00-15:00", "Wed 09:15-09:45"]

Iteration 2 — the model gets the same array plus those two new entries, re-reads all of it, and replies:

tool_call: create_event(with="priya", start="2026-09-01T14:00", minutes=30)

It picked the 2pm slot. Not because it "remembered" the 10am rule between turns, but because the rule was still sitting in the array it just re-read.

Iteration 3 — the array now has four entries beyond the start. The model returns text with no tool call, the loop returns it, done.

💡 What the trace makes obvious

Nothing carries over except the array. There is no separate place where the agent stores "the user said no mornings." If that instruction falls out of the array — trimmed to save tokens, summarized away, pushed past the context limit by a long tool result — the model isn't ignoring the rule. It never saw the rule. This is why "the agent forgot" is nearly always a context-assembly bug, not a reasoning failure.

Tool results are input text, and the model treats them as such. In iteration 2, the model's whole world was the string your function returned. Return Error: 500 and you have handed the model an instruction it cannot act on; return No free slots found for priya between Aug 31 and Sep 4 — try a different week and the next iteration has something to work with. Error strings are prompts. Most teams write them for a log file and then wonder why the agent flails after a failure.

Cost and latency grow with each iteration, not linearly with work. Iteration 3 resends everything from iterations 1 and 2. A six-step task doesn't cost six units — it costs roughly the sum of a growing array, re-read every time. → A single 4,000-token tool result early in a run is paid for again on every subsequent turn. That is usually the answer to "why did this simple task cost so much."

What this changes about debugging

  • ✅ When behavior is wrong, print the exact array sent on the failing iteration. Not the user's request, not the system prompt template — the assembled array.
  • ✅ Read the tool results as if you were the model, with no outside knowledge. If a human couldn't act on that string, the model can't either.
  • ❌ Adding "REMEMBER: never book before 10am" in bold to the system prompt, when the real problem is that the constraint was dropped three iterations ago.
  • ❌ Blaming "hallucination" for a wrong tool argument before checking whether the correct value was in the array at all.
  • ✅ Count iterations. A task that should take three tool calls and takes eleven is a loop that can't tell it's finished — usually a missing success signal, not a weak model.

Where it stops being that simple

Real systems layer things on top: retrieval that injects documents, summarizers that compress old turns, memory stores that write facts to a database and read them back in. Every one of those is a strategy for deciding what goes in the array before the next call. They're worth adding. They are not a different mechanism — they're plumbing feeding the same loop.

The takeaway

An agent is a while-loop around a stateless model, with an array in the middle. Every capability you want comes from putting the right things in that array, and every failure worth debugging shows up there first. Before adding a framework, a second agent, or a bigger model, print the array. The answer is usually sitting in it.

Keep reading

Similar posts

Matched on shared tags and category — the more bars, the stronger the overlap with what you just read.