Top 1% Upwork (8 years) 286+ client deployments 2,036+ projects shipped GoHighLevel Certified Partner Featured speaker: GHL Summit 2025 Client Login
← All issues
The Scale Brief · Issue #149

Your agent will retry.
Without idempotency keys, it double-books.

Search your agent's logs for the same tool call invoked twice within a minute. You'll find it. Now check whether the second call actually did something or was a safe no-op.

Agents retry. They retry when the LLM call times out and the orchestrator falls back. They retry when a tool returns a 5xx. They retry when the network blips mid-stream. They retry when an upstream API rate-limits and the SDK transparently backs off.

None of this is a problem if the tools are idempotent. All of it is a problem if they aren't.

The agent invokes book_calendar(user, slot). The HTTP call returns a 504 after 30 seconds because the calendar API is slow. The orchestrator retries. The second call succeeds in 200ms. The user has two identical bookings, and the first one might have actually succeeded — the 504 lied.

The pattern

Two parts. Each is one line:

  1. Generate a deterministic key per logical action. Hash the action's semantic identity — not the timestamp, not a random UUID. The key is the same key for any retry of the same logical operation.
  2. Pass the key to every side-effecting tool call. The tool's implementation (or the upstream API) uses the key to dedupe.

"Semantic identity" is the operation's full intent: this user + this calendar + this exact slot, not at this exact moment in time. If the agent decides to book the same user for the same slot a second time intentionally — that's a new logical action, with a new key. If it accidentally invokes the same tool twice — same logical action, same key, the second one no-ops.

The implementation

function idempotencyKey(actionType, payload) {
  const semantic = {
    action: actionType,
    user_id: payload.user_id,
    target:  payload.target_id || payload.email || payload.slot_id,
    amount:  payload.amount,
    // session_id pins the key to one conversation turn — so retries within
    // a turn dedupe, but new conversations get fresh keys
    session_id: payload.session_id,
  };
  const canon = JSON.stringify(semantic, Object.keys(semantic).sort());
  return sha256(canon).slice(0, 32);  // 32 hex chars = 128 bits
}

async function bookCalendar(payload) {
  const key = idempotencyKey('book_calendar', payload);
  return await calendarApi.book({
    ...payload,
    'Idempotency-Key': key,  // Stripe-style header
  });
}

async function chargeCard(payload) {
  const key = idempotencyKey('charge_card', payload);
  return await stripe.charges.create({
    amount: payload.amount,
    customer: payload.customer,
    idempotency_key: key,  // Stripe's own param
  });
}

That's it. Every Stripe customer learned this in 2016. Most agent frameworks don't have it built in.

What "semantic identity" actually means

The trickiest part of the pattern is picking the right hash inputs. Get this wrong and you either:

The right set is: operation + all parameters that distinguish this from a different valid call. For booking: user + calendar + specific time slot. For charging: customer + amount + invoice ID. For sending email: recipient + template + the action that triggered it (session_id pins the trigger).

If the same set of parameters appearing twice should mean "do it twice," you've picked the wrong set.

The retry-from-where matters

Where retries originate determines what you need to handle:

The audit signal

Log { action_type, idempotency_key, response_id } on every side-effecting call. Aggregate weekly. Two flags:

The fix list

For every side-effecting tool we ship at AutomateScale, the contract is the same: deterministic idempotency key from semantic inputs, logged on every call, audited weekly. The cost is two lines per tool. The cost of getting it wrong is one of your client's customers getting double-charged in front of their CFO. Want us to audit your tool layer? Apply for the audit.

The one-line summary

Agents retry. The retry isn't the bug — the missing idempotency key is. Hash the semantic identity of every side-effecting action, pass the key through, and your worst-case retry becomes a no-op instead of a duplicate. Pairs with the gate from #148 and the planner from #147.

Enjoyed this? One essay like this every Sunday — 12,400+ founders read it.
Subscribe free RSS

Keep reading

Issue #148
Least-Privilege Agent Tool Access
The gate that pairs with this idempotency layer.
Issue #147
The Cheap-Model Planner: Route, Don't Reason
The planner that feeds the gate that fronts these tools.
Issue #150 · NEW
12 Business Automations + The OS That Makes Them Compound
Why scattered automation can't compound — and the business-OS architecture that fixes it.
★★★★★

"Adam was great! He gave me some great actionable advice, I left our consultation with a page full of notes. He is a true expert, highly recommend a consultation with him if you need an experts opinion on sales funnel."

30 minute consultation · 2023 · Upwork verified →
★★★★★

"Adam was great! He gave me some great actionable advice, I left our consultation with a page full of notes. He is a true expert, highly recommend a consultation with him if you need an experts opinion on sales funnel."

30 minute consultation · 2023·Upwork verified → · Upwork ✓
★★★★★

"Adam is very skilled and knowledgable and was a tremendous help in getting us setup and going."

Configure Kajabi to work with our Infusionsfoft / woocommerce to serve up videos · 4.7h·2015·Upwork verified → · Upwork ✓
Run the audit on your agents

The Scale Audit ships idempotency
+ 24 other patterns on every deploy.

Apply for an audit and we'll instrument every side-effecting tool in your agent layer with deterministic keys + weekly dedupe-audit script.

Apply for a free audit All issues