One reservation, two records, one missing reply
An AI agent must reserve a spare part for a machine. It records the request as handled, then calls the warehouse. The process stops between those steps. After restart it sees “done” and does not retry: no part was reserved. Reverse the order: the warehouse reserves it, but the reply is lost before the agent updates its record. Retrying can now reserve two parts. The same missing message can lead to opposite mistakes.
Abstract. How can we distinguish recorded intent, remote effect and observed acknowledgement? We build an executable model with two independent SQLite databases, three protocols and five interruption boundaries. An outbox protects intent but does not alone prevent duplicates: a receiver contract is also needed. Results concern fifteen deterministic cases, not industrial reliability measurements. No real orders, reservations or external service calls are performed.
The boundary a convincing sentence cannot remove
A local transaction makes a set of changes within one system indivisible: they commit together or do not take effect. It does not automatically include a write in another service. A language model may choose the right part and produce perfect JSON without extending that transaction boundary. This is an execution-protocol problem, not a matter of how convincingly the agent says “operation complete”.
Let I denote durable intent and N the number of effects in the simulated warehouse. For an accepted request that can complete, the target is N=1. N=0 means a missing effect; N=2 a duplicate. These are dimensionless counts, not probabilities. Local “done” describes what the protocol recorded; until its production rule is defined, it proves none of those values by itself.
An outbox preserves work to do, not proof of execution
First, record the application decision and a message awaiting delivery in the same local transaction. This durable queue is the outbox. A forwarding process, or relay, reads committed intents and sends them to the receiver. If it stops after local commit, the pending row survives restart. “Do we remember what to attempt?” now has an answer independent of the agent’s volatile memory.
This does not make the two databases atomic together. After the warehouse applies an effect, the relay can stop before recording delivery: the row remains pending and will be sent again. Our teaching implementation increments the remote counter twice if every request is treated as new. The outbox closes a specific gap between decision and message while leaving the lost-reply ambiguity unresolved.
Identity belongs to intent, not to an attempt
Assign the reservation a stable key, tenantA:reserve42. Every attempt for that intent uses it. The receiver retains a receipt associating key, parameters and result. If a receipt exists with matching parameters, it returns the earlier result without a second effect. Different parameters must cause rejection of ambiguous reuse: reserving two parts is not the same command as reserving one.
The last line is crucial. Writing the receipt and applying the effect in separate transactions recreates the original defect at the receiver. Reversing their order leaves an interval with an effect but no receipt. Our model commits both together. A concurrent service also needs appropriate isolation and uniqueness handling: two workers must not both observe “key absent” and apply the effect.
Fifteen cases: what we actually interrupt
The Python package creates two fresh SQLite files per case: one represents the orchestrator, the other the warehouse. Files remain in runs so state can be inspected. The warehouse holds only a counter, not real stock. An exception interrupts control at a chosen boundary; recovery uses new connections. We do not power off the computer or measure storage resistance to power loss.
Boundaries are: 0 before local recording; 1 after it but before sending; 2 after the remote effect, with the reply unobserved; 3 after the reply but before final local completion; 4 after completion. At boundary 0 we assume the caller resubmits the same intent. Without that assumption, an outbox cannot remember an unrecorded request. Elsewhere the relay resumes pending work; a done row is not processed again.
| Protocol / boundary | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| mark-first | 1 | 0 | 1 | 1 | 1 |
| outbox-no-dedup | 1 | 1 | 2 | 2 | 1 |
| outbox-dedup | 1 | 1 | 1 | 1 | 1 |
Each cell counts effects after recovery. Mark-first writes done before sending, so boundary 1 produces zero: the local record suppresses the necessary attempt. Outbox without deduplication produces two effects at boundaries 2 and 3 because the receiver has acted before the relay records it. Outbox plus an atomic receipt retains one effect in all five modeled cases.

Do not read the chart as a success percentage: we selected five boundaries rather than sampling faults from a representative distribution. This experiment finds counterexamples and checks a property within its model. Assertions verify all three result sequences. The final snippet calls the same simulator and prints the table; the package also plots the bars, so values are not taken from a hand-drawn illustration.
The property and the assumptions supporting it
For each key k, the desired invariant is N(k)≤1: no second effect for the same intent. The receiver transaction supports it: either neither effect nor receipt exists, or both do, and a retained receipt prevents another increment. Obtaining N(k)≥1 also requires retained intent, continuing attempts, a receiver that becomes available and a valid request. The first is a protocol safety property; the second concerns progress. A system can avoid duplicates and remain stuck forever.
“Exactly once” is useful only with a stated boundary: one logical effect per key under specified conditions. It does not mean one network packet, one attempt or no business mistakes. Two distinct intents with different keys can reserve the same part twice, correctly for the protocol but wrongly for the user. Establish the accepted action’s identity before retrying; asking the model to regenerate a key on every recovery defeats the protection.
Where the teaching model stops being sufficient
Receipts have a lifetime. If deleted before an old attempt arrives, that message can look new. Retention must agree with delays, queues and retry policy; alternatively, a closed epoch can reject expired requests. Our experiment does not implement that policy. Tenant identity also matters: the same key from two customers must not conflate independent intents.
A real system must handle concurrent workers, expired leases, ordering updates to one object, partial responses and permanently rejected requests. Remote acceptance may only start asynchronous work: accepted and completed are different states. Without stable identity and outcome lookup at the receiver, a timeout may require reconciliation rather than a success claim. No prompt alone supplies missing execution information.
Conclusion: what the agent can actually claim
A local record establishes retained intent; a verified remote receipt establishes the outcome promised by the receiver contract. In our model, outbox, stable retry identity and atomic deduplication remove both the missing effect at boundary 1 and duplicates at 2 and 3. That evidence path justifies “done”, not the word’s appearance in a log. This teaching experiment does not establish an available EL-AI feature or operational result.
Primary sources explain the outbox pattern and idempotent API contracts; we also read implementation examples and sections on late requests and changed parameters. The simulator, counterexamples and table are our executed teaching analysis, not the authors’ benchmarks. Each case handles one intent and a constant number of transitions; control work scales linearly with case count, while database cost, network latency and large queues are not estimated. Fault injection in an actual service, including concurrency and receipt expiry, is a proposed next experiment, not one performed here.
Sources and reproducibility
AWS Prescriptive Guidance — Transactional outbox pattern.
Malcolm Featonby — Making retries safe with idempotent APIs, Amazon Builders’ Library.
# Run beside experiment.py; all effects are local simulated counters.
from experiment import run
for protocol in ("mark-first", "outbox-no-dedup", "outbox-dedup"):
print(protocol, [run(protocol, cut)["effects"] for cut in range(5)])
Code, data, and instructions · JSON. Educational calculations executed with Python 3.14.0; figures with Matplotlib 3.11.2. AI-assisted analysis, without claiming peer review or human review. Original illustrative ImageGen cover: it does not document EL-AI people, premises, or installations. Sources accessed 3 October 2026.

