CLAUDE LABJP
WWDC — WWDC 2026 confirms Siri runs on Google Gemini; third-party handoff to ChatGPT is dropped, and Siri AI won't ship in the EU under the DMA at iOS 27BILLING — 6 days until the Jun 15 change: Agent SDK, headless Claude Code, GitHub Actions, and third-party agents move to API-rate monthly creditOUTAGE — claude.ai, Claude Code, and Cowork saw an outage (Jun). Scheduled runs are safest when built around fallbackModel and retriesDYNAMIC-WORKFLOWS — Dynamic workflows are on by default on Max/Team and the API, for codebase-wide bug hunts and independent verificationULTRACODE — Claude Code's new ultracode setting sits in the effort menu, fixing effort to xhigh while Claude decides when to run a workflowOPUS4.8 — Claude Opus 4.8 is settled in as the default across major plans, with stronger coding, agentic, and reasoning skillsWWDC — WWDC 2026 confirms Siri runs on Google Gemini; third-party handoff to ChatGPT is dropped, and Siri AI won't ship in the EU under the DMA at iOS 27BILLING — 6 days until the Jun 15 change: Agent SDK, headless Claude Code, GitHub Actions, and third-party agents move to API-rate monthly creditOUTAGE — claude.ai, Claude Code, and Cowork saw an outage (Jun). Scheduled runs are safest when built around fallbackModel and retriesDYNAMIC-WORKFLOWS — Dynamic workflows are on by default on Max/Team and the API, for codebase-wide bug hunts and independent verificationULTRACODE — Claude Code's new ultracode setting sits in the effort menu, fixing effort to xhigh while Claude decides when to run a workflowOPUS4.8 — Claude Opus 4.8 is settled in as the default across major plans, with stronger coding, agentic, and reasoning skills
Articles/API & SDK
API & SDK/2026-03-26Intermediate

Building Automated Agentic Loops with Claude SDK Tool Runner

Learn how to use Claude SDK's Tool Runner and Tool Helpers to build automated tool-calling loops (agentic loops) with practical code examples in Python and TypeScript.

tool-runnertool-use26sdk7agentic-looppython32typescript18

What Is the Claude SDK Tool Runner?

When building applications with Claude's tool use capabilities, developers traditionally had to implement a manual loop: send a request to Claude, detect tool calls in the response, execute those tools, and feed the results back. While straightforward in concept, this loop becomes complex when you factor in error handling, context management, and multi-turn conversations.

The Tool Runner is a beta feature available in the Python, TypeScript, and Ruby SDKs that automates this entire loop. Instead of managing the back-and-forth yourself, the SDK handles tool execution, result passing, and conversation flow automatically.

Here's what Tool Runner brings to the table:

  • Automatic loop management: Each time Claude calls a tool, the runner executes it and feeds the result back without manual intervention
  • Type-safe input validation: Combined with Tool Helpers, tool inputs are validated against their schemas before execution
  • Automatic compaction: When token usage exceeds a threshold, the runner generates conversation summaries to keep context manageable
  • Streaming support: Receive responses in real-time while the agentic loop runs

The Traditional Tool Loop vs. Tool Runner

Let's first look at the traditional pattern to understand what Tool Runner replaces.

import anthropic
 
client = anthropic.Anthropic()
 
tools = [
    {
        "name": "get_weather",
        "description": "Get current weather for a city",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "City name"}
            },
            "required": ["city"]
        }
    }
]
 
def get_weather(city: str) -> str:
    return f"Weather in {city}: Sunny, 72°F"
 
# Manual loop
messages = [{"role": "user", "content": "What's the weather in Tokyo and Osaka?"}]
 
while True:
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        tools=tools,
        messages=messages,
    )
 
    if response.stop_reason == "end_turn":
        break
 
    for block in response.content:
        if block.type == "tool_use":
            result = get_weather(block.input["city"])
            messages.append({"role": "assistant", "content": response.content})
            messages.append({
                "role": "user",
                "content": [{
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": result,
                }]
            })
 
