The loop, in one page
By the end: Describe the agent loop end to end and name what terminates it.
Under every agent, however elaborate the framework around it, is one small loop. It is worth being able to write it from memory, because when an agent misbehaves the cause is almost always somewhere in these few lines.
messages = [user_request]
loop:
response = model(system_prompt, tools, messages)
messages.append(response)
if response has no tool calls:
return response.text # the model is done talking
results = []
for call in response.tool_calls:
results.append(run(call)) # your code, not the model
messages.append(results)
if over_budget() or too_many_rounds():
stopFour things are worth noticing. The model never runs anything: it emits a request to call a tool, and your code decides whether and how to honour it. The conversation grows on every pass, which is where cost and latency come from. The loop ends when the model stops asking for tools — that is the only signal of doneness the protocol gives you. And the safety of the whole system lives in that one line where you run the call.
The model is stateless between calls. It does not remember the last turn; it re-reads the whole conversation each time. Everything the agent "knows" at step seven is whatever is in that message list. This single fact explains most of context engineering, which module 4 is about.

