Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a028de-e2c8-7732-8521-46a7fc5db82f
2.9 KiB
Idempotency Contract
Workflow ID strategy
Every RunActivityWorkflow execution has a deterministic, stable workflow ID.
This is the primary idempotency mechanism — Temporal rejects duplicate starts
with the same workflow ID in the same namespace.
| Trigger type | Workflow ID format |
|---|---|
| Cron / scheduled | activity-{activity_id}:{scheduled_for.isoformat()} |
| External event | activity-{activity_id}:{event.event_id} |
Example (cron): activity-550e8400-e29b-41d4-a716-446655440000:2026-03-01T09:00:00+00:00
Example (event): activity-550e8400-e29b-41d4-a716-446655440000:evt_abc123
Both formats guarantee that re-delivering a trigger (broker retry, duplicate schedule fire) produces at most one workflow execution.
Misfire policy
A misfire occurs when a scheduled trigger fires after its nominal time because the worker was down, overloaded, or the schedule was paused.
| Policy | trigger.misfire_policy value |
Behaviour |
|---|---|---|
| Skip | "skip" |
Missed runs are discarded. Temporal ScheduleOverlapPolicy.SKIP. |
| Catch up | "catchup" |
Missed runs are replayed up to 10 times (configurable). Uses schedule.handle.backfill(). |
| Compress | "compress" |
One run is executed with a widened context window covering all missed intervals. |
trigger.misfire_policy is the runtime control. The persisted
dedupe_key_strategy field is legacy API/DB compatibility metadata; schedule
execution does not read it. In particular, dedupe_key_strategy: skip does
not suppress a later scheduled fire whose resolved context or emitted task
content matches an earlier fire. New definitions should configure only the
trigger policy and should not rely on dedupe_key_strategy for content
deduplication.
Each nominal cron time produces a different workflow ID. If an hourly rule sees the same unread message on consecutive hours, both hours may emit work. That requires an explicit domain idempotency key or consumer-side state transition; it is not a Temporal misfire concern.
Event deduplication
For event-driven activities:
- The Event Router computes the workflow ID as
activity-{activity_id}:{event.event_id}. - It calls
client.start_workflow(..., id=workflow_id, id_reuse_policy=REJECT_DUPLICATE). - If the workflow ID already exists (event was already processed), Temporal returns
WorkflowAlreadyStartedError— the router logs it and moves on.
Prerequisite: every inbound event must have a stable event_id. Events without a
stable ID must be assigned one by the ingress boundary before entering the system.
Database idempotency
activity_runs uses run_id as the primary key (UUID). The log_run activity
uses an upsert (INSERT ... ON CONFLICT DO NOTHING) so that Temporal activity retries
do not produce duplicate run records. Task emission idempotency uses the
consumer reference recorded in task_spawn_log; runtime delivery uses the
unique ops_runs.idempotency_key.