final_text = next(b.text for b in response.content if hasattr(b, "text"))
print(final_text)

This works, but loop management, message appending, and error handling are all on you. Tool Runner eliminates this boilerplate entirely.

Defining Tools with Tool Helpers

Before using the Tool Runner, let's see how Tool Helpers make tool definitions type-safe and concise.

Python (Pydantic-Based)

import anthropic
from pydantic import BaseModel, Field
 
client = anthropic.Anthropic()
 
class GetWeatherInput(BaseModel):
    city: str = Field(description="City to get weather for")
    unit: str = Field(default="celsius", description="Temperature unit (celsius/fahrenheit)")
 
@anthropic.tool(name="get_weather", description="Get current weather for a specified city")
def get_weather(input: GetWeatherInput) -> str:
    return f"Weather in {input.city}: Sunny, 22°C ({input.unit})"
 
# Automatically generates JSON Schema from the Pydantic model
print(get_weather.to_params())
# Output:
# {
#   "name": "get_weather",
#   "description": "Get current weather for a specified city",
#   "input_schema": {
#     "type": "object",
#     "properties": {
#       "city": {"type": "string", "description": "City to get weather for"},
#       "unit": {"type": "string", "default": "celsius", ...}
#     },
#     "required": ["city"]
#   }
# }

TypeScript (Zod-Based)

import Anthropic from "@anthropic-ai/sdk";
import { z } from "zod";
 
const client = new Anthropic();
 
const GetWeatherInput = z.object({
  city: z.string().describe("City to get weather for"),
  unit: z.enum(["celsius", "fahrenheit"]).default("celsius").describe("Temperature unit"),
});
 
const weatherTool = client.betaZodTool({
  name: "get_weather",
  description: "Get current weather for a specified city",
  schema: GetWeatherInput,
  execute: async (input) => {
    // input is automatically typed based on the Zod schema
    return `Weather in ${input.city}: Sunny, 22°C (${input.unit})`;
  },
});

With Tool Helpers, invalid inputs are caught at validation time rather than causing runtime errors deep in your application logic.

Building Agentic Loops with Tool Runner

Python Implementation

import anthropic
from pydantic import BaseModel, Field
 
client = anthropic.Anthropic()
 
class SearchInput(BaseModel):
    query: str = Field(description="Search query")
 
class CalculateInput(BaseModel):
    expression: str = Field(description="Math expression to evaluate")
 
@anthropic.tool(name="web_search", description="Search the web for information")
def web_search(input: SearchInput) -> str:
    return f"Search results for '{input.query}': Latest information found..."
 
@anthropic.tool(name="calculate", description="Evaluate a math expression")
def calculate(input: CalculateInput) -> str:
    try:
        result = eval(input.expression)  # Use safe_eval in production
        return f"Result: {input.expression} = {result}"
    except Exception as e:
        return f"Calculation error: {e}"
 
# Run the agentic loop with Tool Runner
result = client.messages.tool_runner(
    model="claude-sonnet-4-6",
    max_tokens=4096,
    tools=[web_search, calculate],
    messages=[{
        "role": "user",
        "content": "What is Japan's population? Calculate what percentage of the world population that represents."
    }],
)
 
# result is iterable — process each message in sequence
for message in result:
    print(f"[{message.role}]")
    for block in message.content:
        if hasattr(block, "text"):
            print(block.text)
        elif block.type == "tool_use":
            print(f"  → Tool call: {block.name}({block.input})")

TypeScript Implementation

import Anthropic from "@anthropic-ai/sdk";
import { z } from "zod";
 
const client = new Anthropic();
 
const searchTool = client.betaZodTool({
  name: "web_search",
  description: "Search the web for information",
  schema: z.object({ query: z.string().describe("Search query") }),
  execute: async (input) => {
    return `Search results for '${input.query}': Latest information found...`;
  },
});
 
