The Agent Loop: Where It All Comes Together
This is the heart of your agent. The loop does four things:
- Send messages + tool definitions to the model
- Receive response (text or tool call request)
- Execute any tool calls
- Repeat until done or hit max iterations
Let’s build it step by step.
Understanding Conversation State
The key to agents is maintaining state. Each iteration adds to the conversation:
input_list = [
{"role": "user", "content": "Create a weekly digest from ./notes"},
# Agent responds with a plan...
{"role": "assistant", "content": "I'll create a plan..."},
# Agent calls a tool...
{"type": "function_call", "name": "list_files", ...},
# Tool result goes back...
{"type": "function_call_output", "output": '{"files": [...]}'},
# Agent uses result and continues...
]
The input_list grows with each step. The model sees the full history and can make informed decisions.
The Core Agent Function
Here’s the complete agent loop:
Breaking Down the Loop
Let’s understand each part:
1. Initialize Conversation
input_list = [{"role": "user", "content": goal}]
Start with the user’s goal. This is the first message in the conversation.
2. Set Instructions
instructions = (
"You are a helpful ops agent.\\n"
"First, write a short plan (3-7 steps).\\n"
"Then execute the plan by calling tools when needed.\\n"
...
)
Instructions guide the agent’s behavior. This is where you define the agent’s personality and approach.
3. Call the Model
resp = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "system", "content": instructions}] + input_list,
tools=tools,
tool_choice="auto"
)
Send the conversation + tools to the model. tool_choice="auto" lets the model decide when to use tools.
4. Check for Tool Calls
if not message.tool_calls:
return message.content # Agent is done
If there are no tool calls, the agent has finished its work.
5. Execute Tools
for tool_call in message.tool_calls:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
if needs_approval(name) and not approve(name, args):
tool_output = {"status": "denied_by_user"}
else:
tool_output = dispatch_tool(name, args)
For each tool call:
- Extract name and arguments
- Check if approval is needed
- Execute the tool
- Capture the result
6. Add Results to Conversation
input_list.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(tool_output)
})
Tool results go back into the conversation. The model will see them in the next iteration.
Complete Working Example
Let’s put it all together with a full runnable script:
Save this as agent.py and run it:
python agent.py
Try It: Experiment with MAX_ITERS
Exercise 1: Set MAX_ITERS=2 and see what happens.
The agent won’t have enough iterations to complete the task. You’ll see it stop mid-execution.
Why this matters: In production, you need safety limits. Without them, a buggy agent could loop forever and cost you money.
Exercise 2: Deny an approval and watch the agent recover.
When you deny write_file, the agent should see {"status": "denied_by_user"} and explain that it couldn’t complete the task.
Why this matters: Agents need to handle failures gracefully. They should explain what went wrong, not just crash.
Common Issues and Fixes
Issue 1: Agent loops forever
Symptom: Agent keeps calling the same tool repeatedly
Fix: Check your tool implementations. Make sure they return useful results. If a tool returns an error, the agent might retry indefinitely.
Issue 2: Agent doesn’t call tools
Symptom: Agent just talks about what it would do
Fix: Check your tool schemas. Make sure descriptions are clear and parameters are well-defined. The model needs to understand when to use each tool.
Issue 3: Tool calls fail with JSON errors
Symptom: json.loads() raises an exception
Fix: Wrap tool execution in try/except. Return {"error": "..."} instead of crashing.
Key Takeaways
You’ve built a complete agent loop! You now understand:
- ✅ Conversation state - How
input_listgrows with each iteration - ✅ Tool calling flow - Model proposes, code executes, results return
- ✅ Approval gates - How to add human-in-the-loop control
- ✅ Safety limits - Why
MAX_ITERSprevents infinite loops - ✅ Error handling - How to make agents resilient
What’s Next?
In the next page, we’ll add more controls: budgets, rate limits, and dry-run mode. These are essential for production agents.