Retry safety for agent tools and applications

Your agent sent the refund.
The response disappeared.
Should it try again?

Once remembers the logical operation, reuses confirmed results, and blocks unsafe repetition when the original outcome is uncertain. Reconciliation needs authoritative provider truth.

Run the local refund proof without an account or API key.
Run the retry demo in your browser.

Inspect the code and failure tests →
The ledger is execution authority. Changing, deleting, replacing, resetting or rolling back valid state may permit an effect to execute again. The same filesystem path does not prove the same execution history. Fresh provisioning is separate from continuation; restore the original consistent history, never initialize replacement state on restart. UNKNOWN blocks redispatch but cannot repair lost history. Compatibility, backup and detection limits.

When you do not need Once

If a provider idempotency key completely solves your operation, use it. If a database unique constraint solves it, use it. Harmless repeated actions do not need Once.

Use Once when a consequential operation may already have happened, you cannot safely establish its outcome, and repeating it could create another effect. Keep native idempotency wherever available.

See two unprotected refunds versus one protected refund →

What Once changes

Your caller can retry.
The write needs a safe decision.

A retry library schedules another attempt. Once controls whether a protected consequential action is dispatched, replayed or blocked.

CONFIRMED

Reuse the original result.

The same logical identity and input can return a durably confirmed result without another protected dispatch.

CONFLICT

Stop changed input.

A changed amount, recipient or title under the same identity is rejected instead of silently becoming a different action.

UNKNOWN

Keep uncertainty safe.

If the outcome cannot be established, another dispatch stays blocked. Provider-specific truth can confirm the original result.

See the decisions

Retry it. Change it. Lose the response.

Explore the confirmed path first, then the harder case: a provider commits the action but its acknowledgement never arrives.

INTERACTIVE SIMULATION

Ready

No account. No checkout. No external writes.

Without protection · blind retry

  1. The action commits; acknowledgement is lost.
  2. A blind retry can create a second ticket.
—Simulated effects for this intent

With Once · protected boundary

  1. Known success can be replayed.
  2. Unknown outcome blocks another dispatch.
—Simulated effects for this intent
NOT STARTED

Use the controls to explore confirmed replay, changed-input conflict and uncertain outcomes.

This browser simulation illustrates Once’s documented behavior. It does not run the SDK, persist execution state or prove external-provider durability. Inspect reproducible evidence or verify the installed SDK.

Does Once fit this action?

Start where doing it twice would matter.

Evaluate Once when all four conditions apply. Search, reads and generation-only work generally do not need this protection.

Payments, bookings, messages and tickets are use cases, not a promise that every provider is automatically supported. Check provider boundaries.

Choose your integration

What do you want to protect?

Start with one action. Choose the route that matches your application and deployment.

First value, locally

Install is the beginning.
Verification is the goal.

The local quickstart uses the actual SDK with a controlled fake ticket provider. Verify one effect, replay, changed-input conflict, a fresh-process restart and lost-acknowledgement blocking.

npm install @once-agent/sdk@0.1.25

Natural Placement in SDK 0.1.25: wrapTool(callback, semantics) wraps a host-owned tool callback with stable logical identity, complete effect binding and authoritative reconciliation. UNKNOWN blocks redispatch.

Node.js 24.15+ · Durable state on one machine · No Once API key · Opaque internal retries and connector-only model access are outside the boundary.

Trust through evidence

Inspect the boundary. Reproduce the result.

Open source under MIT. Evidence is scoped to its provider, state backend and failure model. A simulated lab is not a production adoption claim.

Installed SDK verification

Run the quickstart and inspect a separate effect journal, including across fresh processes.

Run it yourself →

LangGraph checkpoint replay evidence

Local LangGraph JS replay: 2 unprotected provider effects, 1 with Once. Same-machine shared SQLite only; not Cloud or the ~180s sweeper, and no universal exactly-once claim.

Inspect the proof and voluntary Cloud test →

Published source

Current TypeScript SDK: 0.1.25. Historical evidence stays labelled and pinned.

View releases →

Safety boundaries

Simple to use. Explicit about what is protected.

Identity is your intent.

Keep one identity across retries. Use a different identity for a genuinely new action and bind every effect-bearing field.

Durability has a scope.

Local protection needs a persistent shared SQLite file on one machine. Separate hosts, ephemeral storage and network filesystems are outside that boundary.

Unknown can block progress.

Local mode does not redispatch an ambiguous write even after an absence observation. Recovery needs a supported provider contract; confirmed truth can be reconciled.

Keep provider-native idempotency. Route every consequential call through the protected boundary. Installing MCP or finding a candidate does not activate protection. Once does not promise universal exactly-once execution.
Understand hosted data and credential handling →

Questions before you integrate?

Why not just use an idempotency key?

Keep provider-native idempotency wherever available. Once adds a caller-side logical action boundary, durable result handling and explicit uncertainty decisions. Recovery still depends on the integration and downstream guarantees.

Why not store the result in my database?

A stored result helps replay known success. The hard case is the gap between the external effect committing and the result reaching your database. A local database transaction cannot atomically cover an arbitrary external provider. Once preserves that uncertainty instead of guessing.

What happens after a process restart or a Once crash?

Local protection recognises confirmed actions if every cooperating caller uses the same durable SQLite file on one machine. An interrupted claim can report IN_FLIGHT until its lease expires, then UNKNOWN; it is not blindly redispatched. Separate hosts and network filesystems are outside this local boundary.

What if the provider succeeded but the response was lost?

Once can leave the action UNKNOWN and block another dispatch. An authoritative, read-only provider lookup can confirm the original result where supported. If truth is unavailable, progress can stay blocked. In conservative local mode, reporting absence does not automatically permit redispatch after ambiguity.

What if the amount, recipient or other input changes?

Bind every effect-bearing field to the logical identity. Reusing that identity with a different supported payload raises CONFLICT rather than silently replaying an old result or sending a changed write.

Can Once suppress a genuinely new action?

Yes, if your identity collapses two different intentions into one. Use a stable ID for the same intentional action across retries, and a distinct ID for each genuinely new action. Scope it by tenant and action type. Do not mint a new ID merely to bypass UNKNOWN.

Where is state stored, and is an API key required?

Local TypeScript protection uses a durable same-machine SQLite file and needs no Once API key. Hosted execution sends the operation ID, provider alias and action payload to the Once service and requires authentication and provider setup. Choose the path explicitly.

Does Once add latency?

Local paths add state access; reconciliation adds a provider lookup when needed. Hosted paths also add network work. There is no universal latency promise. Measure the actual deployed integration; documented lab measurements are not production SLAs.

Can it automatically protect any function?

Automatic discovery and classification have explicit boundaries. Stable business identity and complete effect semantics still need trustworthy bindings. Unsupported call shapes fail closed or need reviewed integration.

What does ADAPTER_REQUIRED mean?

The detected operation needs provider-specific integration beyond the automatic transformer. Use the documented local-function or connected-tool route when suitable, or implement and validate an authoritative provider adapter. Detection alone is not protection.

Does installing MCP protect all my tools?

No. The MCP/plugin helps assess and configure a project. The runtime must be wired into the consequential call path. Exposing the original unprotected tool alongside its wrapper bypasses that boundary.

What happens if the hosted Once service is unreachable?

The SDK uses bounded retries, then raises a network or timeout error instead of silently dispatching the protected write directly. Do not handle that error by bypassing Once.

From understanding to a protected action

Protect one action.
Prove it survives a retry.

Want updates after you try it? Follow the public release notes.