# Agent Retry Guard · 0.1.1 A small, free local guard for agent tool retries after a lost response. Node.js 24+ · SQLite built into Node · no npm dependencies · MIT. **First useful result:** download the ZIP, extract it, open a terminal in the extracted folder and run `node demo.mjs`. No signup, API key, wallet or network request. The demo makes mock purchases only and checks its own results. Run `node --test guard.test.mjs mcp-demo.test.mjs` for SDK and MCP integration tests. ## What it does - Claims a stable business operation key in a durable local SQLite file before invoking your callback. Separate processes sharing that local file contend on the same unique row, not independent in-memory flags. - Reuses the JSON result of a confirmed operation after retry or process restart. - Rejects the same key with changed action/input. - Stops on an exception, interrupted process or ambiguous external outcome. It never expires a pending claim and retries an external effect automatically. - Derives a stable downstream idempotency key scoped to your tenant. This is an application integration aid, **not an exactly-once payment system**. It does not detect whether a merchant was paid, sign transactions, hold funds, verify receipts or enforce a wallet budget. A timeout is not proof of failure. ## Integrate at the actual side-effect boundary ```js import { RetryGuard } from './guard.mjs'; const guard = new RetryGuard({ filename: './private-state/operations.sqlite', // create a private directory first scope: 'company-123:production' // trusted backend-derived tenant }); const intent = { key: 'order-1001:purchase', // stable across retries/restarts action: 'merchant-api.example/purchase:v1', // bind exact provider and operation input: { amountMinor: 50, currency: 'USD', productId: 'report-12' } }; const outcome = await guard.run(intent, async ({ idempotencyKey }) => { // Replace this mock with YOUR provider call, using the same validated input. // Forward idempotencyKey if the provider supports it. Return small JSON only. return { receiptId: 'mock-only', providerIdempotencyKey: idempotencyKey }; }); console.log(outcome); // {state:'succeeded', replayed:false|true, value:{...}} guard.close(); ``` Keep one stable key per intended business action, not per HTTP request, retry, agent conversation, payment signature or JSON-RPC request ID. A genuinely new purchase must use a new key. Bind every meaningful term in `action` and `input`, including provider, endpoint/tool version, payee, asset/network and order terms when applicable. The callback must execute those exact validated terms. For an MCP tool server, put `guard.run(...)` inside the authenticated tool implementation, before the actual side effect. Derive `scope` from your trusted backend identity, never an untrusted prompt. Do not expose reconciliation as an agent tool. A local stdio MCP demo is included; it makes mock purchases only. It is not a production payment adapter or a native x402 adapter. ## Machine-readable outcomes | Code/result | What your application must do | | --- | --- | | `succeeded`, `replayed: true` | Return stored result; no new callback | | `KEY_CONFLICT` | Reject changed terms under the same business key | | `IN_PROGRESS_OR_INTERRUPTED` | Stop; the first operation may still run or have crashed | | `UNCERTAIN_OUTCOME` | Stop; query the authoritative provider before any new action | | Database error before claim | Fail closed; do not invoke the provider separately | An exception, malformed/oversized callback result or local completion write failure is treated conservatively as uncertain. Errors are not automatically classified as safe to retry, even if the callback claims no effect occurred. ## Operator reconciliation `guard.inspect(intent)` returns only local state and a recommended decision. It does not query or attest to external execution. Only a trusted operator, **after verifying the authoritative provider outcome** and that the original operation is no longer running, may record a known success with: ```js guard.reconcileSuccess(intent, { result: { receiptId: 'provider-receipt-123' }, evidence: 'Operator checked provider receipt 123 and original execution has ended', acknowledgeExternalVerification: true }); ``` The library stores this operator-supplied statement; it does not independently verify it. There is deliberately no automatic unlock, reset, retry-after-TTL, refund or "failed so try again" method. Unresolved cases need investigation. ## Deployment and data boundaries - This edition supports one trusted host with persistent local storage. Multiple processes must use the **same file and scope**. Separate hosts/files, serverless ephemeral disks and network filesystems are not a shared transaction ledger. - All relevant side-effect paths must use the guard. It cannot stop callbacks that issue multiple purchases, provider-side duplication, direct wallet access, changed operation keys or deliberate bypass by an untrusted agent. - The application owns downstream idempotency, cancellation and reconciliation. No atomic transaction spans SQLite and an external provider. - The input fingerprint is SHA-256 over canonical plain JSON. Input bodies and exception text are not stored. **Successful result JSON and operator evidence are stored locally in plaintext**, and hashes of predictable inputs may be guessable. Return a minimal receipt, never credentials or sensitive payloads. - Keep the directory private, protect backups, and retain the database for as long as duplicate suppression is needed. Removing/rolling back/copying an old database, changing scope or losing its disk loses relevant protection. No automatic retention deletion is performed by the SDK. - Limits: input and result each <=64 KiB, JSON depth <=40, scope/key/action <=200 characters. Use integer minor units for amounts. No telemetry in SDK. - SQLite availability/permissions are the deployer's responsibility; test on your target OS/runtime. The shipped tests ran on Linux Node 24.22.1. ## Price and roadmap The local MIT edition is free; there is no 30-day expiry or future charge. A managed multi-host ledger with team workflows is **not available** in 0.1.1. An optional EUR 9/month entry plan is a pricing hypothesis, not a binding offer, preorder or subscription. No billing, payment method or purchase mandate exists. The existing Agent Trust Firewall 30-day pilot is a separate service. Tell us about an integration or request a managed edition via a GitHub issue in Benji18sport/agent-commerce-os (no credentials/private logs). Reports and feature requests are not paid customers or independent adoption proof. ## Provenance A documented MCP reproduction describes duplicate effects after response loss: https://github.com/modelcontextprotocol/modelcontextprotocol/issues/3394 It is a contributor's bounded reproduction, not a universal MCP failure claim. Application-level idempotency is discussed in x402 issue 452: https://github.com/x402-foundation/x402/issues/452 Existing alternatives and upstream fixes exist. In particular x402 settlement pending recovery was completed, so this release does not claim to fix a still unpatched generic x402 settlement bug: https://github.com/x402-foundation/x402/issues/2294 ## Try with an MCP client — no registration This release includes a local MCP stdio server for **mock purchases only**. It is a reference integration, not a wallet or live merchant connection. With Node 24+ installed, configure your stdio-capable MCP client as follows, replacing both paths with absolute paths on your machine: ```json { "mcpServers": { "agent-retry-guard-demo": { "command": "node", "args": ["/ABSOLUTE/PATH/mcp-demo.mjs", "--state-dir", "/PRIVATE/LOCAL/PATH/demo-state"] } } } ``` On Windows use JSON-escaped backslashes (`C:\\...`) or forward slashes in absolute paths. Clients have different configuration locations; this is a common stdio configuration shape, not a claim of certification by every client. No port is opened. No funds, credentials or network provider are involved. The default state directory, if omitted, is `.agent-retry-guard-demo` in your home directory. Keep it private. Deleting it removes duplicate protection. Ask the agent to: 1. Call `retry_guard_demo_purchase` with `order_id: "try-001"` and `amount_minor: 10`. 2. Repeat the same call: expect the same stored receipt, `replayed: true`, and `mock_effect_count: 1`. 3. Use a NEW order ID `try-002`, same amount, and `mode: "lose_response"`: expect `UNCERTAIN_OUTCOME`. Repeat with `mode: "normal"`: it must stay blocked. 4. Call `retry_guard_demo_status` with the same order and amount; the uncertain operation has one mock effect. Restarting the server does not clear it. The demo provider has its own SQLite file, separate from the guard transaction, so the injected failure occurs after the mock effect. Nothing is reconciled or unlocked automatically. There are no reset or operator-reconciliation MCP tools. The protocol harness tests initialize/discovery/tool calls, server restart and two simultaneous server processes. External MCP-client UIs have not been tested.