C.W.K.
Stream
Lesson 01 of 05 · published

The Five-Step Flow

~22 min · agentic-loop, round-trip, stop-reason

Level 0Curious Reader
0 XP0/48 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete

Every tool-calling system, regardless of provider, runs the same loop. Memorize the steps once and the rest of this quest is a series of decorations on top.

  1. You define tools. Each tool has a name, a one-sentence description, and a JSON Schema describing its arguments.
  2. You call the model with a user message and the tool list. The model sees both the question and the menu of actions available.
  3. The model decides. It either answers in text (no tool needed) or returns a structured tool-call object specifying name and arguments.
  4. You execute the tool in your code (the model never runs anything itself), then send the result back as a new message in the conversation.
  5. You call the model again. It either calls another tool, calls the same tool with different arguments, or finishes with a text answer.

Step five is where new builders get tripped up. The loop continues until the model emits a "I'm done" stop reason — usually end_turn for Anthropic, stop for OpenAI, STOP for Gemini. You do not decide when to stop; you read the stop reason and react. Hard-coding "always loop three times" or "stop after the first tool call" produces agents that either finish too early or hang forever.

The other gotcha is conversation construction. Tool results are first-class messages in the transcript — the model sees them on the next turn the same way it sees user messages. If you forget to append the assistant's tool-call message and the tool result, the next call looks like the question never had a tool call attached, and the model will happily call the same tool again. Round-trip discipline matters.

Code

Honest agent loop, ten lines·python
def agent(user_msg, tools, run_tool, max_turns=20):
    messages = [{"role": "user", "content": user_msg}]
    for _ in range(max_turns):
        resp = client.messages.create(model=MODEL, tools=tools, messages=messages, max_tokens=2048)
        messages.append({"role": "assistant", "content": resp.content})
        if resp.stop_reason == "end_turn":
            return resp
        results = []
        for block in resp.content:
            if block.type == "tool_use":
                results.append({"type": "tool_result", "tool_use_id": block.id,
                                "content": run_tool(block.name, block.input)})
        messages.append({"role": "user", "content": results})
    raise TimeoutError("Loop never finished — check tool descriptions or max_turns")

External links

Exercise

Implement the loop above against any provider. Add a single print statement that logs (turn, stop_reason, tools_called). Run it on three prompts: one that needs zero tools, one that needs one tool, and one that needs the same tool twice with different arguments. Watch the loop terminate on its own.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.