An agentic loop against Claude Haiku: the model calls Google-Places-backed tools across multiple turns to find and rank venues, capped at 8 turns as a safety limit.

# app/services/google_places/agentic_location_search.rb

# lines 169–213 (run_agentic_loop)

def run_agentic_loop
  messages = [{ role: "user", content: "Please find venues that match my criteria." }]
  turns    = 0

  loop do
    turns += 1
    if turns > MAX_TURNS
      Rails.logger.warn("[AgenticLocationSearch] Safety limit reached after #{MAX_TURNS} turns")
      break
    end

    response = anthropic_client.messages.create(
      model:      CLAUDE_MODEL,
      max_tokens: 4096,
      system:     build_system_prompt,
      tools:      TOOL_DEFINITIONS,
      messages:   messages
    )

    # Official SDK returns SDK objects — serialize to plain hashes for messages history.
    content_blocks = response.content
    messages << { role: "assistant", content: serialize_content(content_blocks) }

    # Official SDK may return stop_reason as a symbol (:tool_use) rather than
    # a string ("tool_use"), so normalise before comparing.
    case response.stop_reason.to_s
    when "end_turn"
      # Ineligible types are already stripped by the pre-filter in dispatch_tool;
      # post_filter_venues applies a final Ruby check using cached Google types.
      venues = post_filter_venues(extract_final_venues(content_blocks))
      Rails.logger.info("[AgenticLocationSearch] Returning #{venues.size} venues after #{turns} turn(s)")
      return venues

    when "tool_use"
      tool_results = execute_tool_calls(content_blocks)
      messages << { role: "user", content: tool_results }

    else
      Rails.logger.warn("[AgenticLocationSearch] Unexpected stop_reason: #{response.stop_reason}")
      break
    end
  end

  []
end
 
The loop hands the model real tools (Google Places search, geocoding) and keeps feeding results back until it says end_turn or the turn cap trips.
# app/services/google_places/agentic_location_search.rb

# lines 169–213 (run_agentic_loop)

def run_agentic_loop
  messages = [{ role: "user", content: "Please find venues that match my criteria." }]
  turns    = 0

  loop do
    turns += 1
    if turns > MAX_TURNS
      Rails.logger.warn("[AgenticLocationSearch] Safety limit reached after #{MAX_TURNS} turns")
      break
    end

    response = anthropic_client.messages.create(
      model:      CLAUDE_MODEL,
      max_tokens: 4096,
      system:     build_system_prompt,
      tools:      TOOL_DEFINITIONS,
      messages:   messages
    )

    # Official SDK returns SDK objects — serialize to plain hashes for messages history.
    content_blocks = response.content
    messages << { role: "assistant", content: serialize_content(content_blocks) }

    # Official SDK may return stop_reason as a symbol (:tool_use) rather than
    # a string ("tool_use"), so normalise before comparing.
    case response.stop_reason.to_s
    when "end_turn"
      # Ineligible types are already stripped by the pre-filter in dispatch_tool;
      # post_filter_venues applies a final Ruby check using cached Google types.
      venues = post_filter_venues(extract_final_venues(content_blocks))
      Rails.logger.info("[AgenticLocationSearch] Returning #{venues.size} venues after #{turns} turn(s)")
      return venues

    when "tool_use"
      tool_results = execute_tool_calls(content_blocks)
      messages << { role: "user", content: tool_results }

    else
      Rails.logger.warn("[AgenticLocationSearch] Unexpected stop_reason: #{response.stop_reason}")
      break
    end
  end

  []
end