Agentic Loops & Core API
Claude Messages API gives you fine-grained control over the agent’s behavior. But with that control, your app is responsible for managing the loop, executing tools, and sending results back to Claude. That’s where agentic loops can simplify things.
1. Core Concept
An agentic loop is a deterministic control-flow pattern around Claude. It is not a prompt trick, not a retry loop, and not a normal chatbot turn.
The canonical loop is:
1. User sends task.
2. App calls Messages API with messages + tools.
3. Claude responds.
4. App checks stop_reason.
5. If stop_reason == tool_use:
a. Extract all tool_use blocks.
b. Execute corresponding tools in app code.
c. Append assistant message to history.
d. Append user tool_result message to history.
e. Call Claude again.
6. If stop_reason == end_turn:
Return final answer.
7. If stop_reason is max_tokens / refusal:
Run the appropriate fallback path.
For this module, the two stop reasons to know cold are:
stop_reason |
Meaning | Loop action |
|---|---|---|
tool_use |
Claude wants one or more tools executed | Continue the loop |
end_turn |
Claude has finished naturally | Terminate and return final answer |
Officially, stop_reason is part of every successful Messages API response and tells you why Claude stopped generating; broader production code should also handle values such as max_tokens and refusal, but this module's emphasis is tool_use vs end_turn.
1.1 Request Fields
While making a call to Claude Message API, you need to forward these arguments:
| Field | Purpose |
|---|---|
model |
Selects Claude model |
max_tokens |
Caps output tokens |
system |
Top-level system instruction |
messages |
Conversation history |
tools |
Tool definitions |
tool_choice |
Controls whether Claude may/must call tools |
output_config.format |
Structured JSON output |
1.2 Message Roles
role |
Meaning |
|---|---|
user |
User/app-provided turn, including tool_result blocks |
assistant |
Claude’s prior response, including tool_use blocks |
system |
Instructional authority; normally top-level, with newer support for mid-conversation system messages under placement rules |
1.3 content Blocks
| Block type | Direction | Purpose |
|---|---|---|
text |
Claude → app/user | Natural language |
tool_use |
Claude → app | Structured request to run a tool |
tool_result |
app → Claude | Result of a prior tool call |
thinking |
Claude → app | Thinking/summarized thinking when enabled |
| citation blocks | Claude → app | Source-grounded references when citations are enabled |
1.4 Stop Reasons
stop_reason is a top-level field on the assistant Message response, just like content, role, usage, etc.
| Stop reason | Meaning | Correct handling |
|---|---|---|
end_turn |
Claude finished naturally | Return/process final answer |
tool_use |
Claude wants a tool executed | Execute tool(s), send tool_result, continue loop |
max_tokens |
Output hit your token cap | Treat as truncated; continue or show truncation notice |
stop_sequence |
Custom stop sequence hit | Respect the sequence and inspect output |
refusal |
Claude declined to answer | Handle as a successful response with fallback/escalation |
Sample JSON response with role, content, and stop_reason:
{
"id": "msg_...",
"type": "message",
"role": "assistant",
"content": [
{
"type": "text",
"text": "Here's the answer..."
}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 100,
"output_tokens": 50
}
}
1.5 The Most Important Rule
Use
stop_reasonas the authoritative loop-control signal.
Do not terminate because Claude produced text and stopped. Claude can return text and tool_use then for a user message (app response) with tool_result block. The classic bug scenario is that code checks response.content[0].type == "text", sees text, and returns early even though Claude also requested a tool.
Correct logic:
if response.stop_reason == "tool_use":
# execute tools and continue
elif response.stop_reason == "end_turn":
# final answer
else:
# fallback handling for other stop reasons
Incorrect logic:
That is the premature-termination bug.
2. Tool-Use Contract
Tool use is a contract between your application and Claude:
- You define tools and schemas.
- Claude decides when to call a tool.
- Claude emits a structured
tool_useblock. - Your application executes the tool.
- Your application returns a
tool_result. - Claude reasons over that result and either calls another tool or ends.
Anthropic’s docs are explicit that Claude does not execute client tools itself; your code executes the operation and sends the result back.
The next request after a tool call must include:
This is critical. If you execute the tool but do not append the tool result to history, Claude cannot reason over the new information in the next iteration.
2.1 tool_choice
| Option | Meaning |
|---|---|
auto |
Claude decides whether to call a tool |
any |
Claude must call one of the tools |
tool |
Claude must call a specific tool |
none |
Claude cannot use tools |
2.2 strict Tool Use
Use strict: true when tool arguments must conform to JSON Schema. Anthropic describes strict tool use as grammar-constrained sampling that guarantees Claude’s tool inputs match the schema.
If the scenario says “tool arguments must be valid,” “nested schema,” “downstream type safety,” or “malformed parameters are causing failures,” look for strict tool use and/or schema validation.
2.3 Handling Tool Calls Correctly
assistant:
content:
type: tool_use
id: toolu_123
name: get_order
input: { "order_id": "A123" }
user:
content:
- type: tool_result
tool_use_id: toolu_123
content: "{ \"status\": \"shipped\" }"
When Claude returns a client tool call, the response has stop_reason: "tool_use" and one or more tool_use blocks containing id, name, and input. Your next message should include tool_result blocks whose tool_use_id matches the original tool_use_id.
2.4 Parallel Tool Use
Claude may return multiple tool_use blocks in one assistant turn. Anthropic says the API does not prescribe execution order; your app can execute them concurrently, sequentially, or in another pattern that fits your tool semantics.
3. Model-Driven Decision-Making
Agentic loops are normally model-driven, not fixed decision trees. Claude chooses which tool to call based on the current context and available tools.
Example interpretation:
| Scenario | Preferred architecture |
|---|---|
| Claude should decide whether to search, calculate, or answer directly | Model-driven tool selection |
| Compliance requires approval before refunding money | Deterministic app logic overrides model flexibility |
| Security policy must always block certain operations | Enforce in code, not only prompt instructions |
The key nuance: model-driven flexibility is preferred until business rules require deterministic enforcement.
3.1 Anti-Patterns
Three anti-patterns highly relevant:
| Anti-pattern | Why wrong | Correct answer |
|---|---|---|
| Parsing natural language like “I’m done” | Natural language is ambiguous | Use stop_reason |
| Using arbitrary iteration caps as the primary stop condition | Can cut off useful work or waste calls | Use stop_reason; cap only as safety net |
Checking response.content[0].type == "text" |
Claude can return text plus tool calls | Use stop_reason |
A safety cap is still good engineering. A cap such as MAX_ITERATIONS = 20 is recommended, but only as a runaway-loop guard, not as the primary termination mechanism.
4. Decision Heuristics
| Scenario wording | Best answer pattern |
|---|---|
| “Must call external system” | Tool use |
| “Must not proceed without approval” | Loop with human gate |
| “Must produce parseable JSON” | Structured outputs |
| “Tool inputs must be schema-valid” | Strict tool use |
| “Multiple independent lookups” | Parallel tool use |
| “Order matters” | Disable/avoid parallel tool use |
| “Long-running task, many turns” | Compaction / Managed Agents |
| “Need custom audit logs” | Manual loop |
| “Output cut off” | Handle max_tokens / continue generation |
| “Context limit reached” | Compaction/context management |
4.1 Common Mistakes
- “Just improve the prompt” when the issue requires schema validation, code validation, or human approval.
- Ignoring
stop_reason. - Assuming one and only one tool call.
- Returning tool results without matching
tool_use_id. - Letting Claude decide irreversible writes without approval.
- Returning huge raw tool payloads into context.
- Treating
max_tokensas an API error. - Treating
refusalas an exception instead of a successful response state. - Using parallel tool execution when calls mutate shared state.
- Using a managed abstraction when the scenario requires custom execution control.
- Depending on assistant prose formatting rather than content block types.
- Summarizing away facts needed for audit/compliance.
- Letting tool descriptions be vague or overlapping.
- Failing to test error cases, not just happy paths.