different-ai/openwork · error

Automation engine event sequence is not contiguous

Error message

Automation engine event sequence is not contiguous

What it means

accept() enforces a strictly contiguous sequence: each accepted event's sequence must equal the current cursor + 1. This detects lost or reordered events during replay. A gap (skipped number) means events were dropped between the last accepted event and this one.

Source

Thrown at packages/automations/src/engine.ts:231

  afterSequence = 0,
): AutomationEngineEventSequenceValidator {
  const receipt = automationEngineAdmissionReceiptSchema.parse(rawReceipt)
  if (!Number.isSafeInteger(afterSequence) || afterSequence < 0) {
    throw new Error("Automation engine event cursor must be a non-negative integer")
  }
  let cursor = afterSequence
  const eventKeys = new Set<string>()
  return {
    get cursor() {
      return cursor
    },
    accept(rawEvent) {
      const event = automationEngineEventSchema.parse(rawEvent)
      if (event.executionId !== receipt.executionId || event.runId !== receipt.runId) {
        throw new Error("Automation engine event receipt mismatch")
      }
      if (event.sequence !== cursor + 1) {
        throw new Error("Automation engine event sequence is not contiguous")
      }
      if (eventKeys.has(event.idempotencyKey)) {
        throw new Error("Automation engine event idempotency key was repeated")
      }
      eventKeys.add(event.idempotencyKey)
      cursor = event.sequence
    },
  }
}

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Replay the event stream from the correct checkpoint (afterSequence matching the last accepted event)
  2. Ensure the event source preserves ordering and delivers every event exactly once
  3. If a gap is expected (trimmed log), rebuild the validator from the earliest available sequence instead of mid-stream

Example fix

// before
validator.accept(nextEvent) // sequence 5 while cursor is 3
// after
if (nextEvent.sequence === validator.cursor + 1) validator.accept(nextEvent)
else resumeStreamFrom(validator.cursor)
Defensive patterns

Strategy: validation

Validate before calling

if (event.sequence !== validator.cursor + 1) {
  // resync from the source instead of accepting out-of-order
  restartReplayFrom(validator.cursor)
} else {
  validator.accept(event)
}

Type guard

const isNextInSequence = (e: AutomationEngineEvent, cursor: number): boolean => e.sequence === cursor + 1

Try / catch

try {
  validator.accept(event)
} catch (e) {
  if (e instanceof Error && e.message.includes("not contiguous")) {
    await replayFromSource(validator.cursor) // refill missing events then continue
  } else throw e
}

Prevention

When it happens

Trigger: Feeding accept() an event whose sequence !== cursor + 1 — skipping a number (event lost), replaying the same sequence twice, or starting the stream at sequence > afterSequence + 1.

Common situations: Reading events from a log that compacts/trims old entries; concurrent writers with gaps; resuming with the wrong afterSequence checkpoint; events published to a lossy transport.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/91d53bfc177e448b. Report an issue: GitHub.