Your AI Agent Has No Colleagues A developer argues that AI agents fail at tool-call retries because error messages like "422 validation_error" carry no signal about whether a retry is worthwhile, leaving models to guess based on the retry-heavy code they were trained on. The post proposes a three-part failure trace — a normalized stable identity for each failure, a recorded outcome rather than an intention, and a denominator of total calls — so agents can learn which failures are transient and which require a schema fix. It also warns that intermittent retry success acts as a variable-ratio reinforcement schedule, making the behavior hard to extinguish. "The coordination of the builders is not direct. It is the work already done that directs and triggers the work that follows." — Pierre-Paul Grassé, describing termites, 1959 You have seen this. Every agent framework does it : create issue x 422 validation error create issue retry x 422 validation error create issue retry, arguments tweaked slightly x 422 validation error Three attempts. Three identical failures. Then it apologises to you, which somehow makes it worse. The instinct is to blame the model. But look at what it had to work with: 422 validation error Tell me, from that string, whether retrying is worth it. Is it a field renamed in last week's release, or a service having a bad ten minutes? Same six characters either way. In one case retrying is exactly right and works in thirty seconds. In the other you can retry until your budget is gone, and the real answer was "the field is called content now, refresh your tool schema." The model has to guess. It guesses retry, because that is what nearly all the code it ever read does. The first instinct, once you notice this, is to write a rule. When a tool call fails, do not retry immediately. Consider whether the failure is transient before trying again. It sounds reasonable. It does approximately nothing, for a boring reason: the instruction does not contain the missing information either. You have told the agent to consider whether the failure is transient. It still has no way to find out. You have asked it to make the same guess, more thoughtfully. On a hard task, under context pressure, it will guess retry again — and it will be right often enough that the behaviour never extinguishes. That last part is the trap. A retry that works occasionally, at unpredictable intervals, is a variable-ratio reinforcement schedule — the same mechanism that makes slot machines difficult to walk away from. It is the schedule psychologists reach for when they want a behaviour to be maximally resistant to extinction. Your agent is on it. So are you, at 1am, hammering the same test. The temptation is to log the error and call it a trail. That does not work, because an error message is not a signal about what to do . A useful trace needs three parts: ┌──────────────────────────────────────────────────────────────────┐ │ THE ANATOMY OF A USEFUL FAILURE TRACE │ ├──────────────────────────────────────────────────────────────────┤ │ 1. AN IDENTITY │ │ A stable id for "this exact failure", so two agents can │ │ tell they hit the same thing. Not the raw string: that one │ │ contains a request id and a timestamp and will never match │ │ anything again. │ ├──────────────────────────────────────────────────────────────────┤ │ 2. AN OUTCOME, NOT AN INTENTION │ │ What the next agent tried, and whether it worked. "I │ │ refreshed the schema" is worthless. "I refreshed the schema │ │ and the call then succeeded" is the whole point. │ ├──────────────────────────────────────────────────────────────────┤ │ 3. A DENOMINATOR │ │ Successes too, or the failure rate is meaningless. 100 │ │ failures out of 200 calls is an outage. 100 out of a │ │ million is a Tuesday. │ └──────────────────────────────────────────────────────────────────┘ Part 1 is fiddly and worth spelling out. These two are the same bug: Repository 8823 rejected field body at 2026-09-11T14:02:11Z Repository 41902 rejected field body at 2026-09-12T09:41:55Z Compare them raw and you have two unrelated incidents forever. So you normalise first — replace the parts that vary, keep the parts that mean something — and hash what is left together with the service and operation: text = URL RE.sub "