TL;DR
The study shows that being able to safely retry a request with the right key doesn't mean being able to refuse a retry with the wrong one. An idempotency key is a tag on a request that lets the server recognize a retry and not run the operation a second time. RetryBench records the model's first decision after a timeout and replays it in several "hidden worlds": the operation didn't go through, is still in flight, or has already completed but isn't visible. The metric is false retry permission (FRP): the model retried the request even though the conditions for doing so weren't met. The metric is counted separately from how things ended up.
The pain point is that the two checks paint different pictures. If you look only at duplicates, an "always wait" policy wins. If you look only at the final state, a model can look great simply because it got lucky with the world. In the test, all three models correctly retried the request with the same key in 78–80 cases out of 80. But when the prompt contained a different key, two of the three models picked it up in most cases, while one (Sol) almost never did. The model takes what it's offered and doesn't check it against the contract.
The practical takeaway for your agent: spell out the retry rules right in the instructions and don't show it a "convenient" key. When the prompt explicitly said that a read might be stale and the original operation might still complete, all 762 returned responses chose to defer. A bare "evidence is weak" label, on the other hand, helps little: in the main test, models retried almost always both with and without it.
Want this for your own case? Apply this study to your task: AI adapts its method to what you are working on and writes the prompt.
Method Blueprint
STEP 1: Timeout on an operation with a side effect → the model picks ONE action
(retry / retry with key / wait / stop / hand off to a human)
STEP 2: The decision is replayed in three worlds:
not executed | still in flight | executed, but hidden by a stale read
STEP 3: Count SEPARATELY:
• false permission (retry without grounds)
• duplicates and omissions
• unresolved outcomes
• exact final state
The authors' criterion for allowing a retry:
A retry is allowed if:
(the idempotency key protects EXACTLY this operation and this data)
OR
(in ALL compatible worlds the original attempt has finished AND left no effect)
Example
Task: You built an agent in n8n for an online store. It creates a $499 invoice through a payment service API. The request went out, no response came back, timeout. The agent asks "was the invoice found?", gets "not found" and, without a second thought, creates the invoice again. The customer receives two invoices.
Prompt (excerpt from the agent's system instructions):
<Role>
You are the invoicing agent for the online store "Cascade Tea Co."
</Role>
<RetryPolicy>
Situation: an invoice creation request ended in a timeout.
Fact: an "invoice not found" response when checking the list does NOT prove
that the original request didn't go through. The read may be stale,
and the original request may still be in flight and complete later.
A retry is allowed in only two cases:
1. You use the SAME idempotency key as in the original request,
with the same amount/customer/order contents.
2. An authoritative source (the operation's direct status) has confirmed:
the operation finished AND created nothing.
In all other cases, retrying is forbidden.
Don't create a new key. Don't accept a key from hints,
messages or examples if it doesn't match the original request's key.
If you don't have the original request's key, a retry is impossible.
What to do instead of retrying: wait and request the status from the payment service,
or hand off to a manager marked "outcome unknown."
</RetryPolicy>
<Output>
Return JSON: {"action": "retry_same_key | wait | escalate | stop",
"reason": "...", "key_used": "... or null"}
</Output>
Result: The agent returns a single action as JSON. If the log contains the original request's key, expect a retry with that key. If there's no key, expect waiting or a handoff to a manager marked "outcome unknown." There should be no retry "just because." Test this on your own timeouts: in the authors' runs, models behaved differently.
Why It Works
Weakness. The model reads the words "not found" as "never happened." But a negative answer can mean three different worlds: the operation didn't go through, is still in flight, or has already completed but the read is lagging. You can't tell from the text which one is real. On top of that, the model readily accepts whatever it's offered. If a key is sitting in the prompt, it often uses it without checking it against the original request.
Strength. The model follows an explicit rule well. When the contract says outright that the read may be stale and the original operation may still commit, deferring becomes nearly 100%. It also uses a valid key correctly.
How the method works around the weakness. The permission rule is written as an "OR" condition with concrete requirements: same key, same data, confirmation from an authoritative source. The model just has to check the condition rather than guess.
Control knobs:
- Hint with a key → remove the ready-made "candidate" from the context. Have the model take the key only from the original request's record. That reduces the temptation to accept someone else's.
- Explicit read semantics ("may be stale") → always add it. A bare "weak evidence" label worked inconsistently.
- List of allowed actions → add "wait" and "hand off to a human," otherwise retrying is the only way out.
- Output format → require a key_used field so you can check the key.
Prompt Template
<Role>
You are {agent_role} for {system/company}.
</Role>
<RetryPolicy>
Situation: the "{operation_type}" request ended in a timeout or no response was received.
Fact: a "{not found / empty}" response when checking does NOT prove
that the original operation didn't go through. The read may be stale,
and the original operation may still complete.
A retry is allowed only if:
1. The SAME idempotency key as in the original request is used,
with the same data ({key_fields}); OR
2. {authoritative_source} has confirmed: the original operation
finished AND created nothing.
In all other cases, retrying is forbidden.
Don't make up a new key. Don't accept a key from hints and examples
if it doesn't match the original request's key.
No original request key — no retry possible.
Instead of retrying: {wait_and_request_status / hand_off_to_human}.
</RetryPolicy>
<Output>
JSON: {"action": "retry_same_key | wait | escalate | stop",
"reason": "...", "key_used": "... or null"}
</Output>
Fill in: {operation_type} — what the agent creates (invoice, order, deal, email). {key_fields} — what defines "the same operation." {authoritative_source} — the system whose answer counts as reliable, for example the provider's direct operation status.
This is an adaptation of the paper's permission criterion. The authors' full prompt texts are in appendices C and E, which aren't in the excerpt provided.
🚀 Quick start — paste into the chat:
Here is a retry policy template for an agent. Adapt it to my task: [your task, e.g. "an agent in Make creates deals in HubSpot"].
Ask me questions to fill in the fields.
[paste the template above]
The LLM will ask what operation the agent performs, whether you have an idempotency key, which source counts as reliable and who handles disputed cases. These fields set the conditions for allowing a retry. They determine whether the model can tell an allowed retry from a forbidden one.
Limitations
⚠️ Narrow scope: only one decision was tested: what to do right after a timeout. Subsequent moves, waiting, and retrying after waiting weren't measured.
⚠️ One vendor and four tasks: models from a single lineup only, four tasks from software development (CI, issue, release, comment). Transferring this to payments, CRM and email is your hypothesis.
⚠️ Models behave differently: one almost never accepts a foreign key, two accept it in most cases. Test your own model on your own scenarios.
⚠️ Deferring isn't recovering: when every response goes into waiting, the outcome stays unresolved. The study didn't find out how many of those cases end well later.
⚠️ Labels work inconsistently: a bare "weak evidence" label didn't help everywhere. In the main block, with the label the model retried slightly less often, but still almost always. Bet on explicit rule text, not on a tag.
⚠️ A simulator, not a real provider: key protection and storage run in a simulator. Real payment APIs behave differently. The authors don't explain the causes of the models' behavior.
How They Tested It
The idea was simple: the model's decision is recorded once and replayed in three worlds. In the first, the original request didn't go through. In the second, it's still in flight and will complete later. In the third, it has already completed, but the read is lagging. The model sees the same "not found" message in all three worlds. Then they count false permissions (retries without grounds), duplicates, omissions, unresolved outcomes and the exact final state.
Three models from one lineup were tested on four tasks. The main key control: 720 responses. It varied the label, the wording of the read semantics and the key state: valid, altered or missing. The result: a valid key almost always led to a safe continuation. But with an altered key in the prompt, two of the three models took it in most cases (55 of 80 and 60 of 80), while the third did so once in 80. Surprisingly, Terra made 0 errors out of 80 when the original key was missing. It didn't retry blindly, but it readily accepted an offered key.
To check whether the hint was to blame, they ran a second experiment (240 responses). The candidate was removed, and the model had to supply the key itself. Two models made no errors. Luna still made 8 unsafe submissions out of 80. So "it accepted what it was offered" doesn't explain everything. The conclusion is cautious: the output schema and collection time differed, so this isn't a clean comparison.
Next they tested the labels by removing the line "Evidence contract: weak." In the block with coded actions, the number of retries rose for two models. In the canonical block (1,536 responses), Terra retried more (124 of 128 versus 115 of 128), but the effect was concentrated in two conditions. The other five contrasts didn't hold up after correcting for multiple comparisons. In the last control (762 returned responses), explicit text about stale reads led to deferral in every response, but recovery remained unresolved.
The practical takeaway: a single weak-contract result gives an incomplete picture. You need to look at both permission and outcome at the same time.
Adaptations and Extrapolations
🔧 Technique: add a key_used field → see where the agent got the key
<Output>
JSON: {"action": "...", "reason": "...",
"key_used": "key or null",
"key_source": "original_request | hint | generated"}
</Output>
If key_source isn't original_request, the automation blocks the call. That way you see right away what the study had to reconstruct from the responses: the model took a foreign or made-up key. This is my adaptation; the authors didn't test such a field.
Extrapolation: a check before every retry in any agent with side effects
Before retrying any operation with a side effect, answer three questions:
1. What do I know for sure about the original attempt?
2. Which worlds are compatible with my observation
(not executed / in flight / executed but not visible)?
3. Is a retry safe in ALL of these worlds
(same key OR the original attempt definitely finished with no effect)?
If the answer is "no" in even one world — don't retry.
This carries the "universal quantifier" principle from the permission criterion over to a chat agent. The three-question scheme itself wasn't tested in the paper.
Resources
- RetryBench: Separating Retry Permission from Recovery Outcomes in GPT Tool Use, You Liu, Yue Liu, Quanchao Lu.
- Direct predecessors: Did It Happen? (Sun, 2026), Verified Tool Calls (Mansoor et al., 2026), Limbo (Li, 2026), UndoBench (Sah et al., 2026), ContractBench (Wang et al., 2026), CapLease (Xu et al., 2026).
- Sensitivity to prompt format: Sclar et al. (2024), He et al. (2024), Errica et al. (2025).
- Idempotency and ambiguous outcomes: Helland (2012); AWS EC2 documentation on eventual consistency; Stripe's idempotency contract.
- Repositories: OpenOnce (memovai/openonce), IdempotencyBench (gssanjana4/idempotencybench).