Why Autonomous AI Agents Enter Infinite Retry Loops (And How to Build SHA-256 Idempotency Arbiters in TypeScript) A developer published a zero-dependency TypeScript implementation of an "Idempotency Arbiter" that sits in front of an autonomous LLM agent's tool-execution layer to stop infinite retry loops. The arbiter canonicalizes tool-call arguments, hashes the payload with SHA-256, and checks a 60-second sliding-window TTL cache, returning HTTP 409 for duplicate invocations and tripping a circuit breaker after a configurable number of repeats. The author argues that system prompts are not security boundaries or concurrency controllers, and that a fast agent runtime can fire 60 to 120 duplicate outbound calls during a brief upstream timeout. If you give an autonomous LLM agent running in Cursor, Claude Code, LangChain, or CrewAI the ability to invoke external tools—like dispatching an email, pinging a webhook, or charging a card—you will eventually experience the Infinite Retry Loop . It usually starts innocently: an upstream API experiences a 1.2-second socket timeout or returns a transient HTTP 503 Service Unavailable. In traditional software, your client executes an exponential backoff policy and fails gracefully. But an autonomous agent does not read error codes like a compiler; it reads error text as context in its conversational history. When the LLM observes: { "status": "error", "code": "ETIMEDOUT", "message": "Gateway timeout connecting to mail relay" } The agent's next autoregressive turn is almost always: "The dispatch failed due to a timeout. I will try calling the tool again with the same parameters." If the socket remains congested for 10 seconds, a fast agent runtime executing in a while-loop can easily fire 60 to 120 duplicate outbound calls , obliterating your third-party API quota, triggering provider spam alerts, or spamming your end users. Here is the fundamental rule of agentic engineering: System prompts are not security boundaries, and they are not concurrency controllers. A suggestion in a prompt "Do not retry tool calls if they fail" can and will be overridden when the model hallucinates or prioritizes completing its user goal. You need deterministic, stateful Idempotency Arbiters written in code. An Idempotency Arbiter sits directly in front of your tool execution layer. Before any external I/O or network socket is touched, the arbiter inspects the intent and payload of the tool invocation: ┌─────────────────────┐ │ Autonomous LLM Loop │ └──────────┬──────────┘ │ Tool Call: send email to, subject, body ▼ ┌──────────────────────────────────────────────┐ │ Deterministic Idempotency Arbiter │ ├──────────────────────────────────────────────┤ │ 1. Canonicalize argument keys │ │ 2. Hash payload SHA-256 │ │ 3. Check sliding-window TTL cache e.g. 60s │ └──────────┬────────────────────────┬──────────┘ │ │ New Hash Duplicate Hash │ │ ▼ ▼ ┌──────────────────────┐ ┌────────────────────────────────┐ │ Execute Tool Socket │ │ Intercept & Return HTTP 409 │ │ Cache Hash in TTL │ │ "Duplicate invocation dropped" │ └──────────────────────┘ └────────────────────────────────┘ { a: 1, b: 2 } vs { b: 2, a: 1 } . We must recursively sort all keys before serialization. 409 Conflict , preserving the socket, API budget, and recipient sanity. OPEN state, halting execution and notifying a human operator. Here is a zero-dependency, type-safe implementation of a sliding-window Idempotency Arbiter for tool-calling agents: js import { createHash } from "node:crypto"; export interface ToolInvocation