Why AI agents repeat the same command: the dedup guard signed arguments the dispatcher then rewrote
- We measured our own agent on 2026-07-23, across 50 traces and 2,281 tool calls on SWE-bench tasks: 37.4% of non-edit tool calls were byte-identical repeats of an earlier call in the same task — 785 of 2,097.
- The loop already has a dedup guard. It misses these because it signs the raw model arguments, which the dispatcher then mutates before the call runs.
read_symbolhas the worst rate at 60%.grep_repois the biggest absolute source: 368 repeats out of 1,026 calls.- Only 41 calls in the whole run had trace-redacted arguments, so redaction cannot account for the effect.
Nearly everything written on this subject theorises about why coding agents loop: context windows, attention decay, reward shaping, the model being stubborn. We had no theory worth publishing, so we counted. On 2026-07-23 we walked 50 of our own traces covering 2,281 tool calls on SWE-bench tasks, asking one question of each dispatched call: had this exact call already been dispatched in this task? For the non-edit tools the answer was yes 785 times out of 2,097, or 37.4%. This is our agent's number, not the industry's.
What counts as a repeat
The definition is deliberately strict, because a loose one would have flattered us. An exact repeat is a dispatched call whose (name, args) pair is byte-identical to an earlier dispatched call in the same task — not similar, not the same file at a different line range. Anything the guard caught and cancelled never reached the dispatcher, so the figure counts escapes, not attempts.
The per-tool breakdown
| Tool | Calls | Exact repeats | Rate | What that rate tells you |
|---|---|---|---|---|
read_symbol | 402 | 243 | 60% | Worst rate on the board: the agent re-reads a definition it has already been handed. |
run_pytest | 121 | 47 | 39% | The same invocation with no edit in between. Cheapest to count, most expensive to suffer: repeats here burn the most wall-clock. |
grep_repo | 1026 | 368 | 36% | Biggest absolute source of waste: 368 of the 785 repeats. Search is cheap per call, which is why nobody notices it doubling. |
str_replace | 182 | 61 | 34% | Edit tool — exempt from the guard by design. Re-applying an identical patch is sometimes correct, so this row is context, not defect, and sits outside the headline. |
read_range | 547 | 127 | 23% | Lowest rate, because line windows vary between calls. What repeats here is usually the same window fetched twice after a failed edit. |
| Non-edit total | 2097 | 785 | 37.4% | Better than one non-edit call in three did no work the agent did not already have. |
The control that matters
Before believing a number this large, the first job is to try to kill it. The obvious inflator is redaction: if the trace writer masks arguments, two genuinely different calls collapse into the same string and the counter fires on a collision rather than a repeat. So we counted. Across all 2,281 calls, 41 had trace-redacted arguments. Even if every one were a false match it moves the headline by well under a point. The effect is not an artefact of how we record traces, and that is the part of this page we would defend hardest.
The distribution backs it up. Repeats are not smeared evenly, which is what a measurement bug looks like; they pile up on particular tasks. django__django-14672 dispatched 76 calls, 60 of them repeats, a 79% rate. pydata__xarray-6938 managed 87 repeats across 135 calls — runs where the agent spent most of its budget re-asking questions it had already answered.
Counting by target rather than exact arguments — the same tool pointed at the same path or pattern, however spelled — 72.3% of all calls revisit ground an earlier call already covered. Not all of that is waste, but it is the ceiling the exact-repeat figure sits inside.
The cause: two different values, one of them hashed
Here is the part we did not expect: the loop is not missing a dedup guard. It has one, and it was running for every call in this measurement.
The guard builds its signature from the arguments as the model emitted them. The dispatcher then rewrites those same arguments before the call executes: it injects a backup directory, resolves relative paths to absolute ones against the repository root, rewrites pytest node-ids into canonical form, and sets a working directory. Each step is reasonable alone, and together they are why the tools behave consistently however the model phrases a path.
They are also why the guard never fires. The model asks for tests/test_forms.py on one turn and ./tests/test_forms.py on another: two raw strings, two signatures, guard sees nothing. By the time the dispatcher has resolved both they are the same call, dispatched twice. The normalisation that makes the tools robust is applied after the check meant to use it.
Hence the one-line version, and the reason it generalises beyond our codebase: a guard that hashes a different value from the one that is dispatched is not a guard. It passes every unit test you write, because in a test you hand it normalised arguments yourself. It fails only in production, quietly, as a bill paid in tokens and minutes.
How to decide
Three questions will tell you in an afternoon whether your own loop has the same defect.
- Does your guard hash what the model produced, or what the dispatcher actually sends? Log both payloads for one task and diff them call by call. If they ever differ, every mutation between the two is a hole.
- Which of your tools mutate their arguments in transit? Path resolution, injected working directories, node-id rewriting, default-filling of omitted fields. Enumerate them rather than assume you would have noticed; each is a way for two identical calls to carry different signatures.
- Are your edit tools exempt, and did you decide that on purpose? Exempting them is defensible as a decision, not as an accident — and only if you report the rate over the non-exempt tools, so the exemption cannot flatter the headline.
Frequently asked questions
Is an agent repeating a command always a bug?
No, which is why the headline counts only non-edit tools. Re-running a test after an edit is correct, and re-applying an identical patch can be too, so str_replace sits outside the guard by design. What is not correct is dispatching a byte-identical read, grep or test run when nothing has changed since the last one.
Why does the model not simply remember what it already ran?
Sometimes it cannot see it: long runs summarise or drop older turns, so the result of call twelve may be out of view by call sixty. But that is the convenient half of the story. Our loop had a guard whose entire job was to catch this, and it was letting repeats through. Blaming the model's memory would have hidden a defect in our own dispatch path.
Where should a dedup guard actually sit?
At the point of dispatch, downstream of every argument mutation, hashing the exact payload the tool will receive. Run it any earlier and it signs a draft rather than the real call, and the normalisation applied afterwards silently defeats it.
Try Outlier free
Free Nano + Lite — local, private, no account. Pro is a one-time $249 and adds everything (all 6 model tiers incl. Plus 397B). Founders Lifetime is $249 once. Apple Silicon only.
Download for Mac