ASK KNOX
beta
LESSON 568

Tool Use Part 1: The Agentic Loop

Claude asks, you execute, results return, repeat — the loop that powers every agent including Claude Code itself.

7 min read·Building with Claude

The Central Abstraction

If this track has one lesson that matters most, it is this one. The tool use loop is THE central abstraction of modern AI engineering. Every agent product in production — Claude Code, content pipelines, trading systems that call market APIs, Q&A systems that query databases — is this loop. Learn it deeply and you understand all of them.

The concept is deceptively simple: Claude does not execute code. Claude asks YOU to execute code. Your code runs the tool, returns the result, and Claude synthesizes a response. The intelligence is in Claude; the execution is in your infrastructure.

This division of responsibility is load-bearing. Claude has no direct access to your database, your APIs, your file system. It can only request actions through the tool schema you define. That boundary is what makes agents auditable, interruptible, and safe — you can add approval gates, logging, rate limits, and rollback at the execution layer without changing anything about how Claude reasons.

The sequence diagram shows the full loop. Each step has a specific wire format. Get the format right and Claude handles the rest.

Defining a Tool

A tool has three parts: name, description, and input_schema.

const tools: Anthropic.Tool[] = [
  {
    name: "get_weather",
    description: "Call this when the user asks about current weather or conditions for a specific location. Returns temperature, humidity, and a conditions summary.",
    input_schema: {
      type: "object",
      properties: {
        city: {
          type: "string",
          description: "The city name to retrieve weather for, e.g. 'Raleigh, NC'",
        },
        units: {
          type: "string",
          enum: ["celsius", "fahrenheit"],
          description: "Temperature units — defaults to fahrenheit if not specified",
        },
      },
      required: ["city"],
    },
  },
]

The anatomy diagram surfaces the most important principle: the description is a prompt. It is the primary mechanism through which you influence whether Claude calls the tool, and when.

A weak description says what the tool does. A strong description says WHEN to call it. "Returns weather data" is weak. "Call this when the user asks about current weather or conditions for a location" is strong. The WHEN condition is what a conservative model reads to decide if this is the right moment to reach for the tool. Adding trigger conditions to your tool descriptions is one of the highest-leverage improvements you can make to call rate.

The Wire Format, Exactly

When Claude decides to use a tool, it returns stop_reason: "tool_use" and the response content contains one or more tool_use blocks:

// What Claude returns when it wants to call a tool
{
  id: "toolu_01SomeUniqueId",
  type: "tool_use",
  name: "get_weather",
  input: { city: "Raleigh, NC", units: "fahrenheit" }
}

Three fields matter: id (you echo this back in the result), name (identifies which tool to run), and input (the validated parameters per your schema).

Your code runs the tool and returns results in a user message:

// What you send back
{
  role: "user",
  content: [
    {
      type: "tool_result",
      tool_use_id: "toolu_01SomeUniqueId",  // matches the id from tool_use
      content: "Raleigh, NC: 72°F, partly cloudy, humidity 58%",
    }
  ]
}

The tool_use_id is the correlation key. If you return a result with the wrong tool_use_id, the API cannot match it to the corresponding request and returns a 400 error.

The Full Manual Loop

Here is the complete tool loop in ~35 lines of TypeScript. This is the implementation that powers every manual agent you will build:

import Anthropic from "@anthropic-ai/sdk"

const client = new Anthropic()

async function runToolLoop(userMessage: string): Promise<string> {
  const messages: Anthropic.MessageParam[] = [
    { role: "user", content: userMessage }
  ]

  while (true) {
    const response = await client.messages.create({
      model: "claude-sonnet-4-6",
      max_tokens: 4096,
      tools,
      messages,
    })

    // Append the assistant response to history verbatim
    messages.push({ role: "assistant", content: response.content })

    if (response.stop_reason === "end_turn") {
      // Done — extract the final text response
      return response.content
        .filter(b => b.type === "text")
        .map(b => b.text)
        .join("")
    }

    if (response.stop_reason !== "tool_use") {
      throw new Error(`Unexpected stop_reason: ${response.stop_reason}`)
    }

    // Process ALL tool calls in this response
    const toolResults: Anthropic.ToolResultBlockParam[] = []

    for (const block of response.content) {
      if (block.type !== "tool_use") continue

      const result = await executeTool(block.name, block.input)
      toolResults.push({
        type: "tool_result",
        tool_use_id: block.id,
        content: result,
      })
    }

    // Return ALL results in ONE user message
    messages.push({ role: "user", content: toolResults })
  }
}

The loop has four critical behaviors:

  1. Append assistant content verbatim before processing tool calls. The next request needs the full history including the tool_use blocks.
  2. Process ALL tool calls before returning. Claude may request multiple tools in one response — handle every tool_use block in the content array.
  3. Return ALL results in ONE user message. The content array in that user message contains one tool_result per tool_use block.
  4. Loop until end_turn. Claude may request more tools after seeing the first results. The loop continues until the model decides it has enough information to respond.

Parallel Tool Calls

Claude may request multiple tools in a single response — this is called a parallel tool call. In the example above, response.content might contain:

[
  { type: "tool_use", id: "toolu_01", name: "get_weather", input: { city: "Raleigh" } },
  { type: "tool_use", id: "toolu_02", name: "get_weather", input: { city: "Durham" } },
  { type: "tool_use", id: "toolu_03", name: "get_stock_price", input: { ticker: "AAPL" } },
]

Your code can execute these in parallel with Promise.all:

const toolResults = await Promise.all(
  response.content
    .filter(b => b.type === "tool_use")
    .map(async (block) => {
      if (block.type !== "tool_use") return null
      const result = await executeTool(block.name, block.input)
      return {
        type: "tool_result" as const,
        tool_use_id: block.id,
        content: result,
      }
    })
)

All results still go back in ONE user message — the parallelism is in your execution layer, not in the API protocol.

What Comes Next

This lesson covered the mechanics: define a tool, run the loop, handle results. The next lesson covers quality: how to write tool descriptions that fire reliably, how to return errors that help Claude adapt, when to force specific tools, and how to use the SDK's tool runner instead of hand-rolling the loop. The loop is the foundation; reliability is the craft.