What is an AI agent on the Claude API?

An AI agent on the Claude API is a program where Claude decides which actions to take and your code carries them out. You give Claude a goal and a list of tools. Claude replies either with an answer or with a request to call one of the tools. Your code runs the tool, sends the result back, and Claude continues until the goal is done.

This pattern is called tool use (also known as function calling). It is not a separate API. It is a feature of the same Messages API endpoint you use for plain chat, so an agent is simply a chat loop that knows how to run tools.

How does tool use work, step by step?

Tool use follows the same four steps every time:

  1. You send the conversation plus a tools list.
  2. Claude responds with stop_reason: "tool_use" and one or more tool_use blocks. Each block has an id, the tool name and an input object.
  3. Your code runs each requested tool and builds a tool_result block with the matching tool_use_id.
  4. You send the tool results back as the next user message. Claude reads them and either calls more tools or finishes with stop_reason: "end_turn".

The table below lists the stop reasons your loop must handle.

stop_reason What it means What your loop should do
end_turn Claude is done Show the final text and stop
tool_use Claude wants tools run Run them, send tool_result blocks, loop
max_tokens The reply hit the token limit Do not run half-written tool calls; raise the limit or stop
refusal Claude declined the request Stop and show a safe message
pause_turn A long server-side tool turn paused Send the conversation back to let it continue

How do I define a tool?

A tool definition has three parts: a name, a description, and an input_schema written in JSON Schema. The description matters more than most people expect. Claude chooses tools by reading their descriptions, so say what the tool does, when to use it, and what it returns.

tools = [
    {
        "name": "get_order_status",
        "description": (
            "Look up the shipping status of a customer order by its order ID. "
            "Use this whenever the user asks where their order is. "
            "Returns the status and the last update time."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "order_id": {"type": "string", "description": "Order ID, e.g. A-1042"}
            },
            "required": ["order_id"],
            "additionalProperties": False,
        },
        "strict": True,  # guarantees the input matches the schema exactly
    }
]

Setting strict: True on the tool makes the API guarantee that input validates against your schema. It needs additionalProperties: False and a required list, as above.

How do I write the agent loop in Python?

Install the SDK with pip install anthropic and set the ANTHROPIC_API_KEY environment variable. The loop below is complete and runnable. The model ID is the current default Claude model at the time of writing (September 2026); check Anthropic's model list when you build.

import json
import anthropic

client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY from the environment
MODEL = "claude-opus-5"
MAX_TURNS = 10

def get_order_status(order_id: str) -> dict:
    # Replace with a real database or API call.
    return {"order_id": order_id, "status": "shipped", "updated": "2026-09-23"}

TOOL_FUNCTIONS = {"get_order_status": get_order_status}

def run_tool(name: str, tool_input: dict) -> str:
    return json.dumps(TOOL_FUNCTIONS[name](**tool_input))

def run_agent(user_message: str) -> str:
    messages = [{"role": "user", "content": user_message}]
    for _ in range(MAX_TURNS):
        response = client.messages.create(
            model=MODEL,
            max_tokens=16000,
            system="You are a support agent for an online shop. Use tools to look up facts.",
            tools=tools,
            messages=messages,
        )
        if response.stop_reason == "refusal":
            return "Sorry, I can't help with that request."
        if response.stop_reason == "max_tokens":
            raise RuntimeError("Reply was cut off; raise max_tokens before running tools.")

        # Keep the full assistant turn (text and tool_use blocks) in the history.
        messages.append({"role": "assistant", "content": response.content})

        if response.stop_reason == "end_turn":
            return "".join(b.text for b in response.content if b.type == "text")
        if response.stop_reason == "pause_turn":
            continue  # a server-side tool paused; send the history back to resume

        results = []
        for block in response.content:
            if block.type != "tool_use":
                continue
            try:
                output = run_tool(block.name, block.input)
                results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
            except Exception as err:
                results.append({"type": "tool_result", "tool_use_id": block.id,
                                "content": f"Tool failed: {err}", "is_error": True})
        messages.append({"role": "user", "content": results})

    raise RuntimeError(f"Agent did not finish within {MAX_TURNS} turns.")

