
Skip the While Loop: The Anthropic SDK's Tool Runner Runs Your Agent For You
Chris Harper
3 min read
Aug 30, 2026 · 12:05 UTC
TL;DR: The Anthropic SDK's beta @beta_tool decorator + client.beta.messages.tool_runner runs the tool-call loop automatically — the same four-step round trip, with no explicit tool_result handling code.
The four-step tool-use loop (define tool → get stop_reason: "tool_use" → execute → send tool_result back) is the right pattern when you need per-call logging, cost gating, or conditional execution. When you do not need those, the SDK ships a wrapper that handles the loop for you.
Two decorators, one loop:
import json
from anthropic import Anthropic, beta_tool
client = Anthropic()
@beta_tool
def get_weather(location: str, unit: str = "fahrenheit") -> str:
"""Get the current weather in a given location.
Args:
location: The city and state, e.g. San Francisco, CA
unit: Temperature unit, either 'celsius' or 'fahrenheit'
"""
# your real implementation here
return json.dumps({"temperature": "18°C", "condition": "Cloudy"})
@beta_tool
def calculate_sum(a: int, b: int) -> str:
"""Add two numbers together.
Args:
a: First number
b: Second number
"""
return str(a + b)
runner = client.beta.messages.tool_runner(
model="claude-sonnet-5-20260801",
max_tokens=1024,
tools=[get_weather, calculate_sum],
messages=[{"role": "user", "content": "Weather in Berlin, and what is 15 + 27?"}],
)
for message in runner:
# each iteration is a BetaMessage — the loop stops on end_turn
if message.stop_reason == "end_turn":
for block in message.content:
if block.type == "text":
print(block.text)
@beta_tool inspects the function's type hints and the Args: section of its docstring to generate the JSON schema Claude receives — the same schema you would write by hand in a manual loop. The runner handles the stop_reason check, calls your function, wraps the return value as a tool_result, and sends the next request. It stops when Claude returns stop_reason: "end_turn".
When to use which pattern:
| Pattern | Use when |
|---|---|
| Tool Runner | Quick scripts, prototyping, simple single-agent tasks |
| Manual loop | Per-call logging, cost gating, approval before execution, result transformation |
The runner is iterable — each loop iteration yields a BetaMessage — so you still have access to intermediate messages when you need them. What you cannot do is intercept a call before it runs, which is what human-in-the-loop and conditional retry patterns need.
The real limit: Tool Runner is in beta (client.beta.messages.tool_runner). The API surface has changed between SDK minor versions; pin your anthropic version in requirements.txt and check the GitHub release notes when upgrading. An async variant (@beta_async_tool) is available for asyncio-based code.
Sources: Tool Runner — platform.claude.com · Anthropic SDK Python tools.md — GitHub · Claude Agent SDK complete guide — hidekazu-konishi.com · Anthropic Agent SDK: What It Ships vs. What It Leaves to You — Augment Code