Idempotency middleware for LLM agent tool calls — prevent duplicate side effects on retry.
Project description
latch
Idempotency middleware for LLM agent tool calls. Prevents duplicate side effects (double-charged orders, duplicate emails, duplicate records) when an agent retries a tool call after a timeout or transient failure.
The problem
An agent calls a tool. The call times out. The agent doesn't know if the underlying action completed before the timeout — it just knows it didn't get a response. Retrying is the only reasonable move, but if the tool isn't idempotent, retrying can execute the side effect twice.
Install
pip install latch-idempotent
Quickstart
from latch import idempotent
@idempotent()
def create_order(order_id: str, amount: float) -> dict:
# ... call your payment/order API ...
return {"order_id": order_id, "status": "created"}
# The agent framework supplies a unique key per logical operation.
result = create_order(order_id="A1", amount=42.0, idempotency_key="run-7-step-3")
# A retry with the same key returns the cached result instead of
# re-executing create_order — no duplicate order.
result_again = create_order(order_id="A1", amount=42.0, idempotency_key="run-7-step-3")
assert result == result_again
Works the same way for async def tool functions.
How it works
idempotency_keyis a required keyword argument —latchnever guesses or auto-generates one. The caller (your agent framework or orchestration code) decides what constitutes "the same logical operation."- On first call with a given key, the function executes and the result is cached.
- On any subsequent call with the same key, within the TTL window (default 24h), the cached result is returned and the function is not re-executed.
- If the wrapped function raises, nothing is cached — the exception propagates normally so retries can still happen.
Storage backends
Default is an in-memory store (single-process, not persistent across restarts). For multi-process or production deployments, use a distributed store:
from latch import idempotent, InMemoryStore
store = InMemoryStore()
@idempotent(store=store, ttl_seconds=3600)
def send_email(to: str) -> dict:
...
Redis backend is planned for v0.2 (pip install latch-idempotent[redis]).
Status
v0.1 — idempotency only. See CLAUDE.md for the roadmap (circuit breaker, budget guardrails, saga/compensation, and framework adapters are planned for later versions).
Prior art
This library exists alongside academic work on agent reliability — see SagaLLM, Robust Agent Compensation, and ReliabilityBench. latch's contribution is a small, adoptable, production-ready implementation of the idempotency pattern specifically — not a claim of inventing it.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file latch_idempotent-0.1.0.tar.gz.
File metadata
- Download URL: latch_idempotent-0.1.0.tar.gz
- Upload date:
- Size: 9.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ae04ca9597abe61e800007ba8eede89d6af599864d212590e83a9cd350da6237
|
|
| MD5 |
aea80e13c087c69bcdbc3c72820a74f5
|
|
| BLAKE2b-256 |
2f91645351c3a70b915cd472f64007846cf73e0eaea1d32e667d0a3b9c353b5e
|
File details
Details for the file latch_idempotent-0.1.0-py3-none-any.whl.
File metadata
- Download URL: latch_idempotent-0.1.0-py3-none-any.whl
- Upload date:
- Size: 7.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
70239c4f8b606e61698bcef08cafdb2e987c892c3cd1f0640fda617f1c65967f
|
|
| MD5 |
46ce533fbcf6ed74333f8760180e8f8f
|
|
| BLAKE2b-256 |
10d2f7220652253f6ae74cc1987f36f58d8bbccb55f56cfa1bc07e2ca84decf5
|