we-promise/sure · error · Assistant::Responder::ToolCallLimitError

Assistant exceeded the tool-call limit of #{max_tool_call_it

Error message

Assistant exceeded the tool-call limit of #{max_tool_call_iterations} for one response

What it means

Raised by Assistant::Responder#respond as ToolCallLimitError when the LLM keeps requesting function calls for more than max_tool_call_iterations round trips within a single user turn. The counter increments once per loop iteration (one LLM response with function_requests → tool execution → next LLM response); the default limit is 5, overridable via ENV['ASSISTANT_MAX_TOOL_CALL_ITERATIONS']. It is a runaway-loop guard so one turn cannot ping-pong with tools forever.

Source

Thrown at app/models/assistant/responder.rb:26

    @instructions = instructions
    @function_tool_caller = function_tool_caller
    @llm = llm
  end

  def on(event_name, &block)
    listeners[event_name.to_sym] << block
  end

  def respond(previous_response_id: nil)
    response, response_has_text = request_response(previous_response_id: previous_response_id)
    any_response_has_text = response_has_text
    in_flight_function_results = []
    iteration = 0

    while response.function_requests.any?
      iteration += 1
      if iteration > max_tool_call_iterations
        raise ToolCallLimitError,
              "Assistant exceeded the tool-call limit of #{max_tool_call_iterations} for one response"
      end

      function_tool_calls = function_tool_caller.fulfill_requests(response.function_requests)
      function_results = function_tool_calls.map(&:to_result)
      in_flight_function_results.concat(function_results)

      emit(:response, {
        id: response.id,
        function_tool_calls: function_tool_calls
      })

      response, response_has_text = request_response(
        function_results: provider_preserves_response_context? ? function_results : in_flight_function_results.dup,
        previous_response_id: response.id
      )
      any_response_has_text ||= response_has_text
    end

View on GitHub (pinned to e69894adb9)

Solutions

  1. Raise the limit via ENV when the workflow legitimately needs more round trips: ASSISTANT_MAX_TOOL_CALL_ITERATIONS=10 (invalid or non-positive values fall back to 5).
  2. Inspect the emitted :response events to see which tool the model loops on, then fix that tool's output — usually an error result it cannot recover from.
  3. Tighten instructions so the model must answer after gathering data, and encourage batching (update_budget accepts multiple categories in one call).
  4. Rescue Assistant::Responder::ToolCallLimitError in the caller and surface a friendly message instead of a raw 500.

Example fix

# before
responder.respond

# after — bound the turn and degrade gracefully
begin
  responder.respond
rescue Assistant::Responder::ToolCallLimitError
  message.update!(content: "I hit my tool-use limit trying to answer that. Could you narrow the request?")
end

# and/or, in env, when the workflow legitimately needs more hops:
# ASSISTANT_MAX_TOOL_CALL_ITERATIONS=10
Defensive patterns

Strategy: try-catch

Validate before calling

limit = Integer(ENV.fetch("ASSISTANT_MAX_TOOL_CALL_ITERATIONS", 5))
raise ArgumentError, "ASSISTANT_MAX_TOOL_CALL_ITERATIONS must cover this workflow" if workflow_needs_more_hops_than?(limit)

Try / catch

begin
  responder.respond(previous_response_id: prev_id)
rescue Assistant::Responder::ToolCallLimitError
  # surface partial progress; the turn's tool calls were already emitted via :response events
  message.update!(content: "I hit my tool-use limit — please narrow the request or continue in a new message.")
end

Prevention

When it happens

Trigger: The while loop at app/models/assistant/responder.rb:23-44 runs a 6th iteration: e.g. the model alternates get_budget → update_budget → get_budget → ... never emitting text; or a tool keeps returning an error payload the model keeps retrying; or the task legitimately needs more than 5 sequential tool round trips (default).

Common situations: Broad prompts requiring many sequential lookups (scan every account, then every month); tool results that are error hashes the model retries instead of giving up; instructions that encourage verification loops after each write; ASSISTANT_MAX_TOOL_CALL_ITERATIONS lowered or left at default while workflows grew.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/745edbc21b1eee2b. Report an issue: GitHub.