Skip to content

How the AI Agent Loop Pattern Works: The Core of Every AI Agent

I spent weeks trying to build an AI agent. I kept overcomplicating it with state machines, complex workflows, and endless abstractions. Then I finally understood the core pattern.

It’s a while True loop.

That’s it. The entire agent architecture in under 30 lines of code.

The Problem: Over-Engineering Agents

When I first started building AI agents, I thought I needed:

  • Complex state management
  • Workflow orchestration frameworks
  • Multi-stage pipelines
  • Event-driven architectures

I was wrong. Every AI agent - from simple coding assistants to complex multi-agent systems - is built on one fundamental pattern: the agent loop.

The Pattern Revealed

Here’s what every AI agent actually does:

Agent Loop Flow
User --> messages[] --> LLM --> response
|
stop_reason == "tool_use"?
/ \
yes no
| |
execute tools return text
append results
loop back -----------------> messages[]

The model decides when to call tools and when to stop. The code just executes what the model asks for.

The Core Implementation

Let me show you the actual code. This is from a real agent implementation:

s01_agent_loop.py
def agent_loop(messages: list):
while True:
response = client.messages.create(
model=MODEL,
system=SYSTEM,
messages=messages,
tools=TOOLS,
max_tokens=8000,
)
# Append the assistant's response to conversation history
messages.append({"role": "assistant", "content": response.content})
# Check if the model wants to stop or use tools
if response.stop_reason != "tool_use":
return
# Execute all requested tools
results = []
for block in response.content:
if block.type == "tool_use":
print(f"\033[33m$ {block.input['command']}\033[0m")
output = run_bash(block.input["command"])
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output
})
# Inject results back as user message
messages.append({"role": "user", "content": results})

Under 30 lines. Everything else in agent engineering layers on top of this - without changing the loop.

How the Loop Actually Works

Let me walk through each step:

Step 1: User Input Becomes the First Message

messages = [{"role": "user", "content": "List all Python files in the src directory"}]

The user’s prompt initializes the message array.

Step 2: Send Messages + Tool Definitions to LLM

response = client.messages.create(
model=MODEL,
system=SYSTEM,
messages=messages,
tools=TOOLS, # Tell the model what tools it can use
max_tokens=8000,
)

The LLM receives:

  • The conversation history
  • Available tool definitions (name, description, parameters)
  • System prompt

Step 3: Check the stop_reason

if response.stop_reason != "tool_use":
return # Model is done, return the text response

The stop_reason tells you what the model wants:

  • "tool_use" - The model wants to execute tools
  • "end_turn" - The model is done and returning text

Step 4: Execute Tools and Loop Back

# Execute each tool the model requested
results = []
for block in response.content:
if block.type == "tool_use":
output = run_bash(block.input["command"])
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output
})
# Append results as a user message
messages.append({"role": "user", "content": results})
# Loop back to step 2

The tool results become a user message, and the loop continues.

Why This Matters

I used to think I needed to control when tools were called. I’d write complex logic like:

# DON'T DO THIS - Over-engineered
if "file" in user_input:
call_file_tool()
elif "search" in user_input:
call_search_tool()
else:
ask_llm()

This is wrong. The model controls the flow. Your code just:

  1. Sends messages to the LLM
  2. Checks if it wants tools
  3. Executes what it asks for
  4. Appends results
  5. Loops back

The motto: “One loop & Bash is all you need”

Common Mistakes I Made

Mistake 1: Adding Exit Conditions Beyond stop_reason

I tried to add my own exit logic:

# WRONG - Don't add your own exit conditions
while iteration < MAX_ITERATIONS:
# ... agent loop
if response_contains_answer(response):
break

The model knows when it’s done. Trust stop_reason.

Mistake 2: Not Appending All Message Types

I forgot that every response needs to be in the conversation:

# WRONG - Missing assistant response
if response.stop_reason == "tool_use":
# Execute tools
messages.append({"role": "user", "content": results})
# Forgot to append assistant response!

You must append both:

  1. The assistant’s response (with tool use blocks)
  2. The tool results as a user message

Mistake 3: Complex Tool Selection Logic

I thought I needed to decide which tools to offer:

# WRONG - Don't filter tools
if "code" in user_input:
tools = [bash_tool, read_tool, write_tool]
else:
tools = [search_tool]

Just pass all tools. The model knows what it needs.

How This Scales

All 12 sessions of advanced agent courses preserve this core loop. What they add:

  • Planning: A tool that writes plans
  • Memory: Tools that read/write memory
  • Teams: Multiple agents with shared message history
  • Delegation: One agent calling another as a tool

But the loop never changes. It’s always:

while True:
response = call_llm(messages, tools)
messages.append(assistant_response)
if response.stop_reason != "tool_use":
return
messages.append(user_message_with_tool_results)

The Key Insight

“The MODEL decides when to call tools and when to stop. The CODE just executes what the model asks for.”

This shifted my entire mental model. I stopped trying to build complex control flows and started trusting the LLM’s reasoning.

The loop belongs to the agent. The mechanisms belong to the harness.

What the Loop Provides

  1. State management: The messages array IS your state
  2. Tool orchestration: The model decides what to call and when
  3. Conversation history: Just append, never mutate
  4. Exit conditions: stop_reason handles it all
  • Tool Calling: How LLMs request function execution through structured outputs
  • Message History: Why conversation context matters for multi-turn reasoning
  • Stop Sequences: How models signal completion vs continuation

Summary

The AI agent loop is the heartbeat of every AI agent:

  1. Send messages + tools to LLM
  2. Check stop_reason
  3. Execute tools if needed
  4. Append results
  5. Loop back

Everything else - planning, context, teams, memory - is built on this foundation. The pattern is elegant because it’s simple. The power comes from the model, not the code.

Next time you’re building an agent, start with the loop. Add complexity only when you need it.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments