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
28747 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

From thought to skill. From signal to structure.

Get the whole plugin
Stats
289
Stars
17
Forks
Active
Maintenance
Python
Language
MIT
License
7d ago
Last commit
2mo ago
Created

Repo: agentsope/SkillAlchemy

Other skills on skillalchemy.