const calcTool = client.betaZodTool({
  name: "calculate",
  description: "Evaluate a math expression",
  schema: z.object({ expression: z.string().describe("Math expression") }),
  execute: async (input) => {
    try {
      const result = Function(`"use strict"; return (${input.expression})`)();
      return `Result: ${input.expression} = ${result}`;
    } catch (e) {
      return `Calculation error: ${e}`;
    }
  },
});
 
// Run the agentic loop with Tool Runner
const runner = client.messages.toolRunner({
  model: "claude-sonnet-4-6",
  max_tokens: 4096,
  tools: [searchTool, calcTool],
  messages: [{
    role: "user",
    content: "What is Japan's population? Calculate what percentage of the world population that represents.",
  }],
});
 
// Process each message as it arrives
for await (const message of runner) {
  console.log(`[${message.role}]`);
  for (const block of message.content) {
    if ("text" in block) {
      console.log(block.text);
    } else if (block.type === "tool_use") {
      console.log(`  → Tool call: ${block.name}(${JSON.stringify(block.input)})`);
    }
  }
}

Managing Context with Automatic Compaction

In long-running agent sessions, tool call results accumulate and token counts grow rapidly. The Tool Runner's automatic compaction feature generates conversation summaries when token usage exceeds a configurable threshold, keeping everything within the context window.

# Python: Tool Runner with automatic compaction
result = client.messages.tool_runner(
    model="claude-sonnet-4-6",
    max_tokens=4096,
    tools=[web_search, calculate],
    messages=messages,
    compaction={
        "enabled": True,
        "threshold_tokens": 50000,  # Trigger summarization above this threshold
    },
)
// TypeScript: Tool Runner with automatic compaction
const runner = client.messages.toolRunner({
  model: "claude-sonnet-4-6",
  max_tokens: 4096,
  tools: [searchTool, calcTool],
  messages,
  compaction: {
    enabled: true,
    thresholdTokens: 50000,
  },
});

When compaction triggers, older messages are replaced with a summary while preserving key information, allowing the conversation to continue seamlessly.

Enforcing Schema Compliance with strict: true

Adding strict: true to your tool definitions enables Structured Outputs for tool calls, guaranteeing that Claude's generated inputs always match your specified schema exactly.

tools = [
    {
        "name": "create_user",
        "description": "Create a new user account",
        "strict": True,  # Enable strict schema validation
        "input_schema": {
            "type": "object",
            "properties": {
                "name": {"type": "string"},
                "email": {"type": "string", "format": "email"},
                "age": {"type": "integer", "minimum": 0}
            },
            "required": ["name", "email", "age"],
            "additionalProperties": False  # Required when strict is true
        }
    }
]

With strict: true, you can eliminate type mismatches and missing fields entirely, significantly reducing runtime errors during tool execution.

Wrapping Up

The Claude SDK Tool Runner and Tool Helpers dramatically simplify the process of building agentic applications. By delegating loop management, error handling, and context management to the SDK, you can focus on what matters most: your application's business logic and the tools that power it.

For a deeper dive into tool definitions and advanced patterns, check out the complete guide to Claude API tool use. If you're working with TypeScript, the type-safe production guide for the TypeScript SDK covers SDK-specific best practices. For cost management strategies, see the token counting and cost optimization guide.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

API & SDK2026-04-06
Anthropic SDK Install & Version Mismatch Errors: Complete Fix Guide
Troubleshoot Anthropic SDK installation failures, ImportError, ModuleNotFoundError, and version mismatch issues step by step. Covers Python and TypeScript/Node.js environments with actionable solutions.
API & SDK2026-05-16
Debugging Claude API Tool Use Schema Errors: 3 Patterns I've Hit and How to Fix Them
A practical guide to diagnosing Claude API Tool Use errors—from schema definition mistakes to invalid_tool_use blocks and Claude ignoring your tools entirely. Based on real implementation experience.
API & SDK2026-05-08
Type-Safe Claude API Tool Calling with Zod: Patterns for TypeScript Developers
How to implement Claude API tool calling with TypeScript and Zod for full type safety. Covers schema-to-API conversion, runtime validation, and three common pitfalls with practical code examples.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →