All perspectives
Leadership & Organizations →AgentsEconomicsGovernance

100 operations. 20 lost responses. What did the retries create?

A reproducible fault-injection experiment: both callers acknowledged 100 operations, but ordinary retries left 120 writes. Stable operation keys kept the ledger at 100.

The tool succeeded. The caller never got the response. Should it try again?

That sounds like an error-handling detail. It becomes a product problem when trying again creates a second ticket, a second booking, or a second charge.

I extended the duplicate-action recipe into a small controlled experiment: 100 intended ticket creations, with 20 first acknowledgements deliberately dropped after the write committed. Each caller could retry once. One sent no key. The other reused a stable operation key that the fixture service honored.

Both callers received 100 successful acknowledgements. One service ledger contained 120 tickets; the other contained 100. A success counter at the caller would have missed the difference.

These are observations from executed, deterministic, in-memory code on October 18, 2026. There is no model or real network in the test. The injected 20% loss rate is a chosen test condition, not an estimate of production failures or AI-agent performance.

The question and the protocol

The question is narrow: after a committed write loses its acknowledgement, does a retry preserve one effect for the intended operation?

Each policy starts from a fresh, empty ticket service. There are 100 distinct operation IDs and the same payload for each matching operation across policies. First responses are lost on operations 001, 006, 011, and every fifth operation through 096. That is exactly 20 lost responses in each run. The second response is always delivered, and each operation is limited to two attempts.

The baseline sends no operation key. The repaired caller sends one unique key per intended operation and reuses it on retry. The repaired fixture service recognizes that key, rejects conflicting payloads, and returns its existing ticket. Both policies otherwise use the same code, input and failure schedule.

The assessment reads the service ledger, counts records by intended operation, and compares that with caller acknowledgements. It does not infer success from the last returned ticket ID.

Run the experiment

The table starts with the recorded execution. Replay the protocol locally, or try the no-loss and all-first-responses-lost boundary cases. Changing a setting does not change the table until you press Run. Browser replays are labeled separately from the recorded observations.

Executed fixture / Revision 1

One intention. How many writes?

Recorded execution · October 18, 2026 · 100 operations · 20 lost responses.

Both policies start with an empty service. The same first responses are dropped after their writes commit. No model, real network, or production service is involved.

Retry experiment results. On narrow screens, swipe or focus the comparison and use the arrow keys to scroll.

Service-ledger results for 100 intended operations
MeasureOrdinary retryStable key + server deduplication
Operations acknowledged to the caller100100
Tool attempts120120
Retries2020
Responses dropped after commit2020
Records in the service ledger120100
Extra writes for the same intention200
Operations with exactly one write80100

Both callers acknowledged 100 operations. The service recorded 120 writes without keys and 100 with stable keys. Exactly one write existed for 80 and 100 operations respectively.

Inspect an operation and its two attempts

Operation 001 loses its first acknowledgement after the service commits the ticket.

Ordinary retry

No operation key is sent.

  1. Attempt 1: ticket-001. New record committed. Response lost after commit.
  2. Attempt 2: ticket-002. New record committed. Response received.

Stable key + server deduplication

Both attempts use ticket-create:operation-001.

  1. Attempt 1: ticket-001. New record committed. Response lost after commit.
  2. Attempt 2: ticket-001. Existing record returned. Response received.

This service stores keys in memory for one sequential run. Production idempotency needs durable, atomic server support, appropriate key scope, conflict checks, and a retention policy.

What the execution showed

Ordinary retries produced 120 committed writes. Eighty intended operations had exactly one ticket; 20 had two. Stable keys with server deduplication produced 100 writes, with exactly one for every intended operation.

Both variants made 120 attempts, including 20 retries. Each caller eventually received 100 successful acknowledgements. The repair changed the effects of repeated requests, not how many attempts were needed under this schedule.

This is why I would put the invariant in the service’s state: one correctly created record per intended action. A tool-success metric can be useful, but it cannot substitute for checking the outcome that matters to the user.

Operation 001 makes the mechanism visible. Without a key, its first attempt commits ticket-001 and loses the response. The retry commits ticket-002. With a stable key, the retry returns ticket-001 without making a new record. You can inspect that trace above and every operation in the recorded JSON.

The engineering decision behind the repair

A key is an agreement with the service. Sending a header or adding an instruction to an agent does not make an unsupported API idempotent. The service must recognize an operation’s identity and coordinate that identity with its stored effect.

The AWS Builders’ Library explains caller-provided identifiers, late requests and the distinction between matching requests and matching intent. These are design considerations for the boundary around the model, too.

Stripe’s contract illustrates why the exact provider behavior matters: request reuse, payload conflicts, retention and API-version semantics are part of the agreement. This fixture is not a Stripe implementation. Check the actual endpoint you are integrating.

The negative control is simple: generate a new key on each attempt, and the fixture creates two records again. Distinct business intentions also need distinct keys. Reusing one key for unrelated requests would collapse work that should remain separate.

What I would ask as a leader

Before accepting a dashboard that says a workflow completed successfully, I would ask what its success count actually measures. A received acknowledgement, a stored record, and a correct business outcome answer different questions.

For a system that can act, I would want a named owner for the completion invariant, a service contract for safe retries, and a way to reconcile uncertain outcomes. Review and remediation belong in the economics of the task. Twenty extra records may create work even when every tool call is inexpensive.

This test does not assign a dollar value to that work or demonstrate a staffing reduction. It shows why I would count the work left behind before claiming a saving. The task-cost calculator lets you explore explicitly illustrative cost assumptions separately.

Reproduce it from a clean folder

Download the executable kit. It contains the TypeScript source, executable JavaScript, assertions, recorded observations, manifest, README and MIT license. Use Node 24; no npm installation, account or API key is needed.

tar -xzf retry-experiment-v1.tar.gz
node run.mjs > my-results.json

The runner checks the counts against the service-ledger assessment and exits nonzero if an assertion fails. Its default output should match the included observations.json byte for byte. The build also extracts the distributed kit into a clean folder and checks this reproduction.

To inspect individual files, open the executable source, TypeScript source, recorded JSON, or manifest. Source and result hashes identify the exact files; they are not independent validation of the conclusions.

What this does not establish

This is a sequential, single-process fixture. The key lookup and commit have no asynchronous gap. Keys and records disappear when the run ends. There is no real database, distributed transaction, concurrent worker, restart, key-expiry behavior or network timeout.

The test therefore does not establish production reliability, an exactly-once delivery guarantee, model quality, latency, cost savings, or a real-world loss frequency. The deterministic schedule is designed to expose one mechanism. Passing it is a starting regression check.

A production adaptation should inject the failure around a disposable service, then assess that service’s authoritative records. Add atomic durable storage, appropriate key scope and retention, payload conflicts, concurrent requests, restart and late-arrival tests. When a provider cannot deduplicate an uncertain action, a bounded reconciliation process may be the right next step.

For a guided introduction, read retrying without duplicate actions. For the original executable examples, explore Agent Failure Recipes. The repaired state is the beginning of the engineering discussion, not a reason to stop asking what the service actually guarantees.

Follow the evidence

Sources & further reading

  1. Executed protocol, source hashes, runtime and observations: revision 1 ↗
  2. Complete recorded attempts and authoritative service ledgers ↗
  3. Agent Failure Recipes: original ticket fixture at reviewed commit ac87eb7 ↗
  4. AWS Builders’ Library: Making retries safe with idempotent APIs ↗
  5. Stripe API: idempotent requests and provider-specific semantics ↗

Sources checked Oct 18, 2026. Research and drafting assisted by AI.

Suggest a correction