Log at info level changedagents-and-tools/tool-use/tool-runner
Nearest release: v2.1.312, published under an hour before this site recorded the change. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
Recorded here
Lines+27added
Lines−27removed
From line
4
where the diff opens
First seen
14 Aug 2026
this site's first read of the page
Recorded edits19to this page, all time
The whole hunk
from line 4, old and new numbered
/
from line 4
44description: Use the SDK's tool runner to handle the agentic loop, error wrapping, and type safety automatically.
55---
66
7The 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.
7The 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.
88
99Instead of manually handling tool calls, tool results, and conversation management, the tool runner automatically:
1010
from line 21
2121
2222Define tools using the SDK helpers, then use the tool runner to run them.
2323
24Depending 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.
24Depending 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.
2525
2626<Tabs exclude="shell">
2727 <Tab title="Python">
from line 28
2828 Use the `@beta_tool` decorator to define tools with type hints and docstrings.
2929
3030 <Note>
31 If you're using the async client, replace `@beta_tool` with `@beta_async_tool` and define the function with `async def`.
31 If you’re using the async client, replace `@beta_tool` with `@beta_async_tool` and define the function with `async def`.
3232 </Note>
3333
3434 ```python
from line 251
251251 </Tab>
252252
253253 <Tab title="Go">
254 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.
254 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.
255255
256256 ```go
257257 package main
from line 443
443443 </Tab>
444444
445445 <Tab title="PHP">
446 Define each tool as a `BetaRunnableTool` that pairs the tool's JSON schema definition with a closure that runs it.
446 Define each tool as a `BetaRunnableTool` that pairs the tool’s JSON schema definition with a closure that runs it.
447447
448448 ```php
449449 <?php
from line 589
589589
590590You can end the loop at any iteration with a `break` statement. The runner loops until Claude returns a message without a tool use, or until it reaches `max_iterations` (csharp, php: `maxIterations`; java: `.maxIterations()`; go: `MaxIterations`) if you set it.
591591
592If you don't need intermediate messages, you can get the final message directly:
592If you don’t need intermediate messages, you can get the final message directly:
593593
594594<Tabs exclude="shell">
595595 <Tab title="Python">
from line 773
773773
774774## Advanced usage
775775
776Within the loop, you can read each response message and modify the runner's state before the next API call. Each iteration follows this lifecycle:
776Within the loop, you can read each response message and modify the runner’s state before the next API call. Each iteration follows this lifecycle:
777777
7787781. The runner sends a request to the Messages API with its current state.
779779
7807802. The runner yields the response message to your loop body.
781781
7823. Your loop body runs. You can read the message and optionally modify the runner's state.
7823. Your loop body runs. You can read the message and optionally modify the runner’s state.
783783
7847844. When your loop body returns, the runner checks whether you modified its message history.
785785
from line 812
812812
813813By default, the runner manages conversation state for you: after each tool-call turn, it appends the assistant message and any tool results to its own message history. You take over message history when you want to retry a turn (discard the response and resend), inject a follow-up message, or build the tool result yourself.
814814
815You take over by modifying the runner's messages from inside the loop body. The exact method depends on the SDK. See the per-language tabs that follow.
815You take over by modifying the runner’s messages from inside the loop body. The exact method depends on the SDK. See the per-language tabs that follow.
816816
817817When you take over for an iteration, the runner does not append the assistant message or tool results from that turn. You become responsible for keeping the conversation valid: append the assistant message and a tool result yourself (if you want the turn to count), modify state conditionally so the loop can still exit when there are no tool calls, and pass `max_iterations` (csharp, php: `maxIterations`; java: `.maxIterations()`; go: `MaxIterations`) to bound the loop. The SDK supports `max_iterations` (csharp, php: `maxIterations`; java: `.maxIterations()`; go: `MaxIterations`) in all seven languages.
818818
819819<Tabs exclude="shell">
820820 <Tab title="Python">
821 Use `generate_tool_call_response()` to inspect or compute the tool result. Calling `append_messages()` inside the loop tells the runner you're managing history yourself, so include the assistant message and tool result in what you append.
821 Use `generate_tool_call_response()` to inspect or compute the tool result. Calling `append_messages()` inside the loop tells the runner you’re managing history yourself, so include the assistant message and tool result in what you append.
822822
823823 ```python
824824 runner = client.beta.messages.tool_runner(
from line 852
852852 </Tab>
853853
854854 <Tab title="TypeScript">
855 Use `runner.params` to read the current request parameters and `setMessagesParams()` to replace them. Calling `setMessagesParams()` or `pushMessages()` inside the loop tells the runner you're managing state yourself: the assistant message and tool result from this iteration are dropped, and the next request goes out with your state.
855 Use `runner.params` to read the current request parameters and `setMessagesParams()` to replace them. Calling `setMessagesParams()` or `pushMessages()` inside the loop tells the runner you’re managing state yourself: the assistant message and tool result from this iteration are dropped, and the next request goes out with your state.
856856
857857 The following example retries a truncated response with a larger `max_tokens` budget.
858858
from line 892
892892 </Tab>
893893
894894 <Tab title="C#">
895 Calling `SetParams()` or `PushMessages()` flags state as modified, which causes the runner to skip its auto-append for that turn. The C# runner still runs the matched tools for that turn and discards their auto-built results, so a tool you also run yourself inside the loop body runs twice unless you account for it. When you take over, push the assistant message and a tool result yourself. Otherwise the conversation won't make forward progress. The C# runner always exits when a response has no tool calls, so condition any state mutation on the presence of a `tool_use` block.
895 Calling `SetParams()` or `PushMessages()` flags state as modified, which causes the runner to skip its auto-append for that turn. The C# runner still runs the matched tools for that turn and discards their auto-built results, so a tool you also run yourself inside the loop body runs twice unless you account for it. When you take over, push the assistant message and a tool result yourself. Otherwise the conversation won’t make forward progress. The C# runner always exits when a response has no tool calls, so condition any state mutation on the presence of a `tool_use` block.
896896
897897 ```csharp
898898 var runner = client.Beta.Messages.ToolRunner(
from line 1022
10221022 </Tab>
10231023
10241024 <Tab title="PHP">
1025 Use `setMessagesParams()` and `pushMessages()` to modify the runner's state, and `getParams()` to read it. Calling either setter inside the loop tells the runner to skip its automatic append, so the conversation continues from your modified state instead.
1025 Use `setMessagesParams()` and `pushMessages()` to modify the runner’s state, and `getParams()` to read it. Calling either setter inside the loop tells the runner to skip its automatic append, so the conversation continues from your modified state instead.
10261026
10271027 The following example doubles `max_tokens` and retries when a response is cut off.
10281028
from line 1096
10961096
10971097### Automatic context management
10981098
1099For 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-threshold), 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. The tool runner has a `compact_before_next_turn()` (typescript, java, php: `compactBeforeNextTurn()`; csharp, go: `CompactBeforeNextTurn()`) helper for on-demand compaction. See [Compact in a loop](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand#compact-in-a-loop). Use it or a `context_management` compaction edit on a runner, not both.
1099For 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-threshold), 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. The tool runner has a `compact_before_next_turn()` (typescript, java, php: `compactBeforeNextTurn()`; csharp, go: `CompactBeforeNextTurn()`) helper for on-demand compaction. See [Compact in a loop](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand#compact-in-a-loop). Use it or a `context_management` compaction edit on a runner, not both.
11001100
11011101### Debugging tool execution
11021102
1103When a tool throws an exception, the tool runner catches it and returns the error to Claude as a tool result with `is_error: true`. The tool result carries the exception's message (in Python, its type and message), not the full stack trace.
1103When a tool throws an exception, the tool runner catches it and returns the error to Claude as a tool result with `is_error: true`. The tool result carries the exception’s message (in Python, its type and message), not the full stack trace.
11041104
1105What the SDK logs is language-specific. The Python SDK logs the full exception, including its stack trace, through the standard `logging` module whenever a tool raises an unhandled exception. The Python, TypeScript, and Java SDKs read the `ANTHROPIC_LOG` environment variable to turn on the SDK's logging, which includes request and response detail:
1105What the SDK logs is language-specific. The Python SDK logs the full exception, including its stack trace, through the standard `logging` module whenever a tool raises an unhandled exception. The Python, TypeScript, and Java SDKs read the `ANTHROPIC_LOG` environment variable to turn on the SDK’s logging, which includes request and response detail:
11061106
11071107```bash
11081108# Log at info level
from line 1112
11121112export ANTHROPIC_LOG=debug
11131113```
11141114
1115The Go, Ruby, C#, and PHP SDKs don't read `ANTHROPIC_LOG`. Outside Python, no SDK logs a failed tool: to see why a tool failed, catch and log the exception inside the tool function before returning or rethrowing it.
1115The Go, Ruby, C#, and PHP SDKs don’t read `ANTHROPIC_LOG`. Outside Python, no SDK logs a failed tool: to see why a tool failed, catch and log the exception inside the tool function before returning or rethrowing it.
11161116
11171117### Intercepting tool errors
11181118
from line 1199
11991199 </Tab>
12001200
12011201 <Tab title="C#">
1202 The C# tool runner doesn't expose a hook for inspecting the tool result before it's sent to Claude. To control error content, throw `BetaToolError` from inside the tool body. The runner converts it to a `tool_result` with `is_error: true` and the content you supply.
1202 The C# tool runner doesn’t expose a hook for inspecting the tool result before it’s sent to Claude. To control error content, throw `BetaToolError` from inside the tool body. The runner converts it to a `tool_result` with `is_error: true` and the content you supply.
12031203
12041204 ```csharp
12051205 var client = new AnthropicClient();
from line 1252
12521252 </Tab>
12531253
12541254 <Tab title="Go">
1255 Intercepting tool errors before they're sent to Claude is not currently supported in the Go SDK. The runner converts an error returned from your handler into a tool result with `is_error: true` internally. To customize the error content, catch the error inside your handler and return a result instead of returning the error.
1255 Intercepting tool errors before they’re sent to Claude is not currently supported in the Go SDK. The runner converts an error returned from your handler into a tool result with `is_error: true` internally. To customize the error content, catch the error inside your handler and return a result instead of returning the error.
12561256 </Tab>
12571257
12581258 <Tab title="Java">
1259 Intercepting tool errors before they're sent to Claude is not currently supported in the Java SDK. The runner catches any exception thrown from a tool's function and converts it into a tool result with `is_error: true` automatically. To control the error content, catch the exception inside the function and return your own content.
1259 Intercepting tool errors before they’re sent to Claude is not currently supported in the Java SDK. The runner catches any exception thrown from a tool’s function and converts it into a tool result with `is_error: true` automatically. To control the error content, catch the exception inside the function and return your own content.
12601260 </Tab>
12611261
12621262 <Tab title="PHP">
1263 The PHP tool runner does not currently expose tool results before they are appended. Exceptions thrown from a tool's `run` closure are caught and sent to Claude as tool results with `is_error: true` automatically. To inspect or replace error content, use the manual `pushMessages()` pattern shown in [Modifying tool results](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-runner#modifying-tool-results).
1263 The PHP tool runner does not currently expose tool results before they are appended. Exceptions thrown from a tool’s `run` closure are caught and sent to Claude as tool results with `is_error: true` automatically. To inspect or replace error content, use the manual `pushMessages()` pattern shown in [Modifying tool results](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-runner#modifying-tool-results).
12641264 </Tab>
12651265
12661266 <Tab title="Ruby">
from line 1316
13161316
13171317### Modifying tool results
13181318
1319You can modify tool results before they're sent back to Claude. This is useful for adding metadata such as `cache_control` to enable [prompt caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) on tool results, or for transforming the tool output.
1319You can modify tool results before they’re sent back to Claude. This is useful for adding metadata such as `cache_control` to enable [prompt caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) on tool results, or for transforming the tool output.
13201320
13211321<Tabs exclude="shell">
13221322 <Tab title="Python">
from line 1407
14071407 </Tab>
14081408
14091409 <Tab title="C#">
1410 Modifying tool results before they're appended (for example, to add `cache_control`) is not currently supported in the C# SDK. The runner constructs the `tool_result` block internally and provides no hook to alter it.
1410 Modifying tool results before they’re appended (for example, to add `cache_control`) is not currently supported in the C# SDK. The runner constructs the `tool_result` block internally and provides no hook to alter it.
14111411 </Tab>
14121412
14131413 <Tab title="Go">
from line 1482
14821482 </Tab>
14831483
14841484 <Tab title="PHP">
1485 The PHP tool runner has no callback to mutate the auto-generated `tool_result` block. To add fields such as `cache_control`, build the tool result yourself and push it. Calling `pushMessages()` skips the runner's auto-append for that turn.
1485 The PHP tool runner has no callback to mutate the auto-generated `tool_result` block. To add fields such as `cache_control`, build the tool result yourself and push it. Calling `pushMessages()` skips the runner’s auto-append for that turn.
14861486
14871487 ```php
14881488 $client = new Client();
from line 1576
15761576
15771577## Streaming
15781578
1579Enable streaming to process each turn's response incrementally. Each iteration yields a stream object that you can iterate for events.
1579Enable streaming to process each turn’s response incrementally. Each iteration yields a stream object that you can iterate for events.
15801580
15811581<Tabs exclude="shell">
15821582 <Tab title="Python">
from line 1768
17681768
17691769<CardGroup cols={2}>
17701770 <Card title="Strict tool use" icon="check" href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/strict-tool-use">
1771 Enforce JSON Schema compliance on Claude's tool inputs with grammar-constrained sampling.
1771 Enforce JSON Schema compliance on Claude’s tool inputs with grammar-constrained sampling.
17721772 </Card>
17731773
17741774 <Card title="Handle tool calls" icon="arrows-left-right" href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/handle-tool-calls">
No line in this hunk matches that.