C.W.K.
Stream
Lesson 03 of 05 · published

The Ambiguous State

~12 min · ambiguous, state-machine, billing, uncertainty

Level 0Empty Shelf
0 XP0/35 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"'I might have paid and I have nothing to show for it' is a real state. Give it a name."

The State Most Systems Refuse to Model

The paid submission moves through a small state machine: preparingsubmittingcompleted. That looks tidy. But there's a fork most designs pretend doesn't exist, and it's the one that costs money. Picture the executor in submitting: it sent the request, then the connection died before any response came back. What is the state now?

You genuinely do not know. The provider may have received the request, run the transcription, and charged for it — the response just never made it home. Or the request may have never arrived at all. From where Recall stands, those two worlds are indistinguishable, and they have opposite financial consequences. Pretending it's a success loses the result; pretending it's a clean failure invites a retry that could double-charge. The honest answer is: this is a third thing, and it needs its own name.

Name It, Don't Guess It

Recall calls it ambiguous, and models it as a real, terminal-until-resolved state. The rules are precise:

  • completed returns the stored response with no new call — the safe, already-paid path.
  • submitting with a saved provider response in the executor's outbox is completed from that saved response (next lesson) — no new charge.
  • submitting with no saved response is ambiguous: maybe billed, maybe not, no result in hand.

And the crucial rule: an ambiguous call is never automatically retried. A blind retry is a coin-flip bet with real money — heads you needed it, tails you just paid twice. So Recall stops and routes the uncertainty to a human. A person checks the provider's actual billing record, learns which of the two worlds is real, and resolves the call explicitly: it was not billed (safe to redo), or it was billed but lost (accept the loss, don't redo for free), or it was billed and you genuinely need to pay again (a separate, explicit authorization). The machine refuses to guess where guessing spends money.

Why a First-Class State Beats a Best Guess

The temptation is always to collapse ambiguity into success or failure so the code has fewer branches. That tidiness is exactly the bug. Uncertainty that you fold into 'probably failed, retry' silently authorizes double-spending; uncertainty you fold into 'probably fine' silently loses data. Modeling the ambiguous state as its own named thing — parked, un-retried, waiting for a human with access to ground truth the machine doesn't have (the provider's billing) — is what keeps the money honest. When a machine can't know, the right move isn't a confident guess; it's an explicit 'I don't know' that a human can resolve.

Code

The paid submission state machine (the fork is the point)·text
preparing ──▶ submitting ──▶ completed
                   │
                   └──▶ ambiguous   (submitted, no response,
                                      no saved outbox copy)

completed  : return stored response, no new POST
submitting + outbox response : complete from the saved copy
submitting + NO response     : AMBIGUOUS  (maybe billed?)

ambiguous is NEVER auto-retried. A human inspects billing, then:
  resolve --not-billed     -> safe to redo
  resolve --billed-lost    -> accept loss, do NOT redo for free
  resolve --billed-lost --allow-paid-rerun -> explicit re-pay

External links

Exercise

Find an operation in your own work that can fail after a side effect in a way where you can't tell if the side effect happened — a payment that timed out, a message send that didn't confirm, a write that may or may not have landed. Right now, what does your code do in that case: assume success, assume failure, or retry? Design an explicit 'ambiguous' state for it: what gets parked, what ground truth a human would check, and what the resolution options are.
Hint
The tell is a catch block that treats 'timed out after sending' the same as 'never sent.' Those are different states with different costs. Add a third outcome that means 'unknown — do not retry,' record enough to investigate later, and define the explicit human resolutions (it happened / it didn't / redo on purpose). The goal is to never let the machine spend or destroy on a guess.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.