Field note

Scheduling AI Workflows Without Duplicate Actions

Run scheduled AI workflows that never act twice: separate schedules from runs, checkpoint state, use idempotency keys, classify retries, reconcile uncertain outcomes and test failures.

Contents

A train station departure board hanging beside a clock

Image: William Dmytrow on Unsplash.

A scheduled AI workflow is safe when running it twice, or stopping it halfway, cannot repeat an external action. Separate the schedule from each run, record progress in durable state, give every external action an idempotency key, and when a call's outcome is uncertain, check the world before retrying.

Here is the failure that matters.

The workflow sends the weekly update.

The provider accepts it.

The process crashes before recording success.

On restart, a naive retry sends it again.

Starting a job on time is easy. Knowing whether it already happened is the hard part. This guide is part of the AI harness engineering guide.

Separate a schedule from an execution

Keep three identities distinct:

Thing Example Identity
Job "Weekly team update, Mondays 09:00" job_weekly_update
Run The run for Monday 20 October job_weekly_update@2026-10-20
Attempt The second try at that run job_weekly_update@2026-10-20#2

The run identity comes from the job and its scheduled time, not from when it happened to start. Two deliveries of the same trigger then share one run ID, which makes duplicates detectable.

Decide in advance:

  • Time zone. Store schedules with an explicit zone and test daylight-saving changes.
  • Missed runs. If the machine was off at 09:00, run late, skip, or ask?
  • Overlap. If last week's run is still going, wait, skip or cancel it?

Design the execution state machine

Every run moves through a small set of states:

State Meaning
pending Scheduled, not started
running In progress, with a checkpoint
awaiting_approval Paused for you to approve an exact action
uncertain An external call may or may not have succeeded
succeeded Final state verified
failed Stopped with a recorded reason
cancelled Stopped by you; completed effects listed

Checkpoint what is needed to resume, not the whole conversation: completed steps, effect IDs, pending approvals. Anthropic's work on long-running agent harnesses makes the same point for coding agents, keeping a progress file and clean artifacts so each session can resume incremental work (Anthropic).

succeeded means verified, not claimed. If the run drafted an update, confirm the draft exists.

Make duplicate delivery safe

An idempotency key is a unique identifier for one intended effect. If the same key arrives twice, the receiver performs the effect once.

Build the key from what should be unique:

key = job_id + scheduled_time + step + target
    = "job_weekly_update|2026-10-20T09:00+01:00|send|team-list"

Then:

  • If the downstream API supports idempotency keys, pass yours and let it deduplicate.
  • If it does not, record the key and the resulting effect ID locally before reporting success, and check for an existing effect before acting.
  • If neither is possible, treat the step as non-idempotent: never retry it automatically.

Be honest about scope. Your key prevents duplicates the system can see. It cannot make an external service that lacks deduplication behave exactly once.

Retry the right failures

Not every error deserves another try.

Failure Retry?
Network timeout before the request was sent Yes, with backoff
Provider rate limit or temporary error Yes, with backoff and a cap
Invalid input or schema error No. Fix the input
Permission denied or approval missing No. Stop and report
Timeout after the request may have been accepted Not yet. Mark uncertain and reconcile
Budget exhausted No. Stop visibly

Cap attempts, total time and spend. Every final failure records its reason.

Reconcile uncertain outcomes

When an action's outcome is unknown, look before you leap:

  1. Query the downstream system for an effect matching the idempotency key, or the expected content and time window.
  2. If found, record the effect ID and mark the step done.
  3. If clearly absent, retry once under the same key.
  4. If you cannot tell, stop and ask a person.

Asking is a correct outcome. For a consequential, non-idempotent action, a duplicate is worse than a delay.

Handle approval expiry and cancellation

Approvals expire. If a run waits for approval and resumes the next day, check the approval again. Do not revive yesterday's consent. The permissions guide covers exact-action approval.

Cancellation stops new work. It cannot undo completed actions. When cancelled, list what already happened, with effect IDs, and what did not.

Inject failures around the side effect

Test the dangerous moments deliberately, in a sandbox:

Test Interrupt the process Expected
Before the call Just before sending Resume sends once
During the call While the request is in flight Run becomes uncertain, reconciliation decides
After the call After acceptance, before recording Reconciliation finds the effect; no second send
Duplicate trigger Deliver the same schedule event twice One run, one effect
Restore Restore the database from before the send Reconciliation finds the effect; no second send

The last row matters: restoring a backup rolls back your records, not the outside world. The backup and restore runbook builds on this.

Check final state in every test. Count the messages in the sandbox inbox; do not trust the log alone.

Operate the schedule over time

Watch a handful of signals:

runs stuck in running or uncertain,
jobs failing repeatedly,
runs skipped because of overlap,
approvals that expire unanswered.

Send these to a person rather than letting retries absorb them. The logging guide covers the receipts these signals come from, and the troubleshooting guide covers diagnosis.

Frequently asked questions

Why can a scheduled job run twice?

Schedulers, queues and restarts can deliver the same trigger more than once, and a crash can hide whether an action already happened.

Are retries always safe?

No. Retry only failures that are known not to have produced an effect, or effects protected by an idempotency key.

What happens if the tool succeeds but the process crashes?

The run becomes uncertain. On restart, reconcile by checking the downstream system before doing anything else.

Can a cancelled job undo an action it already completed?

No. Cancellation stops future steps. Completed external actions stay done and should be listed.

AI WorkflowSchedulingIdempotencyReliabilityAI Harness Engineering