Ring 2: The agentic loop
Ring 1 assumed Claude would call the tool exactly once. Real tasks often need several calls: Claude might create an event, read the confirmation, then create another. The fix is a while loop that keeps running tools and feeding results back until stop_reason is no longer "tool_use".
The other change is conversation history. Instead of rebuilding the messages array from scratch on each request, keep a running list and append to it. Every turn sees the complete prior context.
bash
#!/bin/bash
# Ring 2: The agentic loop.
TOOLS='[
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {"type": "array", "items": {"type": "string", "format": "email"}},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1}
}
}
},
"required": ["title", "start", "end"]
}
}
]'
run_tool() {
local name="$1"
local input="$2"
if [ "$name" = "create_calendar_event" ]; then
local title=$(echo "$input" | jq -r '.title')
jq -n --arg title "$title" '{event_id: "evt_123", status: "created", title: $title}'
else
echo "{\"error\": \"Unknown tool: $name\"}"
fi
}
# Keep the full conversation history in a JSON array so each turn sees prior context.
MESSAGES='[{"role": "user", "content": "Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com."}]'
call_api() {
curl -s https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d "$(jq -n --argjson tools "$TOOLS" --argjson messages "$MESSAGES" \
'{model: "claude-opus-5", max_tokens: 1024, tools: $tools, tool_choice: {type: "auto", disable_parallel_tool_use: true}, messages: $messages}')"
}
RESPONSE=$(call_api)
# Loop until Claude stops asking for tools. Each iteration runs the requested
# tool, appends the result to history, and asks Claude to continue.
while [ "$(echo "$RESPONSE" | jq -r '.stop_reason')" = "tool_use" ]; do
TOOL_USE=$(echo "$RESPONSE" | jq '.content[] | select(.type == "tool_use")')
TOOL_NAME=$(echo "$TOOL_USE" | jq -r '.name')
TOOL_INPUT=$(echo "$TOOL_USE" | jq -c '.input')
TOOL_USE_ID=$(echo "$TOOL_USE" | jq -r '.id')
RESULT=$(run_tool "$TOOL_NAME" "$TOOL_INPUT")
ASSISTANT_CONTENT=$(echo "$RESPONSE" | jq '.content')
MESSAGES=$(echo "$MESSAGES" | jq \
--argjson assistant "$ASSISTANT_CONTENT" \
--arg tool_use_id "$TOOL_USE_ID" \
--arg result "$RESULT" \
'. + [
{role: "assistant", content: $assistant},
{role: "user", content: [{type: "tool_result", tool_use_id: $tool_use_id, content: $result}]}
]')
RESPONSE=$(call_api)
done
echo "$RESPONSE" | jq -r '.content[] | select(.type == "text") | .text'bash
#!/usr/bin/env bash
# Ring 2: The agentic loop.
# Uses jq for cross-turn message-array state — building an agentic loop in shell
# requires JSON manipulation beyond ant's single-call --transform scope.
set -euo pipefail
run_tool() {
local name="$1" input="$2"
if [ "$name" = "create_calendar_event" ]; then
jq -n --arg title "$(jq -r '.title' <<<"$input")" \
'{event_id: "evt_123", status: "created", title: $title}'
else
printf '{"error": "Unknown tool: %s"}' "$name"
fi
}
# Keep the full conversation history in a JSON array so each turn sees
# prior context.
MESSAGES='[{"role": "user", "content": "Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com."}]'
call_api() {
# ant reads the request body as YAML on stdin: no auth headers, no
# hand-built JSON envelope. The static keys (model, tools, tool_choice)
# live in a quoted heredoc; the growing messages array is appended as
# JSON, which YAML accepts as flow syntax.
{
cat <<'YAML'
model: claude-opus-5
max_tokens: 1024
tool_choice: {type: auto, disable_parallel_tool_use: true}
tools:
- name: create_calendar_event
description: Create a calendar event with attendees and optional recurrence.
input_schema:
type: object
properties:
title: {type: string}
start: {type: string, format: date-time}
end: {type: string, format: date-time}
attendees:
type: array
items: {type: string, format: email}
recurrence:
type: object
properties:
frequency: {enum: [daily, weekly, monthly]}
count: {type: integer, minimum: 1}
required: [title, start, end]
YAML
printf 'messages: %s\n' "$MESSAGES"
} | ant messages create --format json
}
RESPONSE=$(call_api)
# Loop until Claude stops asking for tools. Each iteration runs the
# requested tool, appends the result to history, and asks Claude to
# continue.
while [ "$(jq -r '.stop_reason' <<<"$RESPONSE")" = "tool_use" ]; do
TOOL_USE=$(jq '.content[] | select(.type == "tool_use")' <<<"$RESPONSE")
TOOL_NAME=$(jq -r '.name' <<<"$TOOL_USE")
TOOL_INPUT=$(jq -c '.input' <<<"$TOOL_USE")
TOOL_USE_ID=$(jq -r '.id' <<<"$TOOL_USE")
RESULT=$(run_tool "$TOOL_NAME" "$TOOL_INPUT")
MESSAGES=$(jq \
--argjson assistant "$(jq '.content' <<<"$RESPONSE")" \
--arg tool_use_id "$TOOL_USE_ID" \
--arg result "$RESULT" \
'. + [
{role: "assistant", content: $assistant},
{role: "user", content: [
{type: "tool_result", tool_use_id: $tool_use_id, content: $result}
]}
]' <<<"$MESSAGES")
RESPONSE=$(call_api)
done
jq -r '.content[] | select(.type == "text") | .text' <<<"$RESPONSE"python
# Ring 2: The agentic loop.
import json
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {
"type": "array",
"items": {"type": "string", "format": "email"},
},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1},
},
},
},
"required": ["title", "start", "end"],
},
}
]
def run_tool(name, tool_input):
if name == "create_calendar_event":
return {"event_id": "evt_123", "status": "created", "title": tool_input["title"]}
return {"error": f"Unknown tool: {name}"}
# Keep the full conversation history in a list so each turn sees prior context.
messages = [
{
"role": "user",
"content": "Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com.",
}
]
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto", "disable_parallel_tool_use": True},
messages=messages,
)
# Loop until Claude stops asking for tools. Each iteration runs the requested
# tool, appends the result to history, and asks Claude to continue.
while response.stop_reason == "tool_use":
tool_use = next(block for block in response.content if block.type == "tool_use")
result = run_tool(tool_use.name, tool_use.input)
messages.append({"role": "assistant", "content": response.content})
messages.append(
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": json.dumps(result),
}
],
}
)
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto", "disable_parallel_tool_use": True},
messages=messages,
)
final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)typescript
// Ring 2: The agentic loop.
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const tools: Anthropic.Tool[] = [
{
name: "create_calendar_event",
description:
"Create a calendar event with attendees and optional recurrence.",
input_schema: {
type: "object",
properties: {
title: { type: "string" },
start: { type: "string", format: "date-time" },
end: { type: "string", format: "date-time" },
attendees: {
type: "array",
items: { type: "string", format: "email" },
},
recurrence: {
type: "object",
properties: {
frequency: { enum: ["daily", "weekly", "monthly"] },
count: { type: "integer", minimum: 1 },
},
},
},
required: ["title", "start", "end"],
},
},
];
function runTool(name: string, input: Record<string, unknown>) {
if (name === "create_calendar_event") {
return { event_id: "evt_123", status: "created", title: input.title };
}
return { error: `Unknown tool: ${name}` };
}
// Keep the full conversation history so each turn sees prior context.
const messages: Anthropic.MessageParam[] = [
{
role: "user",
content:
"Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com.",
},
];
let response = await client.messages.create({
model: "claude-opus-5",
max_tokens: 1024,
tools,
tool_choice: { type: "auto", disable_parallel_tool_use: true },
messages,
});
// Loop until Claude stops asking for tools. Each iteration runs the requested
// tool, appends the result to history, and asks Claude to continue.
while (response.stop_reason === "tool_use") {
const toolUse = response.content.find(
(block): block is Anthropic.ToolUseBlock => block.type === "tool_use",
)!;
const result = runTool(toolUse.name, toolUse.input as Record<string, unknown>);
messages.push({ role: "assistant", content: response.content });
messages.push({
role: "user",
content: [
{
type: "tool_result",
tool_use_id: toolUse.id,
content: JSON.stringify(result),
},
],
});
response = await client.messages.create({
model: "claude-opus-5",
max_tokens: 1024,
tools,
tool_choice: { type: "auto", disable_parallel_tool_use: true },
messages,
});
}
for (const block of response.content) {
if (block.type === "text") {
console.log(block.text);
}
}csharp
// Ring 2: The agentic loop.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
List<ToolUnion> tools =
[
new ToolUnion(new Tool()
{
Name = "create_calendar_event",
Description = "Create a calendar event with attendees and optional recurrence.",
InputSchema = new InputSchema()
{
Properties = new Dictionary<string, JsonElement>
{
["title"] = JsonSerializer.SerializeToElement(new { type = "string" }),
["start"] = JsonSerializer.SerializeToElement(new { type = "string", format = "date-time" }),
["end"] = JsonSerializer.SerializeToElement(new { type = "string", format = "date-time" }),
["attendees"] = JsonSerializer.SerializeToElement(new
{
type = "array",
items = new { type = "string", format = "email" },
}),
["recurrence"] = JsonSerializer.SerializeToElement(new
{
type = "object",
properties = new
{
frequency = new { @enum = new[] { "daily", "weekly", "monthly" } },
count = new { type = "integer", minimum = 1 },
},
}),
},
Required = ["title", "start", "end"],
},
}),
];
// Run the requested tool and return its result as a string.
string RunTool(ToolUseBlock toolUse)
{
if (toolUse.Name == "create_calendar_event")
{
var title = toolUse.Input.TryGetValue("title", out var t) ? t.GetString() : "";
return JsonSerializer.Serialize(new { event_id = "evt_123", status = "created", title });
}
return JsonSerializer.Serialize(new { error = $"Unknown tool: {toolUse.Name}" });
}
var toolChoice = new ToolChoice(new ToolChoiceAuto { DisableParallelToolUse = true });
// Keep the full conversation history in a list so each turn sees prior context.
List<MessageParam> messages =
[
new()
{
Role = Role.User,
Content = "Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com.",
},
];
var response = await client.Messages.Create(new MessageCreateParams
{
Model = Model.ClaudeOpus5,
MaxTokens = 1024,
Tools = tools,
ToolChoice = toolChoice,
Messages = messages,
});
// Loop until Claude stops asking for tools. Each iteration runs the requested
// tool, appends the result to history, and asks Claude to continue.
while (response.StopReason == StopReason.ToolUse)
{
ToolUseBlock? toolUse = null;
foreach (var block in response.Content)
{
if (block.TryPickToolUse(out var picked))
{
toolUse = picked;
break;
}
}
var result = RunTool(toolUse!);
messages.Add(new()
{
Role = Role.Assistant,
Content = response.Content.Select(block => new ContentBlockParam(block.Json)).ToList(),
});
messages.Add(new()
{
Role = Role.User,
Content = new MessageParamContent(
[
new ContentBlockParam(new ToolResultBlockParam() { ToolUseID = toolUse!.ID, Content = result }),
]),
});
response = await client.Messages.Create(new MessageCreateParams
{
Model = Model.ClaudeOpus5,
MaxTokens = 1024,
Tools = tools,
ToolChoice = toolChoice,
Messages = messages,
});
}
foreach (var block in response.Content)
{
if (block.TryPickText(out var text))
{
Console.WriteLine(text.Text);
}
}go
// Ring 2: The agentic loop.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/anthropics/anthropic-sdk-go"
)
func runTool(name string, input map[string]any) string {
if name == "create_calendar_event" {
title, _ := input["title"].(string)
return fmt.Sprintf(`{"event_id": "evt_123", "status": "created", "title": %q}`, title)
}
return fmt.Sprintf(`{"error": "Unknown tool: %s"}`, name)
}
func main() {
client := anthropic.NewClient()
ctx := context.Background()
tools := []anthropic.ToolUnionParam{
{OfTool: &anthropic.ToolParam{
Name: "create_calendar_event",
Description: anthropic.String("Create a calendar event with attendees and optional recurrence."),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"title": map[string]any{"type": "string"},
"start": map[string]any{"type": "string", "format": "date-time"},
"end": map[string]any{"type": "string", "format": "date-time"},
"attendees": map[string]any{
"type": "array",
"items": map[string]any{"type": "string", "format": "email"},
},
"recurrence": map[string]any{
"type": "object",
"properties": map[string]any{
"frequency": map[string]any{"enum": []string{"daily", "weekly", "monthly"}},
"count": map[string]any{"type": "integer", "minimum": 1},
},
},
},
Required: []string{"title", "start", "end"},
},
}},
}
toolChoice := anthropic.ToolChoiceUnionParam{
OfAuto: &anthropic.ToolChoiceAutoParam{DisableParallelToolUse: anthropic.Bool(true)},
}
// Keep the full conversation history in a slice so each turn sees prior context.
messages := []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock(
"Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com.",
)),
}
response, err := client.Messages.New(ctx, anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5,
MaxTokens: 1024,
Tools: tools,
ToolChoice: toolChoice,
Messages: messages,
})
if err != nil {
log.Fatal(err)
}
// Loop until Claude stops asking for tools. Each iteration runs the requested
// tool, appends the result to history, and asks Claude to continue.
for response.StopReason == "tool_use" {
var toolUse anthropic.ContentBlockUnion
for _, block := range response.Content {
if block.Type == "tool_use" {
toolUse = block
break
}
}
var input map[string]any
if err := json.Unmarshal(toolUse.Input, &input); err != nil {
log.Fatal(err)
}
result := runTool(toolUse.Name, input)
var assistantContent []anthropic.ContentBlockParamUnion
for _, block := range response.Content {
assistantContent = append(assistantContent, block.ToParam())
}
messages = append(messages, anthropic.NewAssistantMessage(assistantContent...))
messages = append(messages, anthropic.NewUserMessage(
anthropic.NewToolResultBlock(toolUse.ID, result, false),
))
response, err = client.Messages.New(ctx, anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5,
MaxTokens: 1024,
Tools: tools,
ToolChoice: toolChoice,
Messages: messages,
})
if err != nil {
log.Fatal(err)
}
}
for _, block := range response.Content {
if block.Type == "text" {
fmt.Println(block.Text)
}
}
}java
// Ring 2: The agentic loop.
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.JsonValue;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.MessageParam;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.StopReason;
import com.anthropic.models.messages.Tool;
import com.anthropic.models.messages.Tool.InputSchema;
import com.anthropic.models.messages.ToolChoiceAuto;
import com.anthropic.models.messages.ToolResultBlockParam;
import com.anthropic.models.messages.ToolUseBlock;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
String runTool(ToolUseBlock toolUse) {
// The raw tool input is a JSON object; read fields out of it as a map.
Map<String, JsonValue> input = (Map<String, JsonValue>) toolUse._input().asObject().get();
if (toolUse.name().equals("create_calendar_event")) {
String title = input.containsKey("title") ? input.get("title").asStringOrThrow() : "";
return "{\"event_id\": \"evt_123\", \"status\": \"created\", \"title\": \"" + title + "\"}";
}
return "{\"error\": \"Unknown tool: " + toolUse.name() + "\"}";
}
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
Tool calendarTool = Tool.builder()
.name("create_calendar_event")
.description("Create a calendar event with attendees and optional recurrence.")
.inputSchema(InputSchema.builder()
.properties(JsonValue.from(Map.of(
"title", Map.of("type", "string"),
"start", Map.of("type", "string", "format", "date-time"),
"end", Map.of("type", "string", "format", "date-time"),
"attendees", Map.of(
"type", "array",
"items", Map.of("type", "string", "format", "email")
),
"recurrence", Map.of(
"type", "object",
"properties", Map.of(
"frequency", Map.of("enum", List.of("daily", "weekly", "monthly")),
"count", Map.of("type", "integer", "minimum", 1)
)
)
)))
.required(List.of("title", "start", "end"))
.build())
.build();
ToolChoiceAuto toolChoice = ToolChoiceAuto.builder()
.disableParallelToolUse(true)
.build();
// Keep the full conversation history in a list so each turn sees prior context.
List<MessageParam> messages = new ArrayList<>();
messages.add(MessageParam.builder()
.role(MessageParam.Role.USER)
.content("Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com.")
.build());
Message response = client.messages().create(MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5)
.maxTokens(1024L)
.addTool(calendarTool)
.toolChoice(toolChoice)
.messages(messages)
.build());
// Loop until Claude stops asking for tools. Each iteration runs the requested
// tool, appends the result to history, and asks Claude to continue.
while (response.stopReason().isPresent()
&& response.stopReason().get().equals(StopReason.TOOL_USE)) {
ToolUseBlock toolUse = response.content().stream()
.flatMap(block -> block.toolUse().stream())
.findFirst()
.orElseThrow();
String result = runTool(toolUse);
messages.add(response.toParam());
messages.add(MessageParam.builder()
.role(MessageParam.Role.USER)
.contentOfBlockParams(List.of(ContentBlockParam.ofToolResult(
ToolResultBlockParam.builder()
.toolUseId(toolUse.id())
.content(result)
.build())))
.build());
response = client.messages().create(MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5)
.maxTokens(1024L)
.addTool(calendarTool)
.toolChoice(toolChoice)
.messages(messages)
.build());
}
response.content().stream()
.flatMap(block -> block.text().stream())
.forEach(textBlock -> IO.println(textBlock.text()));
}php
<?php
// Ring 2: The agentic loop.
use Anthropic\Client;
use Anthropic\Messages\ToolChoiceAuto;
$client = new Client();
$tools = [
[
'name' => 'create_calendar_event',
'description' => 'Create a calendar event with attendees and optional recurrence.',
'input_schema' => [
'type' => 'object',
'properties' => [
'title' => ['type' => 'string'],
'start' => ['type' => 'string', 'format' => 'date-time'],
'end' => ['type' => 'string', 'format' => 'date-time'],
'attendees' => [
'type' => 'array',
'items' => ['type' => 'string', 'format' => 'email'],
],
'recurrence' => [
'type' => 'object',
'properties' => [
'frequency' => ['enum' => ['daily', 'weekly', 'monthly']],
'count' => ['type' => 'integer', 'minimum' => 1],
],
],
],
'required' => ['title', 'start', 'end'],
],
],
];
function runTool(string $name, array $input): string
{
if ($name === 'create_calendar_event') {
return json_encode([
'event_id' => 'evt_123',
'status' => 'created',
'title' => $input['title'],
]);
}
return json_encode(['error' => "Unknown tool: {$name}"]);
}
$toolChoice = ToolChoiceAuto::with(disableParallelToolUse: true);
// Keep the full conversation history in an array so each turn sees prior context.
$messages = [
[
'role' => 'user',
'content' => 'Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com.',
],
];
$response = $client->messages->create(
model: 'claude-opus-5',
maxTokens: 1024,
tools: $tools,
toolChoice: $toolChoice,
messages: $messages,
);
// Loop until Claude stops asking for tools. Each iteration runs the requested
// tool, appends the result to history, and asks Claude to continue.
while ($response->stopReason === 'tool_use') {
$toolUse = null;
foreach ($response->content as $block) {
if ($block->type === 'tool_use') {
$toolUse = $block;
break;
}
}
$result = runTool($toolUse->name, $toolUse->input);
$messages[] = ['role' => 'assistant', 'content' => $response->content];
$messages[] = [
'role' => 'user',
'content' => [
[
'type' => 'tool_result',
'tool_use_id' => $toolUse->id,
'content' => $result,
],
],
];
$response = $client->messages->create(
model: 'claude-opus-5',
maxTokens: 1024,
tools: $tools,
toolChoice: $toolChoice,
messages: $messages,
);
}
foreach ($response->content as $block) {
if ($block->type === 'text') {
echo $block->text, "\n";
}
}ruby
# Ring 2: The agentic loop.
require "anthropic"
client = Anthropic::Client.new
tools = [
{
name: "create_calendar_event",
description: "Create a calendar event with attendees and optional recurrence.",
input_schema: {
type: "object",
properties: {
title: {type: "string"},
start: {type: "string", format: "date-time"},
end: {type: "string", format: "date-time"},
attendees: {
type: "array",
items: {type: "string", format: "email"}
},
recurrence: {
type: "object",
properties: {
frequency: {enum: ["daily", "weekly", "monthly"]},
count: {type: "integer", minimum: 1}
}
}
},
required: ["title", "start", "end"]
}
}
]
def run_tool(name, input)
case name
when "create_calendar_event"
JSON.generate({event_id: "evt_123", status: "created", title: input[:title]})
else
JSON.generate({error: "Unknown tool: #{name}"})
end
end
tool_choice = {type: "auto", disable_parallel_tool_use: true}
# Keep the full conversation history in an array so each turn sees prior context.
messages = [
{
role: "user",
content: "Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com."
}
]
response = client.messages.create(
model: "claude-opus-5",
max_tokens: 1024,
tools: tools,
tool_choice: tool_choice,
messages: messages
)
# Loop until Claude stops asking for tools. Each iteration runs the requested
# tool, appends the result to history, and asks Claude to continue.
while response.stop_reason == :tool_use
tool_use = response.content.find { |block| block.type == :tool_use }
result = run_tool(tool_use.name, tool_use.input)
messages << {role: "assistant", content: response.content}
messages << {
role: "user",
content: [
{
type: "tool_result",
tool_use_id: tool_use.id,
content: result
}
]
}
response = client.messages.create(
model: "claude-opus-5",
max_tokens: 1024,
tools: tools,
tool_choice: tool_choice,
messages: messages
)
end
response.content.each do |block|
puts block.text if block.type == :text
endWhat to expect
text
I've set up your weekly team standup for the next 4 Mondays at 9am with Alice, Bob, and Carol invited.The loop might run once or several times depending on how Claude breaks down the task. Your code no longer needs to know in advance.