IntentCorrelation & Causation

Correlation & Causation

Every committed event links back to the intent that produced it. That link is causation — which intent caused this event. When one user action fans out across many intents and entities, you also need correlation — a shared ID that ties the whole chain together.


Causation: which intent caused this?

Causation answers: which intent produced this event?

The runtime records the intent ID on every ledger commit. If you see an unexpected ARTIST_FOLLOWED event, look up that intent ID to find the original FOLLOW_ARTIST submission — input payload, outcome, and timestamp.

In the control plane:

  • Intent status — final result for a given intent ID
  • Decision Log — how rules evaluated for that intent
  • Ledger history — patch and emits at the commit cursor

Causation is always available. You do not declare anything extra in the DSL for a single intent → event path.


Correlation: which user request started the chain?

Correlation answers: which top-level request produced this entire cascade?

One purchase might produce:

TICKET_PURCHASED → INVENTORY_DECREMENTED → CONFIRMATION_EMAIL_QUEUED → REVENUE_RECORDED

Each step may be a different intent on a different entity. They share a common origin — the user’s purchase request — but the runtime does not automatically assign a cross-chain correlation ID. You pass one explicitly when you need end-to-end traceability.

ConceptScopeBuilt-in?
CausationOne intent → its eventsYes — intent ID on every commit
CorrelationMany intents → one user requestNo — add to payloads when needed

Passing correlation IDs in payloads

For multi-hop flows, declare correlation_id and optionally caused_by on your events:

events:
  TICKET_PURCHASED:
    state: ticket
    entity_expr: event.ticket_id
    payload:
      ticket_id:      string
      show_id:        string
      user_id:        string
      price_cents:    int
      correlation_id: string   # top-level request ID
      caused_by:      string   # intent that triggered this
 
  CONFIRMATION_EMAIL_QUEUED:
    state: notification
    entity_expr: event.notification_id
    payload:
      notification_id: string
      user_id:         string
      ticket_id:       string
      correlation_id:  string  # propagated from parent
      caused_by:       string  # intent ID of the purchase

Propagate from the submitting intent:

actions:
  PURCHASE_TICKET:
    state: show
    entity_id_expr: intent.show_id
    input:
      show_id:        { type: string, required: true }
      user_id:        { type: string, required: true }
      ticket_id:      { type: string, required: true }
      correlation_id: { type: string, required: false }
    side_effects:
      rules:
        - name: emit_purchase
          when: {}
          then:
            - op: emit
              event_type: TICKET_PURCHASED
              payload:
                ticket_id:      intent.ticket_id
                show_id:        intent.show_id
                user_id:        intent.user_id
                price_cents:    intent.price_cents
                correlation_id: intent.correlation_id

The caller supplies correlation_id at the top of the chain (HTTP request ID, client-generated UUID, etc.). Each downstream intent and event copies it forward.

When side_effects submit a follow-up intent, set caused_by to the current intent’s ID so the parent-child link is visible in event payloads without manual ledger inspection.


What gets stored

Stored asWhat it is
Intent IDCausation — always on every commit and in intent status
Intent typeAction name (e.g. PURCHASE_TICKET)
Event typeDomain event name (e.g. TICKET_PURCHASED)
PayloadYour declared fields, including optional correlation_id and caused_by
CursorPosition on the entity timeline for this commit
TimestampWhen the commit was recorded

Envelope fields like event type and timestamp are automatic. Correlation across multiple intents is only present if you put it in the payload.


Why it matters

Debugging projection failures. Failure records include event type and payload. A correlation_id in the payload links a bad projection row back to the originating user request.

Tracing fan-out chains. A FOLLOW_ARTIST intent may trigger listeners, side effects, and downstream submits. Without correlation metadata, you follow the chain hop by hop via intent IDs. With a shared correlation_id, filter once in the Decision Log, projections, or logs.

Cross-entity causation. Intent A emits event A, which triggers intent B. The ledger ties event A to intent A; caused_by in event B’s payload makes the A → B relationship explicit in your domain data.


How to trace a chain

By causation (single intent)

  1. Start from an event or entity state change in the control plane.
  2. Note the intent ID on that commit.
  3. Open intent status and the Decision Log for that ID — see input, rule steps, and outcome.

By correlation (multi-intent)

  1. Ensure your flows declare and propagate correlation_id.
  2. Filter projection tables or application logs on that value.
  3. Optionally materialize correlation_id in a projection for query-service lookups.

See Debugging for control plane forensics.


Best practices

Include correlation_id for complex flows. When one user action spans multiple entity types and intents, propagate a stable string through the entire chain.

Set caused_by on submitted follow-up intents. When side effects submit another intent, record which intent triggered it.

Project correlation for read-side tracing. Derive correlation_id into a projection if dashboards or support tools need to search by request ID.

Don’t over-engineer simple flows. Single-entity actions with one or two events — intent ID alone is enough for debugging.


See also

  • Debugging — Decision Log, ledger commits, intent status
  • Idempotency — retry-safe intent design
  • Workflowssubmit and multi-intent chains
  • Events — emitting and payload design