Assume an event can arrive more than once

Imagine a workflow that receives a support request, asks an AI model to draft a reply and then sends it for review. The event that starts the workflow might be delivered twice. A service may retry after a timeout, a worker may restart, or someone may replay an event while troubleshooting. If both runs reach the final step, the workflow could create two review tasks or send two messages.

The goal is not to make duplicate delivery impossible. It is to make repeated delivery safe. This is called idempotency: performing the same intended action again should not create another effect. In an AI workflow, that protection belongs around actions such as sending, publishing, charging or creating records. Asking a model to “avoid duplicates” is not a reliable substitute for a check in the workflow.

Give each intended action a stable key

Start by defining what counts as the same action. If the event source supplies a stable event ID, record it. Then create an action key that also identifies the step, such as the event ID combined with “create-review-task.” This keeps two different steps triggered by one event from blocking each other.

An event ID is not always enough. Two events may describe the same underlying request, or one event may legitimately call for two actions. Choose a key based on the outcome you want to protect. For example, a review task might be unique for a particular request and review stage, while a weekly summary might be unique for a team and reporting period. Write down when a new action should be allowed, including what should happen if a person intentionally asks for a rerun.

Avoid building the key from the AI-generated draft. Model output may change between runs even when the request has not. If a key contains personal or confidential information, store a non-reversible representation where appropriate and limit access to the underlying event data.

Record the action before doing the work

A simple “check whether this key exists, then insert it” process has a race condition: two workers can both pass the check before either inserts a record. Instead, use a durable store that can enforce a unique key. Have each worker attempt to claim the action with that key. Only the worker whose claim succeeds proceeds; the other can inspect the existing record and stop or return the earlier result.

Track enough state to explain what happened, such as pending, in progress, completed or needing attention. Save the key, the intended action, an external reference if one is returned, and an error summary if a step fails. Keep the record for as long as duplicate delivery or replay remains a realistic possibility. An in-memory flag is not sufficient if workers can restart or run in parallel.

Claiming an action is only part of the solution. A worker might create a task and crash before marking its record completed. On retry, the workflow must determine whether the task already exists rather than assuming the failed record means nothing happened.

Handle the gap between your records and external tools

When an external service accepts an idempotency key, pass the same stable action key on every retry. Where possible, store the external service’s task or message ID after success. If the service has no such feature, check whether you can search for a record bearing your key before creating another. A timeout is especially ambiguous: the request may have succeeded even though your workflow never received the response.

For important actions, separate deciding what to do from delivering it. Save the proposed action and its key in a durable queue or outbox, then let a worker process that record. This reduces the chance that two model runs independently decide to send the same thing. It does not, by itself, guarantee that an external side effect happens exactly once. If you cannot verify the outcome of an ambiguous attempt, pause for human review rather than retrying an irreversible action blindly.

AI output can still be regenerated when needed, but keep that separate from authorization to act. A revised draft may replace an earlier draft without creating a second send action. Decide explicitly whether a later change deserves a new action key.

Test the failure cases, not just the happy path

Send the same event twice and confirm that only one protected action occurs. Then try two simultaneous deliveries, a retry after a timeout, and a worker restart immediately after the external tool succeeds. Check what the workflow records and what a teammate would see when investigating each case.

Document the intended behavior in a short rule: what makes an action unique, how long its record is retained, when a retry is safe, and who resolves an uncertain outcome. Those decisions make duplicate protection maintainable as prompts, models and connected tools change. The practical aim is not a perfect claim that an event can run only once; it is a workflow that can recognize a repeat and avoid repeating its consequences.

Back to category ↗