Tool runner is a Python SDK feature invoked via client.beta.messages.tool_runner(...) that executes an agentic loop: it sends a message to the model, parses tool-use blocks, executes the tools, routes results back, and repeats until the model stops requesting tools or hits a termination condition like max_iterations or model refusal. The runner manages message state across iterations (propagating container IDs, tracking token usage against a context_token_threshold, and compacting context when needed), tracks tool availability as the conversation adds or removes tools, and returns a final ParsedBetaMessage with usage metrics including cache and iteration counts.
The tool runner is invoked via client.beta.messages.tool_runner(...) and returns an object with an .until_done() method that executes the agent loop and returns the final ParsedBetaMessage.[1] The final ParsedBetaMessage contains ParsedBetaTextBlock items alongside fields including container, context_management, diagnostics, stop_details, and a BetaUsage object with cache_creation, inference_geo, iterations, server_tool_use, and speed fields.[1] The RequestOptions TypedDict in src/anthropic/lib/tools/_beta_runner.py defines the extra per-call options accepted by the tool runner: extra_headers, extra_query, extra_body, and timeout.[2]
BaseToolRunner supports a max_iterations parameter; once _iteration_count reaches or exceeds max_iterations, _should_stop() returns True and the tool loop terminates.[2] BaseSyncToolRunner.__run__ terminates the tool loop immediately when the model's stop_reason is "refusal", rather than attempting to execute the tool-use blocks, to avoid firing unconfirmed side effects.[2] After each turn, BaseSyncToolRunner.__run__ propagates the container.id from the last assistant message back into params["container"] to support programmatic tool calling with containers.[2] BaseSyncToolRunner.__run__ in src/anthropic/lib/tools/_beta_runner.py treats a pause_turn stop reason as non-terminal and continues iterating, supporting long-running or async tools that temporarily yield control without halting the agent loop.
BaseToolRunner._available_tool_names computes the currently active tool set after applying any mid-conversation tool_removal / tool_addition blocks; tools absent from this set are routed down the unknown-tool path even if the model still emits a tool_use for them.[2] available_tool_names is exported from anthropic.lib.tools._tool_dispatch, indicating the tool-dispatch module maintains a registry of recognized tool names.[1] BaseToolRunner.set_messages_params accepts either a new params dict or a callable that receives the existing params and returns updated params, and invalidates any cached tool call response.[2] BaseToolRunner.append_messages similarly invalidates the cached tool call response, causing tools to be called again on the next loop iteration.[2] BaseToolRunner attaches an x-stainless-helper header to every API call via stainless_helper_header, merged on top of any extra_headers the caller supplies.[2]
BaseSyncToolRunner._check_and_compact computes total token usage as input_tokens + cache_creation_input_tokens + cache_read_input_tokens + output_tokens and triggers compaction only when this sum exceeds context_token_threshold.[2] During client-side compaction, if the last message is from the assistant and contains only tool_use blocks, the entire message is dropped before issuing the compaction request to avoid a 400 error caused by an unpaired tool_use / tool_result.[2] BaseSyncToolRunner.__init__ emits a DeprecationWarning when compaction_control is enabled, directing users to server-side compaction via edits=[{'type': 'compact_20260112'}] in the params passed to tool_runner() instead.[2]
The tool runner is not supported with Pydantic v1; the entire TestSyncRunTools test class is skipped when Pydantic v1 is active.[1] Tool runner snapshot tests are auto-generated from the live API and can be updated by running ANTHROPIC_LIVE=1 ./scripts/test --inline-snapshot=fix -n0.[1]
Sources