Every agent is the same four-step loop — perceive, reason, act, observe — running until the task is done. Build the loop skeleton first, before adding any tools.
Why: every agent framework, no matter how complex, is this loop underneath — perceive input, reason about it, act (call a tool), observe the result, repeat. When: keep this picture in mind for the whole course; each lesson fills in one box.
┌─────────────────────────────────────────────┐
│ │
▼ │
PERCEIVE ──► REASON ──► ACT (tool) ──► OBSERVE
(input) (model) (your code) (result)
│ ▲
└───────────────────────────────────────────────
loop until the model says it's doneWhy: before wiring tools, build the loop shell so you understand the control flow. When: the model has nothing to call yet, so it answers in one pass and the loop exits immediately — that is the simplest possible "agent".
def run(user_input):
messages = [{"role": "user", "content": user_input}]
while True:
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
messages=messages,
)
# No tools wired yet, so the model always finishes in one turn.
if response.stop_reason == "end_turn":
return text_of(response)
print(run("Name three uses for an AI agent."))Why: the loop is driven entirely by stop_reason — it tells you whether the model is done, wants a tool, or hit a limit. When: you will branch on "tool_use" in the next lessons; for now, learn the values you must handle.
end_turn — The model finished its answer. Read the text and exit the loop.tool_use — The model wants you to run a tool. Execute it, return the result, and loop again. (Wired up in the next lessons.)max_tokens — The response hit the max_tokens cap mid-thought. Raise max_tokens or stream the response.refusal — The model declined for safety reasons. Surface it to the user — do not retry the same prompt.response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
print(response.stop_reason) # "end_turn"