WorkflowsTimers & Scheduling

Timers & Scheduling

Causet supports delayed event emission via op: schedule in side effects rules. This covers common patterns like reminders, expiry checks, and time-windowed follow-ups. For longer or more complex schedules, use an external scheduler to submit intents to Causet at the right time.

op: schedule Syntax

side_effects:
  rules:
    - name: <rule_name>
      when: <conditions>
      then:
        - op: schedule
          event_type: <EVENT_TYPE>
          delay_seconds: <integer>
          payload:
            <key>: <expression>

Fields

FieldRequiredDescription
event_typeYesEvent type to emit after the delay
delay_secondsYesDelay in seconds from intent processing time
payloadNoPayload for the scheduled event

Example: Show Reminder 24 Hours Before Event

side_effects:
  rules:
    - name: schedule_show_reminder
      when:
        - path: /reminder_enabled
          op: eq
          value: true
      then:
        - op: schedule
          event_type: SHOW_REMINDER_SENT
          delay_seconds: 86400
          payload:
            user_id: intent.user_id
            show_id: intent.show_id
            venue: intent.venue

When the intent is processed, the runtime enqueues a SHOW_REMINDER_SENT event to fire 86400 seconds (24 hours) later. That event is committed to the ledger and can advance sagas or trigger further side effects.

Common Scheduling Patterns

Post-Purchase Follow-Up

side_effects:
  rules:
    - name: schedule_followup
      when: {}
      then:
        - op: schedule
          event_type: PURCHASE_FOLLOWUP_TRIGGERED
          delay_seconds: 259200
          payload:
            user_id: intent.user_id
            purchase_id: intent.purchase_id

Stale Session Expiry

side_effects:
  rules:
    - name: schedule_session_expiry
      when:
        - path: /session_active
          op: eq
          value: true
      then:
        - op: schedule
          event_type: SESSION_EXPIRED
          delay_seconds: 3600
          payload:
            session_id: intent.session_id
            user_id: intent.user_id

Determinism

Scheduled events are deterministic: given the same entity state and intent payload, the same events will be scheduled. This matters for replay: if you replay the ledger, the rule logic will schedule the same events again. Ensure your rules guard against duplicate scheduling using entity state:

side_effects:
  rules:
    - name: schedule_reminder_if_not_already_scheduled
      when:
        - path: /reminder_scheduled
          op: eq
          value: false
      then:
        - op: schedule
          event_type: SHOW_REMINDER_SENT
          delay_seconds: 86400
          payload:
            user_id: intent.user_id
            show_id: intent.show_id

And in core, set the guard field when the schedule fires:

core:
  rules:
    - name: mark_reminder_scheduled
      when: {}
      then:
        - op: set
          path: /reminder_scheduled
          value: true

Limitations

Relative Delays Only

delay_seconds is always relative to the time the intent is processed. There is no way to schedule at an absolute timestamp directly in the DSL.

If you need to fire at a specific time (e.g., 30 minutes before show start), calculate the delay in your client before submitting the intent, or use an external scheduler.

No Cancellation

Once a op: schedule fires, the scheduled event cannot be canceled. Use saga state to guard against processing stale scheduled events:

preflight:
  rules:
    - name: reminder_still_relevant
      when:
        - path: /status
          op: neq
          value: "cancelled"
      error:
        code: REMINDER_NOT_RELEVANT
        message: "show was cancelled, ignore reminder"

When the scheduled SHOW_REMINDER_SENT event fires, the action’s preflight rejects it if the show was cancelled in the interim. The event is still written to the ledger (preflight rejects the intent that would process it further), but no state change occurs.

External Scheduling for Complex Cases

For schedules that don’t fit delay_seconds:

  • Absolute timestamps: external scheduler calculates remaining seconds, submits intent at the right time
  • Recurring schedules: cron job or EventBridge rule → submit intent on each occurrence
  • Very long delays (weeks, months): use a durable external scheduler like AWS EventBridge Scheduler, Google Cloud Scheduler, or a database-backed job queue

The entity in Causet doesn’t know or care that the trigger came from an external scheduler. It processes the intent the same as any other.

Checking Scheduled Event Status

Scheduled events appear in the ledger_events table when they fire. You can verify a scheduled event was committed:

SELECT event_id, event_type, entity_id, ts, payload
FROM ledger_events
WHERE event_type = 'SHOW_REMINDER_SENT'
  AND payload->>'show_id' = 'show_xyz'
ORDER BY ts DESC
LIMIT 10;