Skip to content

Conversation Context

When machines communicate, the system automatically maintains a conversation context that enables response routing without explicit correlation keys.

The Problem

When @order sends an event to @payment, and @payment responds, how does the response know which specific order instance to return to?

flow
// @order emits to @payment
emit :payment_request to @payment

// Later, @payment responds
emit :payment_success to @order   // But WHICH order?

The Solution: Automatic Correlation

EventFlow automatically maintains conversation context. You don't need to manage correlation IDs.

How It Works

┌─────────────────────────────────────────────────────────────┐
│                    Conversation Flow                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  @order:abc123                      @payment:xyz789         │
│       │                                  │                  │
│       │ ─── :payment_request ──────────> │                  │
│       │     conversation_id: conv-001    │                  │
│       │     from: @order:abc123          │                  │
│       │                                  │                  │
│       │ <── :payment_success ─────────── │                  │
│       │     conversation_id: conv-001    │                  │
│       │     (auto-routed to @order:abc123)                  │
│       │                                  │                  │
└─────────────────────────────────────────────────────────────┘

Event Metadata

When you write:

flow
emit :payment_request to @payment

The runtime automatically adds metadata:

json
{
  "event": ":payment_request",
  "conversation_id": "conv-001",
  "from": {
    "machine": "@order",
    "aggregate_id": "order-abc123"
  },
  "to": {
    "machine": "@payment"
  },
  "data": { ... }
}

When @payment responds:

flow
emit :payment_success to @order

The runtime uses the conversation context:

json
{
  "event": ":payment_success",
  "conversation_id": "conv-001",
  "from": {
    "machine": "@payment",
    "aggregate_id": "payment-xyz789"
  },
  "to": {
    "machine": "@order",
    "aggregate_id": "order-abc123"
  }
}

Key Point: EventFlow authors don't need to manage correlation keys - the runtime handles it automatically.

Complete Example

flow
machine: @order

scenario: checkout flow

  on> :checkout from @customer
    // New aggregate created: order-abc123
    order moves to #awaiting_payment
    emit :payment_request to @payment      // Starts conversation

  on :payment_success from @payment        // Same conversation
    // Automatically routed to order-abc123
    order moves to #paid
    emit :reserve_stock to @inventory      // New conversation branch

  on :stock_reserved from @inventory       // Returns via conversation
    order moves to #fulfilled

machine: @payment

scenario: payment processing

  on :payment_request from @order
    // First event for @payment → new aggregate: payment-xyz789
    // Conversation context links back to order-abc123
    process card
    ? card is valid
      emit :payment_success to @order      // Returns to originator
    ?
      emit :payment_failed to @order

machine: @inventory

scenario: stock management

  on :reserve_stock from @order
    // First event for @inventory → new aggregate: inventory-456
    // Conversation context links back to order-abc123
    ? stock is available
      reserve items
      emit :stock_reserved to @order       // Returns to originator
    ?
      emit :out_of_stock to @order

Multiple Conversations

A single aggregate can participate in multiple conversations simultaneously:

flow
on> :checkout from @customer
  order moves to #processing
  emit :payment_request to @payment        // Conversation A
  emit :fraud_check to @fraud              // Conversation B
  emit :reserve_stock to @inventory        // Conversation C

on :payment_success from @payment          // Response to A
  $payment_ok becomes true
  ? $payment_ok and $fraud_ok and $stock_ok
    order moves to #confirmed

on :fraud_cleared from @fraud              // Response to B
  $fraud_ok becomes true
  ? $payment_ok and $fraud_ok and $stock_ok
    order moves to #confirmed

on :stock_reserved from @inventory         // Response to C
  $stock_ok becomes true
  ? $payment_ok and $fraud_ok and $stock_ok
    order moves to #confirmed

Each emit starts a separate conversation, and responses are correctly routed back to the originating aggregate.

Conversation Branching

Conversations can branch when one machine emits to multiple others:

@order:abc123

    ├── emit :payment_request ──────> @payment:xyz789
    │                                      │
    │   <── :payment_success ──────────────┘

    ├── emit :fraud_check ──────────> @fraud:check-001
    │                                      │
    │   <── :fraud_cleared ────────────────┘

    └── emit :reserve_stock ────────> @inventory:res-001

        <── :stock_reserved ───────────────┘

All three responses are routed back to @order:abc123.

Nested Conversations

A machine handling a request can start new conversations:

flow
machine: @order

  on> :checkout from @customer
    emit :payment_request to @payment
    order moves to #awaiting_payment

machine: @payment

  on :payment_request from @order
    // @payment starts its own conversation with @bank
    emit :authorize_card to @bank
    payment moves to #authorizing

  on :card_authorized from @bank
    // Can still respond to original @order
    emit :payment_success to @order

The conversation chain is preserved through all levels.

Manual Correlation (When Needed)

In rare cases where you need explicit correlation (e.g., calling external APIs), you can include IDs in event data:

flow
emit :external_api_call to @gateway
  with:
    | order_id    | $order_id    |
    | callback_id | $callback_id |

But for internal machine-to-machine communication, automatic correlation is preferred.

Conversation Lifecycle

  1. Start - First emit from an aggregate creates conversation context
  2. Propagate - Context is passed along with each event
  3. Branch - Multiple emits create parallel conversation branches
  4. End - Conversations end when processing completes (no explicit close)

Benefits

  • No boilerplate - No correlation ID passing in EventFlow code
  • Automatic routing - Responses find their way back automatically
  • Multiple conversations - Handle parallel flows naturally
  • Nested support - Works across multiple levels of machine interaction
  • Audit trail - Conversation IDs provide traceability

Released under the MIT License.