Detect an Agent Stuck in a Loop
An agent that calls the same tool with the same arguments turn after turn burns every turn max_turns allows and produces nothing. Detect the loop and halt it.
Implement two functions.
is_stuck(llm, window=3): True when the last window tool calls the model made are identical: same tool, same arguments, in a row. Read them from Trace.of(llm).calls; a turn with no tool call breaks the streak. Fewer than window calls so far is not stuck.
run_guarded(question, model, tools, max_turns=8, window=3): the standard tool loop, but after every turn check is_stuck and halt if it fires. Return {"answer": ..., "turns": int, "stopped_early": bool}; answer is the model's text when it finished normally and None when the guard stopped it.
The scripted model in the tests always asks for the same search: without the guard it burns all eight turns, with it the run ends at window. A retry after a transient error is legitimate, which is why window is a parameter.