
Cloud Edventures
Everyone throws around βAI agentβ like it explains something. It doesnβt.
The actual mechanism β the thing that makes Claude act instead of just respond β is called an agentic loop.
Itβs not a prompt trick. Itβs not retry logic. Itβs a deterministic control flow driven by a single field in the API response.
Once you understand it, every Claude-based agent becomes readable β multi-agent systems, MCP servers, tool chains β all of it.
Hereβs exactly how it works.
A standard Claude API call is one-shot:
You send a message β Claude responds β Done.
An agent is different.
Instead of answering immediately, Claude evaluates whether it needs more information:
This cycle β send β inspect β act β repeat β is the agentic loop.
When using the AWS Bedrock Converse API, every response includes a stopReason.
For agent systems, only two values matter:
Thatβs the entire control mechanism.
No parsing. No heuristics. No guesswork.
Just stopReason.
import boto3
client = boto3.client("bedrock-runtime", region_name="us-east-1")
tools = [{
"toolSpec": {
"name": "get_order_status",
"description": "Look up the status of a customer order by order ID",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"order_id": {"type": "string"}
},
"required": ["order_id"]
}
}
}
}]
def run_tool(tool_name, tool_input):
if tool_name == "get_order_status":
return {"status": "shipped", "eta": "2 days"}
return {"error": "unknown tool"}
def run_agent(user_message):
messages = [{"role": "user", "content": [{"text": user_message}]}]
for _ in range(20): # Safety cap
response = client.converse(
modelId="anthropic.claude-3-sonnet-20240229-v1:0",
messages=messages,
toolConfig={"tools": tools}
)
stop_reason = response["stopReason"]
output_message = response["output"]["message"]
# Always append Claude's response first
messages.append(output_message)
if stop_reason == "end_turn":
for block in output_message["content"]:
if "text" in block:
return block["text"]
elif stop_reason == "tool_use":
tool_results = []
for block in output_message["content"]:
if "toolUse" in block:
tool = block["toolUse"]
result = run_tool(tool["name"], tool["input"])
tool_results.append({
"toolResult": {
"toolUseId": tool["toolUseId"],
"content": [{"json": result}]
}
})
messages.append({"role": "user", "content": tool_results})
return "Max iterations reached"
# Required
messages.append(output_message)
Without this, Claude loses context and may hallucinate or loop infinitely.
# WRONG
if message["content"][0]["type"] == "text":
return
# RIGHT
if stop_reason == "end_turn":
return
Claude can return text alongside tool calls. Text presence β completion.
for i in range(10):
if i == 9:
break # Wrong
Iteration limits are safety guards β not control logic.
stopReason decides completion, not counters.
Once you understand the loop, everything else is just an extension:
The system scales because the model makes decisions, not your control flow.
Your job is to implement the loop correctly and trust the signal.
Reading this is one thing.
Watching stopReason flip between tool_use and end_turn in real time is where it clicks.
This pattern is covered in Mission 1 of the Claude Certified Architect track on Cloud Edventures.
What part of the agentic loop pattern is least clear? Drop a comment β happy to go deeper.
42 people reacted to this article
Written by Cloud Edventures
Previous
No more articles
Next
No more articles