/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
$ npx -y skills add agentsope/SkillAlchemy --skill agentsop-llm-tool-idempotency --agent claude-codeHow 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.mdname: 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
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
Other skills on skillalchemy.
- /LEAP
LEAP — 落地执行引擎。内含两条管线:A 分支蒸馏(从 raw data 提取 skill)、 B 分支融合(多 skill 编织为一个)。被 SkillAlchemy 编排器调用。 Use when 编排器判断需要蒸馏或融合时。
Open skill - /Lens
Lens — 给你的问题加一层认知镜片。输入任意任务描述,输出增强版 description, 发现「你不知道自己不知道」的隐性维度、前置条件和认知路线。 Use when 用户说「帮我想想」「分析一下」「生成 skill」「蒸馏」「融合」 或输入看起来太简单需要展开。
Open skill - /agentsop-agent-topology-selection
Cross-framework enhancement overlay for choosing a multi-agent topology BEFORE writing any agent. A binary-question rubric — is single-agent + tools enough? do agents need to know about each other? does the output need one voice? — maps the answer to single-agent / supervisor /
Open skill - /agentsop-aider
SOP for terminal-based, git-native AI pair programming with Aider (git work-tree + tree-sitter repo-map + edit-format + human-in-loop REPL). Use when editing code in an existing git repo via an LLM, when you need to converge a change to 2-5 files, pick an edit format that fits
Open skill - /agentsop-bio-fraud-forensics
Screens biomedical / life-science papers for signs of data fabrication, image manipulation, and statistical anomalies, using the detection techniques distilled from the field's canonical exposure platforms (PubPeer, Data Colada, Science Integrity Digest, For Better Science) and
Open skill - /agentsop-bounded-loop
Universal discipline for any LM-driven loop — agent retries, plan-act-observe, multi-agent handoffs, optimiser passes, test-fix cycles. Encodes the one rule every framework documents quietly and every team relearns expensively: the LM in the loop is NEVER a reliable terminator.
Open skill

