The write that knows it already ran
An agent sends a write. Nothing comes back. Five seconds, thirty, a socket closes.
Four things could have happened. The call never arrived. It arrived and failed. It arrived, committed, and the reply was lost on the way home. Or it is still running and will commit in a moment. From where the agent sits, all four look identical: no reply.
A person handles this by going to look. An agent handles it the way it handles everything else that returns nothing, which is to try again. That instinct is correct almost everywhere in software, and it is how a dropped reply once wrote a record twice on our own platform.
Why the second attempt is the dangerous one
Not every repeat is destructive. Set a field to a value twice and the second call changes nothing. This is why declarative systems are pleasant: apply the same Kubernetes manifest twice and the cluster shrugs.
Line-level edits do not have that property. A patch that says replace lines 40 through 44 means something different once lines 40 through 44 have already been replaced, and a patch that inserts before line 40 will happily insert again. The second application corrupts the file.
We already guard part of this. A patch can carry anchorText, a short string it expects to find in the line it is about to replace, and the server refuses when the anchor no longer matches. That catches the common case. Be precise about the limits: it is a heuristic on one line, it cannot see the rest of the file, it is optional, and it does not run at all on an insertion. The worked example above is an insertion.
So the anchor answers has this line changed. It does not answer the question the agent is actually stuck on, which is did my write land.
Give the write a name
OBTO's artifact write tools accept an idempotencyKey: a string of 8 to 128 characters that the caller generates, naming one logical write. A UUID is the obvious choice. The rule is one fresh key per new edit, never recycled across different ones.
With a key present, the write becomes a lookup before it becomes a write:
- No receipt on file. The artifact write executes, then records a receipt under that key for 24 hours.
- A receipt on file. Return it with
replayed: true, the tool that originally ran, and when it completed. The write does not execute a second time. - The store cannot be read. Refuse.
The third line is where the argument was.
Fail-closed when the lookup fails
When a key is supplied and the receipt lookup does not come back inside its five-second bound, the call returns this and stops:
{
"ok": false,
"error": "idempotency_lookup_failed",
"message": "An idempotencyKey was supplied but the receipt store
could not be read (5s bound). Refusing to execute blind:
if an earlier attempt with this key committed,
re-executing could double-apply.
NOTHING WAS WRITTEN by this call.",
"hint": "Retry with the SAME key. If this persists, re-read
the artifact and compare contentHash before deciding
to write without a key."
}The verb is refuses, in the strict sense we hold ourselves to elsewhere: the server declines, the write is not applied, and once a key is present there is no path around it. The opt-in is the key itself. Send no key and you get the old behaviour, no receipt and no protection, which is the honest shape of the trade.
Note what the hint concedes. The server tells you that dropping the key and writing anyway is available to you, once you have gone and looked at the artifact yourself. It declines to make that call on your behalf while it cannot see.
The reasoning is short. The key exists to stop a duplicate. The moment the server cannot tell a first attempt from a second is precisely the moment a duplicate is on the table, and executing anyway would abandon the guarantee at the only time it was load-bearing.
Failing open here is the feature switching itself off in the one condition it was built for.
A demo never reaches this branch, because a demo is the happy path. An incident reaches it, eighteen months later.
The half we do not get to call a guarantee
Storing the receipt happens after the write has committed, and it is best-effort. A committed write is never failed because its bookkeeping failed. That is the right call, and it leaves a real gap: the change is live and unnamed.
The response says so, rather than a footnote:
"idempotency": {
"key": "…",
"stored": false,
"note": "The WRITE COMMITTED but its receipt could not be stored,
so a retry with this key would re-execute instead of
replaying. Do not retry blindly — re-read and compare
contentHash first (anchorText still guards a patch)."
}We report that. We do not guarantee it, and the response says which one you are getting. A system that fails closed before the write and tells you the truth after it is one you can reason about. A system that drops the receipt and returns a cheerful ok: true gives you nothing to reason about at all.
The 24-hour retention is enforced in code when a receipt is read, not by the database expiry index, which only deletes rows nobody will look at. Otherwise a silent infrastructure failure would quietly widen the window in which a stale receipt replays as fresh.
What to ask, of us and of anyone else
Four questions, all quick to ask.
- Is there a per-write idempotency key, or only job-level retry? A re-runnable job is a coarser promise about a bigger unit.
- When the deduplication store is unavailable, does the write execute or refuse? Failing open is the common default, and nothing in normal operation reveals which one you have.
- How long is a receipt kept, and what enforces the expiry? If only a TTL index enforces it, an index that failed to build is a correctness bug wearing a maintenance costume.
- Does a replayed receipt describe the state then, or now? Ours describes the state right after the original write, and says so.
The difference from a pipeline here is structural. Per-write deduplication needs the write to be individually addressable and its outcome recorded under a name the caller chose. A batch of unknown size arriving as one job has neither. That is a property of the substrate, not something CI can be configured into.
The limits, stated plainly
A key stops the same write happening twice. It says nothing about whether the write should have happened at all. It will not catch a semantically wrong but well-formed change, it does not compile anything, and it is not a substitute for verifying the bytes that landed or for tests. Integrity and correctness are different properties, and only one of them is on offer here.
There is also no staging tier on OBTO today, no separate environment and no promotion path. If a change must be exercised somewhere that is not live before it ships, that gap belongs in your evaluation.
What a key buys is narrower and worth having: an agent that hits an ambiguous timeout has one correct move, which is to send the same call again with the same key. It either learns what already happened or is told the server cannot say. Both beat guessing. An error an agent can act on is the whole design goal.
The getting-started guide covers a first deploy on the free Builder tier, and the pricing page lists the tiers and the metered rates.