Day 35: Idempotent Tool Calls Against DynamoDB

Day 35: Idempotent Tool Calls Against DynamoDB

Agents retry. Networks retry. Users mash buttons. If mutating tools are not idempotent, you will double-charge cards or open two Jira tickets for one intent. Day 35 standardizes DynamoDB conditional writes and client tokens across every side-effecting tool.

⚡ TL;DR: Require idempotencyKey on mutating tools. Conditional put on (tenant, tool, key). Replay stored results on duplicates. Include the key in EventBridge jobs (Day 34) and approval flows (Day 33).

Schema requirement

{
  "name": "create_refund_v1",
  "inputSchema": {
    "json": {
      "type": "object",
      "required": ["orderId", "amountCents", "idempotencyKey"],
      "properties": {
        "orderId": {"type": "string"},
        "amountCents": {"type": "integer", "minimum": 1},
        "idempotencyKey": {"type": "string", "minLength": 16, "maxLength": 128}
      },
      "additionalProperties": false
    }
  }
}

Prefer UI-generated keys so the model cannot wobble between retries. If the model generates keys, instruct: one UUID per user intent, reuse on retry.

Conditional write pattern

def mutate(tenant: str, tool: str, key: str, fn):
    pk, sk = f"t#{tenant}", f"tool#{tool}#id#{key}"
    try:
        table.put_item(
            Item={"pk": pk, "sk": sk, "status": "IN_PROGRESS", "ttl": ttl_hours(24)},
            ConditionExpression="attribute_not_exists(pk)",
        )
    except ClientError as e:
        if e.response["Error"]["Code"] != "ConditionalCheckFailedException":
            raise
        item = table.get_item(Key={"pk": pk, "sk": sk})["Item"]
        if item["status"] == "DONE":
            return item["result"]
        raise Conflict("in_progress")
    result = fn()
    table.update_item(
        Key={"pk": pk, "sk": sk},
        UpdateExpression="SET #s=:d, result=:r",
        ExpressionAttributeNames={"#s": "status"},
        ExpressionAttributeValues={":d": "DONE", ":r": result},
    )
    return result
create_refund(order_id, amount)

Exactly-once delivery is a myth; at-least-once + idempotency is the design.

Production checklist

  • [ ] All mutating tools require idempotencyKey
  • [ ] Conditional put + stored result replay
  • [ ] TTL on idempotency records (24–72h)
  • [ ] Metrics for dedupe hits
  • [ ] Chaos: replay same call 10× in staging
  • [ ] Keys scoped with region if multi-region (Day 38)

Series navigation

← Day 34 · Day 36 →

Last updated September 11, 2026


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply