← All articles Engineering

The write that knows it already ran

OBTO Team · Insights from the Glass Box

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:

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.

  1. Is there a per-write idempotency key, or only job-level retry? A re-runnable job is a coarser promise about a bigger unit.
  2. 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.
  3. 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.
  4. 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.

Describe it. Ship it. Own it.

Build on OBTO from the free Builder tier, with an itemized per-run ledger of model, tokens, cost and tools on by default.

Get started

Frequently asked questions

What is an idempotency key for an AI agent write?

It is a caller-generated name for one logical write, sent along with the write itself. The server records a receipt under that name when the write commits. A later call carrying the same name gets the stored receipt back instead of executing again, so a retry after an ambiguous failure cannot apply the change twice. Use a fresh name for every new write, and never reuse one across different edits.

What should happen when the idempotency store is unavailable?

The write should be refused. If a key was supplied and the server cannot read the receipt store, it cannot tell whether an earlier attempt already committed, and executing anyway is exactly the duplicate the key exists to prevent. On OBTO that path returns idempotency_lookup_failed and nothing is written. Ask any vendor what their system does here, because failing open is the common default and it will not show up in an evaluation.

Does an idempotency key make a retry safe?

It stops a retry re-applying a write that already committed, which is narrower than safe. The key says nothing about whether the write was correct, whether the code compiles, or whether the change was a good idea. Integrity and correctness are different properties. Correctness still needs tests, review, and a person who cares.

Why can't a CI/CD pipeline do per-write idempotency?

Because its unit of work is a job, not a write. A pipeline can make a job re-runnable, and declarative systems such as Kubernetes get idempotency for free because applying the same desired state twice is a no-op. Imperative line-level edits have neither property. Per-write deduplication needs the write to be individually addressable and its outcome recorded, which is a property of the substrate rather than something a pipeline can add on top.

Does OBTO have a staging environment?

No. There is no separate dev or staging tier and no built-in promotion path today. Checks run on the write itself. If your change has to be exercised somewhere that is not live before it ships, that is a real gap and you should weigh it before building on us.

More from the OBTO blog