class

Anthropic::ToolRunner

Inherits Reference < Object

Automatic tool execution loop

Runs a conversation with Claude where tools are automatically executed and their results are fed back to Claude until the conversation completes.

Supports auto-compaction to manage conversation length in extended sessions.

# Basic usage - iterate all messages
runner = client.beta.messages.tool_runner(
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  messages: [Anthropic::MessageParam.user("What's the weather?")],
  tools: [weather_tool]
)
runner.each_message { |msg| pp msg.content }

# Step-by-step control
runner = client.beta.messages.tool_runner(...)
while msg = runner.next_message
  pp msg.content
  if some_condition
    runner.feed_messages([MessageParam.user("Actually, also check...")])
  end
end

# Streaming with tool execution
runner.each_streaming do |event|
  case event
  when Anthropic::ContentBlockDeltaEvent
    print event.text # event.text is a streaming helper, not Message#text
  end
end

Constants

RESUME_STOP_REASONS = ["pause_turn", "compaction"]

Stop reasons for unfinished turns. pause_turn pauses a long-running turn; compaction hands the turn back before the model answers (pause after compaction). Sending the turn back unchanged continues it.

Constructors

new(client : Client, model : String, max_tokens : Int32, messages : Array(MessageParam), tools : Array(Tool), max_iterations : Int32 = 10, system : String | Nil = nil, compaction : CompactionConfig | Nil = nil, speed : String | Nil = nil, thinking : ThinkingConfig | Nil = nil, output_config : OutputConfig | Nil = nil, inference_geo : String | Nil = nil, container : String | ContainerConfig | Nil = nil, betas : Array(String) = [] of String, use_beta : Bool = false)
Source

Instance methods

add_tools(*tools : Tool | ToolDefinition)

Offer more tools from the next request on.

Sends the tools' full definitions in tool_addition blocks with the next request, leaving the runner's tools and the prompt cache alone. A Tool runs under its name straight away, replacing a same-name tool even for a call already in the message being handled; a raw ToolDefinition is never run here and stops a same-name tool from running. Needs the inline-tools-2026-09-15 beta (attached automatically when changes are sent). Requires a beta runner (use_beta: true).

Queued changes only take effect on manual next_message loops: the each_*, final_message, and run_until_finished entry points reset the runner first, dropping anything queued before the run.

runner.add_tools(my_tool)
Source
compact_before_next_turn(compaction : CompactionParam | Nil = nil)

Compact the conversation before the model's next turn.

Once the current turn has finished, including any tool calls, the runner asks the API for a summary and replaces its messages with the compaction response, which is returned like any other message. Requires a beta runner (use_beta: true) and the compact-2026-09-04 beta (attached automatically).

Only takes effect on manual next_message loops: the each_*, final_message, and run_until_finished entry points reset the runner first, dropping a request queued before the run.

runner.compact_before_next_turn
Source
current_messages

Get the current accumulated messages (including tool results)

Source
each_message

Iterate through messages, auto-executing tools

Yields each message response, including those with tool use. Continues until max_iterations is reached or Claude stops using tools.

If compaction is enabled, automatically compresses conversation when token usage exceeds the configured threshold.

Note: This resets the runner state before iterating.

Source
each_streaming

Iterate through streaming events while auto-executing tools

Similar to each_message but yields streaming events in real-time. Tool execution still happens between streaming responses.

A pending explicit compaction runs silently as a single non-streaming turn (no events are yielded for it); the replaced conversation then streams normally.

runner.each_streaming do |event|
  case event
  when Anthropic::ContentBlockDeltaEvent
    if text = event.text
      print text
    end
  end
end
Source
feed_message(message : MessageParam)

Add a single message to the conversation

Source
feed_messages(messages : Array(MessageParam))

Add messages to the conversation mid-loop

Use this to inject additional context or instructions during tool execution. Messages are added after the current tool results.

while msg = runner.next_message
  # Check content and inject more messages if needed
  runner.feed_messages([
    Anthropic::MessageParam.user("Here's additional context: ..."),
  ])
end
Source
final_message

Get final message after all tool execution

Runs the entire conversation and returns the last message.

Source
finished?

Check if the runner has finished (no more tool calls or max iterations reached)

Source
last_response

Get the last response received

Source
next_message

Get the next message in the tool execution loop

Returns nil when the loop is complete (no more tool calls or max iterations). Use this for fine-grained control over the execution loop.

while msg = runner.next_message
  pp msg.content
  # Optionally inject messages
  runner.feed_messages([...]) if some_condition
end
Source
params

Get current runner parameters (read-only)

Useful for inspecting or logging the current state.

Source
remove_tools(*tools : Tool | String)

Withdraw tools from the next request on.

Sends tool_removal blocks with the next request. The tools stop being run straight away, so a call to one gets the "not found" error result. Needs the inline-tools-2026-09-15 beta (attached automatically when changes are sent). Requires a beta runner (use_beta: true).

Queued changes only take effect on manual next_message loops: the each_*, final_message, and run_until_finished entry points reset the runner first, dropping anything queued before the run.

runner.remove_tools("legacy_tool")
Source
reset

Reset the runner to its initial state

Source
run_until_finished

Run until finished and return all messages

Executes the entire tool loop and returns all messages generated.

messages = runner.run_until_finished
messages.each { |msg| pp msg.content }
Source