print(run_agent("Where is my order A-1042?"))

Three details in this loop prevent most bugs:

  • The whole response.content goes back into the history, not just the text. Claude needs its own tool_use blocks to match your tool_result blocks.
  • All tool results go in one user message. If Claude asked for two tools, answer both together.
  • Errors are returned, not swallowed. A tool_result with is_error: True tells Claude the call failed, so it can retry with different input or explain the problem to the user.

Should I use the SDK's tool runner instead?

The Python SDK includes a tool runner (currently a beta feature) that runs this loop for you. You decorate plain Python functions with @beta_tool, and the SDK builds the JSON Schema from the type hints and docstring, calls the functions and feeds results back until Claude is done.

import anthropic
from anthropic import beta_tool

client = anthropic.Anthropic()

@beta_tool
def get_order_status(order_id: str) -> str:
    """Look up the shipping status of a customer order.

    Args:
        order_id: Order ID, e.g. A-1042.
    """
    return f"Order {order_id}: shipped"

runner = client.beta.messages.tool_runner(
    model="claude-opus-5",
    max_tokens=16000,
    tools=[get_order_status],
    messages=[{"role": "user", "content": "Where is my order A-1042?"}],
)
for message in runner:
    print(message)

The runner is the better default once you understand the protocol. Write the manual loop first anyway: when something goes wrong, knowing what tool_use, tool_result and stop_reason look like makes debugging much faster.

What makes a Claude agent reliable in production?

A working demo and a reliable agent are different things. These habits close the gap:

  • Keep the tool list small and focused. Five well-described tools beat twenty vague ones.
  • Validate tool input yourself before touching real systems, even with strict on. Treat tool input like any user input.
  • Ask for confirmation before actions with side effects, such as refunds or emails. Return a "user declined" result when the user says no.
  • Log every turn: the request, each tool call, each result and the stop reason. Most agent bugs are visible in the log.
  • Test with real conversations. Keep a set of real user requests and rerun them after every prompt or tool change. The article on testing LLM apps walks through this.
  • Handle refusals. Newer Claude models can stop with stop_reason: "refusal". Check for it before reading the content, as the loop above does. Anthropic also offers a server-side fallback option (a beta feature) that retries a refused request on another model; read the current API docs before enabling it.

Summary

An agent on the Claude API is a loop around one endpoint: send tools, run the tool_use requests, return tool_result blocks, and stop on end_turn. Start with the manual loop to learn the protocol, then switch to the tool runner, and add turn limits, input checks and logging before real users arrive. If you want an agent like this built on your own data and systems, see AI applications, or read how RAG works to give your agent access to your documents.

Need this built? See AI applications or get in touch.

FAQ

Questions about this topic

Do I need a framework like LangChain to build an agent on Claude?

No. The official anthropic Python SDK is enough: tool use is a feature of the Messages API, and the loop is a few dozen lines. A framework can help later, but it is not required.

What is the difference between a tool and a function?

The function is your code. The tool is the description of that function you send to Claude: its name, what it does, and the JSON Schema of its input. Claude only sees the tool definition, never your code.

Can Claude call several tools at once?

Yes. One response can contain several tool_use blocks. Run them (in parallel if you like) and return all the tool_result blocks together in a single user message.

How do I stop an agent from looping forever?

Put a hard cap on the number of turns in your loop, give each tool a timeout, and return clear error results so Claude can change course instead of retrying the same call.

Keep reading

More on AI applications

Have a bot, a backend or a strategy in mind?

Tell me what you want to build and where you are with it. Send a few lines about the project and I’ll reply with questions and next steps.

Rajshahi, Bangladesh