Log at info level
agents-and-tools/tool-use/tool-runner
History
agents-and-tools/tool-use/tool-runner Changed · +1 / -1 lines
### Automatic context management -For long-running agentic tasks, the Python, TypeScript, and Ruby tool runners support automatic [compaction](https://platform.claude.com/docs/en/build-with-claude/context-editing#client-side-compaction-sdk), which generates summaries when token usage exceeds a threshold so the conversation can continue beyond context window limits. All three SDKs have deprecated this client-side option in favor of server-side [context editing](https://platform.claude.com/docs/en/build-with-claude/context-editing), which is available in every SDK. The Go, Java, C#, and PHP tool runners don't include client-side compaction. +For long-running agentic tasks, the TypeScript and Ruby tool runners support automatic [compaction](https://platform.claude.com/docs/en/build-with-claude/context-editing#client-side-compaction-sdk), which generates summaries when token usage exceeds a threshold so the conversation can continue beyond context window limits. Both SDKs have deprecated this client-side option in favor of [server-side compaction](https://platform.claude.com/docs/en/build-with-claude/compaction), which works with every SDK's tool runner through the `context_management` request parameter. The Python SDK (v1.0 and later) and the Go, Java, C#, and PHP tool runners don't include client-side compaction. ### Debugging tool execution
agents-and-tools/tool-use/tool-runner First recorded · 1728 lines, first recorded
## Basic usage ## Iterating over the tool runner ## Advanced usage ### Taking over message history ### Automatic context management ### Debugging tool execution ### Intercepting tool errors ### Modifying tool results ## Streaming ## Next steps
The first capture of this source. The page was already there, and this is what it said.
---
title: Tool runner (SDK)
url: https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-runner
description: Use the SDK's tool runner to handle the agentic loop, error wrapping, and type safety automatically.
---
The tool runner handles the agentic loop, error wrapping, and type safety so you don't have to. When you need human-in-the-loop approval, custom logging, or conditional execution, use the [manual loop](https://platform.claude.com/docs/en/agents-and-tools/tool-use/handle-tool-calls) instead.
Instead of manually handling tool calls, tool results, and conversation management, the tool runner automatically:
* Runs tools when Claude calls them
* Handles the request/response cycle
* Manages conversation state
* Provides type safety and validation
<Note>
The tool runner is in beta and available in the [Python SDK](https://github.com/anthropics/anthropic-sdk-python/blob/main/tools.md), [TypeScript SDK](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/helpers.md#tool-helpers), [C# SDK](https://github.com/anthropics/anthropic-sdk-csharp/blob/main/examples/ToolRunnerExample/Program.cs), [Go SDK](https://github.com/anthropics/anthropic-sdk-go/blob/main/tools.md), [Java SDK](https://github.com/anthropics/anthropic-sdk-java/blob/main/anthropic-java-example/src/main/java/com/anthropic/example/BetaToolRunnerExample.java), [PHP SDK](https://github.com/anthropics/anthropic-sdk-php/blob/main/examples/beta/beta_tool_runner.php), and [Ruby SDK](https://github.com/anthropics/anthropic-sdk-ruby/blob/main/helpers.md#3-auto-looping-tool-runner-beta).
</Note>
## Basic usage
Define tools using the SDK helpers, then use the tool runner to run them.
Depending on the SDK's tool signature, a tool returns its result as a string or as content blocks (text, image, or document blocks), so a tool can return multimodal results. A returned string becomes a single text content block. To return structured data, such as a JSON object or a number, encode it as a string first.
<Tabs>
<Tab title="Python">
Use the `@beta_tool` decorator to define tools with type hints and docstrings.
<Note>
If you're using the async client, replace `@beta_tool` with `@beta_async_tool` and define the function with `async def`.
</Note>
```python
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'
"""
return json.dumps({"temperature": "20°C", "condition": "Sunny"})
@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-opus-5",
max_tokens=1024,
tools=[get_weather, calculate_sum],
messages=[
{
"role": "user",
"content": "What's the weather like in Paris? Also, what's 15 + 27?",
}
],
)
for message in runner:
print(message)
```
The `@beta_tool` decorator inspects the function arguments and docstring to derive the JSON schema for you.
</Tab>
<Tab title="TypeScript">
Use `betaZodTool()` for type-safe tool definitions with Zod validation, or `betaTool()` for JSON Schema-based definitions.
TypeScript offers two approaches for defining tools:
**Using Zod (recommended)** - Use `betaZodTool()` for type-safe tool definitions with Zod validation (requires Zod 3.25.0 or higher):
```typescript
import Anthropic from "@anthropic-ai/sdk";
import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";
import { z } from "zod";
const client = new Anthropic();
const getWeatherTool = betaZodTool({
name: "get_weather",
description: "Get the current weather in a given location",
inputSchema: z.object({
location: z.string().describe("The city and state, e.g. San Francisco, CA"),
unit: z.enum(["celsius", "fahrenheit"]).default("fahrenheit").describe("Temperature unit")
}),
run: async (input) => {
return JSON.stringify({ temperature: "20°C", condition: "Sunny" });
}
});
const finalMessage = await client.beta.messages.toolRunner({
model: "claude-opus-5",
max_tokens: 1024,
tools: [getWeatherTool],
messages: [{ role: "user", content: "What's the weather like in Paris?" }]
});
for (const block of finalMessage.content) {
if (block.type === "text") {
console.log(block.text);
}
}
```
**Using JSON Schema** - Use `betaTool()` for type-safe tool definitions without Zod:
<Note>
The input generated by Claude is not validated at runtime. Perform validation inside the `run` function if needed.
</Note>
```typescript
import Anthropic from "@anthropic-ai/sdk";
import { betaTool } from "@anthropic-ai/sdk/helpers/beta/json-schema";
const client = new Anthropic();
const calculateSumTool = betaTool({
name: "calculate_sum",
description: "Add two numbers together",
inputSchema: {
type: "object",
properties: {
a: { type: "number", description: "First number" },
b: { type: "number", description: "Second number" }
},
required: ["a", "b"]
},
run: async (input) => {
return String(input.a + input.b);
}
});
const finalMessage = await client.beta.messages.toolRunner({
model: "claude-opus-5",
max_tokens: 1024,
tools: [calculateSumTool],
messages: [{ role: "user", content: "What's 15 + 27?" }]
});
for (const block of finalMessage.content) {
if (block.type === "text") {
console.log(block.text);
}
}
```
</Tab>
<Tab title="C#">
Define each tool as a `BetaRunnableTool`, providing a `Definition` with a JSON schema and a `Run` delegate that runs when Claude calls the tool.
```csharp
using System.Text.Json;
using Anthropic;
using Anthropic.Helpers.Beta;
using Anthropic.Models.Beta.Messages;
using MessageCreateParams = Anthropic.Models.Beta.Messages.MessageCreateParams;
using InputSchema = Anthropic.Models.Beta.Messages.InputSchema;
using Role = Anthropic.Models.Beta.Messages.Role;
using Model = Anthropic.Models.Messages.Model;
var client = new AnthropicClient();
var getWeatherTool = new BetaRunnableTool
{
Name = "get_weather",
Definition = new BetaTool
{
Name = "get_weather",
Description = "Get the current weather in a given location.",
InputSchema = new InputSchema
{
Properties = new Dictionary<string, JsonElement>
{
["location"] = JsonSerializer.SerializeToElement(
new { type = "string", description = "The city and state, e.g. San Francisco, CA" }
),
},
Required = ["location"],
},
},
Run = (toolUse, _) =>
{
var location = toolUse.Input["location"].GetString();
return Task.FromResult<BetaToolResultBlockParamContent>(
$"Weather in {location}: 20°C, sunny"
);
},
};
var calculateSumTool = new BetaRunnableTool
{
Name = "calculate_sum",
Definition = new BetaTool
{
Name = "calculate_sum",
Description = "Add two numbers together.",
InputSchema = new InputSchema
{
Properties = new Dictionary<string, JsonElement>
{
["a"] = JsonSerializer.SerializeToElement(new { type = "number" }),
["b"] = JsonSerializer.SerializeToElement(new { type = "number" }),
},
Required = ["a", "b"],
},
},
Run = (toolUse, _) =>
{
var a = toolUse.Input["a"].GetDouble();
var b = toolUse.Input["b"].GetDouble();
return Task.FromResult<BetaToolResultBlockParamContent>($"{a + b}");
},
};
var runner = client.Beta.Messages.ToolRunner(
new MessageCreateParams
{
Model = Model.ClaudeOpus5,
MaxTokens = 1024,
Messages =
[
new()
{
Role = Role.User,
Content = "What's the weather like in Paris? Also, what's 15 + 27?",
},
],
},
[getWeatherTool, calculateSumTool]
);
await foreach (var message in runner)
{
Console.WriteLine(message);
}
```
</Tab>
<Tab title="Go">
Define a tool with `toolrunner.NewBetaToolFromJSONSchema`. The handler's input type is a struct with `jsonschema:` tags. The SDK reflects on it to generate the JSON schema.
```go
package main
import (
"context"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/toolrunner"
)
type GetWeatherInput struct {
Location string `json:"location" jsonschema:"required,description=The city and state, e.g. San Francisco, CA"`
Unit string `json:"unit,omitempty" jsonschema:"enum=celsius,enum=fahrenheit,description=Temperature unit"`
}
type CalculateSumInput struct {
A int `json:"a" jsonschema:"required,description=First number"`
B int `json:"b" jsonschema:"required,description=Second number"`
}
func main() {
client := anthropic.NewClient()
ctx := context.Background()
getWeather, err := toolrunner.NewBetaToolFromJSONSchema(
"get_weather",
"Get the current weather in a given location.",
func(ctx context.Context, input GetWeatherInput) (anthropic.BetaToolResultBlockParamContentUnion, error) {
return anthropic.BetaToolResultBlockParamContentUnion{
OfText: &anthropic.BetaTextBlockParam{Text: "20°C, Sunny"},
}, nil
},
)
if err != nil {
log.Fatal(err)
}
calculateSum, err := toolrunner.NewBetaToolFromJSONSchema(
"calculate_sum",
"Add two numbers together.",
func(ctx context.Context, input CalculateSumInput) (anthropic.BetaToolResultBlockParamContentUnion, error) {
Cut at 300 lines.