To course

The agent loop

One step of an agent loopPractice: one step of the loopThe loop in codePractice: the loop in codeStopping and retryingPractice: stopping and retryingWhat only code can decidePractice: what code decides

Reliability in production

Timeout and idempotency keyPractice: timeout and idempotency keySays done, but nothing happenedPractice: false successDurable agent: checkpoints and resumePractice: durable agent

Agent designs

Workflow or agentPractice: workflow or agentReAct and plan-and-executePractice: ReAct and plan-and-executePlan-and-SolvePractice: Plan-and-SolveReflexionPractice: ReflexionOne agent or a swarmPractice: one agent or a swarm

Context and cost

Why the agent bill growsPractice: tokens and cacheContext engineeringPractice: managing the windowAgent memoryPractice: what the agent keeps

Tools and protocols

MCPPractice: MCPTool descriptions and injectionPractice: tool descriptions and injectionCode instead of a toolsetPractice: code instead of a toolsetAgent skills and A2APractice: agent skills and A2A

Frameworks

LangGraph or the raw loopPractice: LangGraph or the raw loopAgent SDKs: what you are buyingPractice: agent SDKs

Coding agents

Driving a coding agent from a specPractice: driving a coding agent from a spec
←→RUSign in
Agents › The agent loop

The loop in code

Builds onOne step of an agent loop

This block turns the phone call from the previous block into code. We go through the loop line by line, then look at what happens when the model asks for two things at once and when a tool fails.

We are still building the Northwind support agent, directly on the Anthropic Python SDK.

The phone call as a function

You dial, read out the whole conversation and listen. If your colleague asks for something, you do it and ring back. If they give you a final answer, you are done.

def run_agent(client, session, text):
    messages = [{"role": "user", "content": text}]
    for _ in range(MAX_STEPS):
        reply = client.messages.create(model=MODEL, max_tokens=1024,
                                       system=SYSTEM, tools=TOOLS,
                                       messages=messages)
        messages.append({"role": "assistant", "content": reply.content})
        if reply.stop_reason == "end_turn":
            return text_of(reply)
        if reply.stop_reason != "tool_use":
            return HANDOFF_MESSAGE
        results = [execute(block, session) for block in reply.content
                   if block.type == "tool_use"]
        messages.append({"role": "user", "content": results})
    return HANDOFF_MESSAGE

Here is what each part does.

messages is your notepad. It starts with the customer's message and only ever grows.

client.messages.create is the call. You send the instructions, the list of tools and the whole notepad.

The first append writes down what your colleague said. The reply goes in whole, before you do anything about it.

end_turn means they are finished, so you return the text. This agent has no server tools or stop sequences, so any other reason, such as max_tokens or refusal, means there is no complete answer, and the customer goes to a person.

results is you doing what was asked. Every request gets run, and the second append puts all the answers into one message.

range(MAX_STEPS) is the limit on the call. The next block is about that limit.

Two requests at once

Your colleague can ask for two things in one breath. So they number each request, and you quote the number when you answer. Otherwise the answers get mixed up.

A customer asks about orders 4417 and 5120. One reply from the model holds two lookups, so your code always goes through a list, even when there is usually one item.

One reply, two requests. Each answer quotes the id of its request, and they all travel in one message.
One reply, two requests. Each answer quotes the id of its request, and they all travel in one message.

Three rules apply on Anthropic's API:

The order of execution is yours. Independent reads can run together. Refunds are better one at a time.

Other APIs use the same idea with different names:

Anthropic Messages

OpenAI Responses

OpenAI Chat Completions

Model asks

tool_use block

function_call item

tool_calls in the reply

The number

tool_use_id

call_id

tool_call_id

You answer

tool_result in a user message

function_call_output item

message with role tool

When a tool fails

Your colleague asks you to find order 4471, and there is no such order. You say so, and they ask the customer for the right number. You would not hang up on them.

A failure returned as a result gives the model a next move. An exception ends the run.
A failure returned as a result gives the model a next move. An exception ends the run.

An unknown order is information, so it goes back to the model as a tool result:

def execute(block, session):
    result = {"type": "tool_result", "tool_use_id": block.id}
    try:
        result["content"] = dispatch(block.name, block.input, session)
    except Exception as error:
        result["content"] = f"Failed: {error}"
        result["is_error"] = True
    return result

Make the text specific enough for the model to choose what to do next.

What the model does not get to say

Your colleague does not tell you who the customer is. You already know that from the signed-in session.

The customer's identity is not a tool argument. customer_id stays out of the tool schema, and dispatch reads it from session.

if name == "lookup_order":
    return orders.lookup(order_id=args["order_id"],
                         customer_id=session.customer_id)

Models can invent arguments, and OpenAI's reference warns about parameters outside your schema. Strict mode makes arguments match the schema. It says nothing about whether the values are right, so your code validates them and dispatch passes on only the fields it expects.

Conclusion

Go deeper

Parallel tool use (Anthropic)How to run and answer several calls from one reply.Handle tool calls (Anthropic)Error results with `is_error`, and the ordering rules.Function calling (OpenAI)`call_id`, parallel calls and strict mode on OpenAI.
‹ Previous
Suggest an improvementFound a mistake