Skip to content
Development
Skill

/agentsop-llm-tool-idempotency

Decision protocol for making side-effectful agent tools idempotent — so when an LLM tool call is retried (timeout, framework resume, user re-run, model duplicate emit), the second call is a no-op instead of a double-send. The load-bearing premise: the LM cannot promise it'll

From plugin
skillalchemy
40447 skills
Install
$ npx -y skills add agentsope/SkillAlchemy --skill agentsop-llm-tool-idempotency --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/agentsop-llm-tool-idempotency

Context preview

The summary Claude sees to decide when to auto-load this skill.

Decision protocol for making side-effectful agent tools idempotent — so when an LLM tool call is retried (timeout, framework resume, user re-run, model duplicate emit), the second call is a no-op instead of a double-send. The load-bearing premise: the LM cannot promise it'll

SKILL.md

agentsop-llm-tool-idempotency.SKILL.md
name: agentsop-llm-tool-idempotency
version: 0.1.0
description: >-
  Decision protocol for making side-effectful agent tools idempotent — so when an LLM tool
  call is retried (timeout, framework resume, user re-run, model duplicate emit), the second
  call is a no-op instead of a double-send. The load-bearing premise: the LM cannot promise
  it'll call exactly once; the tool must promise the second call is safe. Framework-agnostic
  — applies to LangGraph node bodies that re-run on resume, MCP tools, OpenAI tool-calling
  retries, CrewAI delegated tool invocations, and direct HTTP wrappers. Search keywords:
  duplicate email sent, charged twice, exactly-once, idempotency key, tool called twice,
  retry side effect, double-send, at-least-once delivery.
domain: coder-agent / tool-execution-safety
audience: engineers wiring LLM agents whose tools have side effects (email,
  payment, DB writes, message posting, file creation, API calls)
trigger_keywords:
  - "idempotency key"
  - "double send"
  - "tool retry"
  - "exactly-once"
  - "at-least-once"
  - "duplicate side effect"
  - "node body re-runs on resume"
  - "interrupt resumed twice"
when_to_use:
  - "any agent tool that performs a side effect: send_email, create_record,
    charge_card, post_message, write_file, publish_event, transfer_funds"
  - "LangGraph node containing both an interrupt and a side effect"
  - "tool that wraps a non-idempotent third-party API (SendGrid, Twilio, S3 PUT
    of a generated id)"
  - "model-orchestrated workflow where the LM may emit the same tool_call_id
    twice due to streaming retries or compaction"
  - "MCP server exposing tools to a client that may re-invoke on transport
    failure"
when_not_to_use:
  - "pure read tools (search, get, list) — already idempotent by HTTP semantics"
  - "the side effect is intrinsically commutative (incrementing a counter where
    duplicates are acceptable — but verify this; usually they aren't)"
  - "single-call manual scripts with no retry layer above"

LLM Tool Idempotency · SOP

> One-liner: **The LM is at-least-once; the tool must be at-most-once.** > Every framework that promises "durable execution" still re-runs node bodies > on resume. Every HTTP client retries on timeout. Every model occasionally > emits the same tool_call twice. Idempotency belongs in the tool, not in a > wish.

---

1. 何时激活 (Activation Rules)

Activate this skill when **any** of the following triggers fire:

  • You're defining a tool whose name contains `send_`, `create_`, `charge_`,

`post_`, `write_`, `publish_`, `transfer_`, `delete_`, `update_`, or `notify_`.

  • The tool wraps a third-party API call (Stripe, SendGrid, Twilio, Slack,

Discord webhook, S3 PUT, payment gateway, internal write API).

  • You're inside a LangGraph node that contains an `interrupt(...)` call AND a

side effect in the same function body — the resume re-runs the body from the top `[langgraph/gotchas]`.

  • The tool is invoked through MCP, OpenAI tool-calling, Anthropic tool use,

CrewAI delegation, or any layer where a transport timeout could be interpreted as "retry" even though the operation succeeded server-side.

  • The user reports "the agent sent it twice" / "charge appeared twice" /

"duplicate row" / "got two emails".

  • Your test harness records the same `(tool_name, args_hash)` invoked more

than once within a single user turn.

**Do not activate** when the tool is read-only (GET-equivalent), or when the side effect is genuinely commutative AND verified safe under duplication.

---

2. 核心心智模型 (Core Mental Model)

2.1 The fundamental asymmetry

   LM tool-call semantics       Tool side-effect semantics
   ─────────────────────        ─────────────────────────
   At-least-once delivery       Must be at-most-once
   (network retry, framework    (one charge, one email,
    resume, model dup-emit,      one record)
    user re-prompt)
        │                                │
        └────── gap to bridge ───────────┘
                       ↓
              IDEMPOTENCY KEY
       (a stable identifier the LM
        commits to BEFORE the call,
        which the tool dedupes on)

**The LM cannot promise it'll call exactly once.** Four independent retry sources stack here:

1. **Transport retry**: HTTP client (or MCP transport) sees a timeout, the server actually completed the operation, the client retries. Stripe's docs call this out as the canonical case `[stripe/idempotency]`. 2. **Framework resume**: LangGraph re-runs the *entire node body* on resume from an interrupt. Code before the interrupt re-executes on every resume `[langgraph/gotchas]`. Same applies to Temporal-style workflows on replay. 3. **Model duplicate emission**: Streaming sometimes yields the same `tool_call_id` twice (rare but documented in OpenAI tool-calling); model may re-emit on context-compaction round-trips. 4. **User-level retry**: The user clicks "send" twice, or re-runs the agent after timeout, with the same instructions.

Any one of these turns a single-intent action into multiple side effects unless the tool itself dedupes.

2.2 The promise inversion

Naive design says: "I'll make the LM call the tool exactly once."

Mature design says: "I'll make the tool ignore the second call."

The inversion matters because the LM is in the *control* path; the tool is in the *execution* path. Execution-path guarantees are the only ones that hold under failure.

2.3 The idempotency key has to come from the agent, not the tool

A common bug: the tool generates a UUID *inside* itself, then dedupes on that UUID. This breaks because retry creates a *new* UUID. The key has to:

  • Be **chosen by the caller** (the agent / orchestrator).
  • Be **deterministic for a single logical operation** — same input → same

key on retry.

  • Be **persisted into agent state** before the tool call, so a resume picks

up the same key.

Stripe's pattern (the industry reference): client generates an idempot

Read more
Ships withskillalchemy

Turn people, methods, and experience into installable, reusable agent skills. SkillAlchemy is an open-world agent skill creation system that turns underspecified skill briefs and open-world sources into installable, reusable agent skills.

Get the whole plugin
Stats
413
Stars
22
Forks
Active
Maintenance
Python
Language
MIT
License
14d ago
Last commit
3mo ago
Created

Repo: agentsope/SkillAlchemy

Other skills on skillalchemy.