Skip to content

Conversation Context ​

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

Aggregate Tracking ​

Remember that when you write machine: @order, you define a template. At runtime, many instances (aggregates) exist:

@order (template)
  ├── @order:abc123 (Customer A's order)
  ├── @order:xyz789 (Customer B's order)
  └── @order:def456 (Customer C's order)

When @order:abc123 sends an event to @payment, and @payment responds, the response must return to the correct instance (@order:abc123), not to some other order.

EventFlow solves this automatically through conversation context - you don't manage correlation IDs manually.

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_requestconversation_id: conv-001from: @order:abc123:payment_successconversation_id: conv-001 (auto-routed)

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 (api)
    // 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 (api)
  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 (api)
    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.