Rightshold™ Sign up

Answers · agent safety

How do I make an AI agent's side-effecting tool calls (payments, emails, tickets, deploys) idempotent, cap retries per task, and require a new approval when the amount, recipient or scope changes?

Last updated

TL;DR: Give every side-effecting action one key, created when the agent decides to act and reused unchanged on every retry, and make the receiving side deduplicate on it: an Idempotency-Key header where the API supports one, your own client-side ledger of keys and results where it doesn't. Count attempts per action and per task, stop at a small cap, and mark errors such as validation failures, denials and insufficient funds as non-retryable so they go to a person instead of the retry loop. Then tie any approval to a hash of the exact action (amount, currency, recipient, scope, tool and arguments). A retry with the same hash can reuse the approval; a retry where the model changed the amount, the payee, the recipient list or the permission scope gets a new hash, so the old yes no longer applies and the agent must ask again. Agent frameworks retry on their own (Temporal retries activities with unlimited attempts by default; LangGraph re-runs a node when it resumes), so the key and the cap have to live outside the model's turn.

What should I check, step by step?

1. List the agent's tools that change something outside the run: payments, refunds, emails and messages, tickets, calendar invites, file writes, deploys, permission grants. Every one of these needs an action key; read-only tools don't.

  1. Create the key once, when the agent commits to the action, from the task id plus a step id (or a random UUID stored with the step). Never generate it inside the function that sends the request, or each retry looks like a new action.
  2. Pass the key to the API if it accepts one (Stripe's Idempotency-Key header, x402 payment-identifier, provider client-request ids). Otherwise write the key and the outcome to your own store before and after the call, and check the store before any retry.
  3. Define the action hash: a SHA-256 of the canonical JSON (for example RFC 8785) of tool name, arguments, amount in minor units, currency, recipient or payee, and requested scope. Keep it next to the key.
  4. Set retry caps in the tool wrapper: for example 3 attempts per action and 10 retries per task, with exponential backoff. Override framework defaults that retry without limit.
  5. Mark errors that won't fix themselves (422 validation, 401/403, policy deny, insufficient funds, recipient not found) as non-retryable, and send them to a person or back to the planner with the reason.
  6. Before retrying with a changed action, recompute the hash. If it differs from the approved hash, cancel the old approval and request a new one; never edit the amount or recipient under an existing approval.
  7. After a timeout, reconcile before retrying: look up the charge, the sent message or the created ticket by its key. An unknown result is not a failure.

Why do AI agents repeat side effects like payments and emails?

Retries come from several layers at once: the HTTP client, the agent framework, the durable-execution engine, and the model itself calling the same tool again when the output was unclear. Each layer can resend a payment or an email unless the action carries a stable key.

How do idempotency keys and action hashes work together?

The idempotency key answers 'is this the same attempt?'. The action hash answers 'is this the same thing a person approved?'. Keep both: the key stops duplicates, the hash stops drift.

How do I set per-task retry caps an agent can't raise?

Count attempts in the tool wrapper or gateway, keyed by task id and action key, not in the prompt. A cap the model can read and argue with is a suggestion.

When must an agent's retry go back to a human for approval?

Amount went up (or the currency changed), even slightly: new hash, new approval.

What are the common failure modes?

The key is generated inside the retry loop, so three timeouts send three emails.

How do I ask Rightshold about this (free, no key)?

Add the MCP server https://www.rightshold.com/mcp to Claude, Cursor, ChatGPT, Grok or VS Code, then ask: “This refund retry changed the amount from 40.00 to 45.00 USD. Ask the owner to approve the exact new action before my agent retries it, with a 30-minute expiry.”

One-click setup

{
  "tool": "request_human_approval",
  "arguments": {
    "action": {
      "tool": "issue_refund",
      "amount_minor": 4500,
      "currency": "USD",
      "recipient": "cus_example",
      "scope": "refunds:write",
      "idempotency_key": "task-81f2:step-3"
    },
    "summary": "Refund retry: amount changed 40.00 -> 45.00 USD; needs a new approval",
    "credential_id": "<this agent's credential id>",
    "expires_in_minutes": 30
  }
}

Rightshold records approvals, spend policies and allow/deny decisions, and signs each one. It doesn't send your requests, retry them, hold idempotency keys for third-party APIs or move money; your tool wrapper has to enforce the key, the cap and the decision. Not financial advice.

What are the sources?

Related

Not legal advice. JSON · Markdown · All answers

Get a free API key in one call

No card and no email needed. 100 free credits; the license and policy checks stay free without a key.

curl -s -X POST https://www.rightshold.com/v1/accounts/signup -H "Content-Type: application/json" -d '{"accept_terms":true}'
export RIGHTSHOLD_API_KEY=rs_...   # the api_key from the reply (shown once)
curl -s https://www.rightshold.com/v1/licenses/check -H "Authorization: Bearer $RIGHTSHOLD_API_KEY" \
  -H "Content-Type: application/json" -d '{"license":"MIT","intended_use":{"commercial_use":true}}'