Technical
Tool-Use Patterns: Closing the Loop on Autonomous Agents
Everyone who builds an agent writes the same loop once: call the model, execute the tool it picks, feed the result back, repeat. The first version always works in a demo. The second version always gets stuck in a loop. Here's what separates production-ready tool use from endless retry loops.
Three Stopping Conditions
My agents stop for exactly three reasons:
- The model returns text without a tool call (task done)
- A hard step budget is exhausted (prevents runaway cost)
- The same tool call repeats with the same arguments (prevents loops)
The third condition is the one everyone forgets. Without it, a confused model will retry the same failing call forever.
The Loop
def run_agent(prompt, tools, max_steps=20):
history = [{"role": "user", "content": prompt}]
seen = set()
for _ in range(max_steps):
resp = model.call(history, tools=tools)
if not resp.tool_calls:
return resp.text
for call in resp.tool_calls:
key = (call.name, json.dumps(call.args, sort_keys=True))
if key in seen:
return f"stopped: repeat of {call.name}"
seen.add(key)
result = tools[call.name](**call.args)
history.append({"role": "tool", "content": result})
return "stopped: step budget"What This Buys You
With these three conditions, an agent that goes off the rails fails fast and visibly instead of burning credits silently. You can ship it to production, monitor the stop reason, and debug from there.
See the Anthropic tool-use docs for more loop patterns.
RELATED READING
The Consulting Shift I Am Making In Year Two
After a year of writing and building, my consulting practice is changing shape. Shorter engagements. Sharper outcomes.
ReadThe Frontend Shift: Shipping Less JavaScript In Year Two
A year ago I reached for Next.js for everything. This year I often reach for nothing.
ReadThe Serverless Lesson I Would Write On A Sticky Note
After a year of shipping serverless projects, one rule explains most of the wins and all of the losses.
Read