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

Check Completed / Pending / Unknown Before Any Model Call

~12 min · retry-logic, state-check, guard, dedupe

Level 0Dead Zone
0 XP0/32 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"A safe retry doesn't ask 'send again?' It asks 'what already happened to this turn?' — and often the answer is 'nothing more is needed.'"

Retry Is a Question, Not a Resend

The naive retry just fires the request again. The safe retry first checks the state of the turn, keyed by its device turn ID, and only calls the model if calling is actually warranted. Because delivery is at-least-once, a retry might be re-sending something that already fully succeeded — the answer exists, only the acknowledgment was lost. Resending blindly would redo real work. So before any model call, the client resolves the turn into one of three states:

  • Completed — the backend already has an answer for this device turn ID. Don't call the model; fetch and show the existing answer.
  • Pending — the turn is known and in flight or queued, but not answered. Don't call again; wait and let it resolve.
  • Unknown — the backend has no record of this ID. This is the only state where calling the model is correct: the intent genuinely hasn't been carried out.

The Guard Turns Duplicates Into No-Ops

This check is the concrete mechanism that delivers exactly-once intent. The dedupe key from the last lesson gives you a stable identity; this guard is what uses it. Every retry passes through the same gate: resolve state, then branch. Completed and pending both short-circuit without new work — they're the no-ops that make at-least-once delivery harmless. Only "unknown" proceeds to the model. Duplicates don't need to be prevented on the wire because this guard absorbs them at the client.

Where the Check Lives Matters

Put the guard before the model call, not after. A check that runs after invoking the model has already paid the cost it was meant to avoid. The right shape is: on retry, ask the backend (or read a durable local record synced from it) for the turn's status by ID; branch on the answer; call the model only in the unknown branch. It's a few lines, but those lines are the entire difference between a client that retries safely and one that quietly does everything twice.

Code

Resolve state first; call the model only when unknown·typescript
async function retry(turn: PippaGoTurn) {
  const state = await resolveTurnState(turn.deviceTurnId); // ask by dedupe key

  switch (state.kind) {
    case "completed":
      return showAnswer(state.answer);        // already done — no model call
    case "pending":
      return showWaiting();                    // in flight — no model call
    case "unknown":
      return materializeFromCanonical(turn);   // ONLY here do we call the model
  }
}

// The model call sits behind the guard, never in front of it.
// 'completed' and 'pending' are the no-ops that make retry safe.

External links

Exercise

Write the three-way retry guard in pseudocode: resolve state by device turn ID, then branch on completed / pending / unknown. Now audit a real retry you've written or seen — does it check state first, or does it just resend? If it resends, describe the exact double-action that at-least-once delivery will eventually cause, and where you'd insert the guard to stop it.
Hint
Most retry code in the wild is a bare resend with a backoff timer — no state check at all. It works until the first dropped acknowledgment, then silently double-acts. The fix is always the same: resolve state by the dedupe key before the call, and make 'completed'/'pending' no-ops.

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.