Agentic AI
The Agent Loop: Why Every Claude Agent Is Just a While Loop
By this point in the series you've seen Claude call one tool and answer. You've seen it save memory. You've seen a terminal transcript where it fixed a failing test suite across multiple files. All three of those are the same mechanism, run a different number of times: a loop that keeps asking Claude "what next?" until the task is actually finished. This post is that loop, in full, with nothing hidden.
The one-tool version doesn't loop
In post #1, we called Claude once, checked if it wanted a tool, ran the tool, and called it again for a final answer. That's not really a loop — it's one detour. Real tasks — "fix the failing tests," "research this topic and summarize it," "refactor this module" — usually need Claude to call a tool, look at the result, decide it needs another tool, look at that result, and so on, an unknown number of times before it's actually done.
The loop, written out
import anthropic
client = anthropic.Anthropic()
def run_agent(user_task: str, tools: list, tool_functions: dict, max_turns: int = 10):
messages = [{"role": "user", "content": user_task}]
for turn in range(max_turns):
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=2048,
tools=tools,
messages=messages,
)
# Done: Claude produced its final answer, no more tools needed
if response.stop_reason != "tool_use":
return response.content[0].text
# Not done: run every tool Claude asked for, feed the results back
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
function = tool_functions[block.name]
result = function(**block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": str(result),
})
messages.append({"role": "user", "content": tool_results})
return "Stopped: hit max_turns without finishing."That's the entire mechanism. Read it slowly once:
- Send the conversation so far to Claude.
- If it stopped for a reason other than
tool_use, it's done — return the answer. - Otherwise, run whatever tool(s) it asked for.
- Append both Claude's request and your tool results to the conversation.
- Go back to step 1.
Claude Code, an MCP-connected research agent, a customer-support bot that looks things up before answering — all of them are this loop, with more tools, better error handling, and usually a smarter stopping condition than "run out of turns."
Why max_turns matters more than it looks
Notice the loop has a hard ceiling (max_turns=10). Without one, a confused agent — stuck between two tools that keep contradicting each other, or hallucinating a tool call that never succeeds — can run forever, burning API calls and money the entire time. A bounded loop isn't a performance detail; it's the first guardrail every real agent needs. (More on that in the next post — it's the difference between "the agent tried something odd once" and "the agent racked up a huge bill overnight.")
Watching it think, turn by turn
If you print the tool calls as they happen, you can watch the actual reasoning unfold — genuinely the most useful debugging habit for agent work:
for block in response.content:
if block.type == "tool_use":
print(f" → calling {block.name}({block.input})")Run a multi-step task through this and you'll see something like:
→ calling search_docs({'query': 'refund policy'})
→ calling search_docs({'query': 'refund policy exceptions'})
→ calling send_reply({'text': '...'})That's Claude deciding, on its own, that one search wasn't enough — searching again with a refined query before it was confident enough to answer. That decision — "was that good enough, or do I need to try again?" — only exists because the loop lets it run more than once.
The stopping condition is the whole design problem
Everything interesting about building a good agent lives in two questions: what tools does it have, and when does it stop. Give it too few turns, and it gives up on real multi-step tasks. Give it unlimited turns with no oversight, and you've built the setup for the next post in this series. Getting that ceiling right — and adding checkpoints where a human confirms before anything consequential happens — is most of what separates a toy demo from something you'd actually trust running unattended.
Reference links
- Claude tool use overview: platform.claude.com/docs/en/agents-and-tools/tool-use/overview
- Official tutorial (builds this exact loop, step by step): platform.claude.com/docs/en/agents-and-tools/tool-use/build-a-tool-using-agent
- Claude Agent SDK (a production-grade version of this loop, maintained by Anthropic): platform.claude.com/docs/en/agent-sdk/overview
Next in this series: "I Connected Claude to My Terminal and Broke My Laptop" — what happens when this loop has too much room to run.
Related articles
Agentic AI
I Gave Claude Hands: My First Tool-Use Agent in 20 Lines
Agents sound complicated until you see the actual request-response loop underneath. Here's the whole thing, stripped down to one tool and 20 lines of Python.
- #claude
- #agentic-ai
- #tool-use
- #python
- #anthropic
Agentic AI
Building a Claude Agent That Remembers You (Without a Vector Database)
Memory is the first thing everyone asks about agents, and the first answer is always 'embeddings and a vector DB.' Here's the version that fits in a JSON file, and why you might not need more than that.
- #claude
- #agentic-ai
- #memory
- #python
- #anthropic
Agentic AI
5 Agent Mistakes I Made So You Don't Have To
Every one of these cost me real debugging time. Each has a one-line fix — here they are, with the code.
- #claude
- #agentic-ai
- #python
- #debugging
- #best-practices