An agent keeps calling tools: how to diagnose and bound the loop
Diagnose agent loops through tool calls and results, apply step limits and duplicate signatures, and distinguish stopping from completion with a locally tested simulator.
Start troubleshooting here
If an agent repeats tool calls, first check whether each result was returned under the matching call ID, then compare arguments and external state. Repeated searches against unchanged state can be stopped by duplicate-signature detection; the task also needs step and time limits. Reaching a limit means execution stopped, not that the task succeeded. The Python standard-library example below was run locally against fixed action lists to verify duplicate blocking and a step limit. It uses no real model or external writes.
Confirm that tool results reached the next turn
Repeated searches, retrying unchanged arguments after an error, or continuing to generate an existing task file are symptoms worth tracing. Take one failing transcript and match tool names and arguments to results in their actual order, then inspect the messages received by the next turn. Do not infer execution solely from the agent’s explanation.
For missing results, inspect call-ID matching, swallowed exceptions and result truncation. Tool failures should return clear error types and actionable correction information. If the next turn receives only “failed” without knowing which argument was invalid, an unchanged retry is unlikely to provide new information.
Compare call signatures alongside external state
Serialize tool names and arguments in a stable key order to detect requests that differ only in field ordering. Repeating the same search over unchanged material usually adds no evidence. Stop that branch or require a verifiable change to the query conditions, rather than accepting a different verbal explanation of the same request.
Polling requires separate handling because server state can change over time, making identical queries useful. Set a polling interval and deadline; require cursors to advance for pagination. Signature detection needs business state, not a blanket ban on repeated parameters. A network timeout also does not establish that a tool never executed.
A local simulator verifies duplicates and the step limit
The code below only simulates execution by appending actions to a list. It does not call a model API or search service. In the first test, the second search merely reorders dictionary keys; its normalized signature matches, so the execution count stays at 1. A second test supplies ten different queries with a limit of three; only three execute and the stop reason is max_steps.
The code was run locally using the Python standard library, and both assertions passed. Its raw output reports duplicate with executed equal to 1 for repeat, and max_steps with executed equal to 3 for budget. This verifies only the simulator’s stop checks. The action lists are supplied directly; it demonstrates neither model planning nor persistent production deduplication.
import json
def simulate(actions, max_steps=3):
seen = set()
executed = []
for action in actions:
if len(executed) >= max_steps:
return {"stop": "max_steps", "executed": len(executed)}
signature = json.dumps(action, sort_keys=True)
if signature in seen:
return {"stop": "duplicate", "executed": len(executed)}
seen.add(signature)
# Simulated tool execution only: no network or external writes.
executed.append(action)
return {"stop": "plan_exhausted", "executed": len(executed)}
repeated = [
{"tool": "search", "args": {"q": "policy", "limit": 1}},
{"args": {"limit": 1, "q": "policy"}, "tool": "search"},
]
budget = [{"tool": "search", "args": {"q": str(i)}} for i in range(10)]
assert simulate(repeated) == {"stop": "duplicate", "executed": 1}
assert simulate(budget, max_steps=3) == {"stop": "max_steps", "executed": 3}
print("repeat:", json.dumps(simulate(repeated)))
print("budget:", json.dumps(simulate(budget, max_steps=3)))
Check the target outcome after execution stops
Business success needs its own check. For a report with required fields, open the actual file and verify its contents and sources before marking completion. A tool success response, an exhausted action list or a step limit is not a substitute. Preserve explicit reasons such as completed, timed out or duplicate blocked, together with inspectable partial results.
Writes also need server-side idempotency based on a business key, especially when execution succeeded but its response was lost. The simulator’s in-memory set disappears on process exit and handles no concurrency, so it cannot ensure that real emails or orders occur only once. Check existing records before retrying a real write instead of treating a transport failure as business failure.
Retest both valid completion and required stops
After fixing result feedback, rerun the original looping case and confirm that the agent uses the previous result or stops for an explicit reason. Add a valid polling case to ensure changed state is not blocked incorrectly. Check the step limit before execution so another tool call cannot begin after the limit. A hanging individual tool also needs a separate timeout.
This method applies to agents whose orchestration loop or call records are accessible. Chat-only products may not expose the settings; retain the times and results of repeated actions for the maintainer. This is an implementation and verification method, not a measurement of any model’s looping rate or a promise that a higher step limit improves completion.
Checked sources
These sources support the concepts and methods described here. Examples and test scope are labeled separately; check the version of the tools you use.