Chapter 25 · Volume III — The Intelligence Layer

Declarative Workflows

25.1 Overview

A workflow is a named, multi-step process that coordinates actions across multiple resources in response to events. Workflows are declared at the domain level because they span resources. They are the orchestration layer — the part of a business process that connects one resource’s lifecycle to another.

State Machines vs Workflows

DimensionState Machine (§12)Workflow
ScopeSingle resourceCross-resource
TriggerDirect API call (transition endpoint)Event from any resource
ActorHuman initiates each transitionRuntime orchestrates automatically
StateStored on the resource recordStored in the workflow instance table
BranchingNo (one transition at a time)Yes (decide, parallel, forEach)
WaitingNo (transitions are synchronous)Yes (wait for events with timeout)
Data flowRequest body onlyAccumulated from all prior steps
ConcurrencyNoneParallel steps with join semantics

State machines define what a single resource can do. Workflows define what happens across resources when something happens. A workflow typically triggers state machine transitions on multiple resources — the state machine enforces the guards, the workflow provides the choreography.


25.2 Declaration

Workflows are declared in the domain-level workflows: block:

domain:
  name: lending
  version: "2.0.0"

  workflows:
    loanOrigination:
      description: "End-to-end loan origination from application through funding"
      trigger:
        resource: LoanApplication
        event: transition
        transition: submitForReview

      steps:
        - name: orderCreditReport
          action: create
          resource: CreditReport
          input:
            applicationId: $trigger.record.id
            borrowerName: $trigger.record.borrowerName
          then: evaluateCredit

        - name: evaluateCredit
          wait:
            resource: CreditReport
            event: transition
            transition: complete
            filter:
              applicationId: $trigger.record.id
          condition: "$event.record.creditScore >= 680"
          then: orderAppraisal
          else: routeToManualReview

        - name: orderAppraisal
          action: create
          resource: AppraisalOrder
          input:
            applicationId: $trigger.record.id
            propertyAddress: $trigger.record.propertyAddress
          then: awaitAppraisal

        - name: awaitAppraisal
          wait:
            resource: AppraisalOrder
            event: transition
            transition: complete
            filter:
              applicationId: $trigger.record.id
          then: routeToUnderwriting

        - name: routeToManualReview
          action: transition
          resource: LoanApplication
          target: $trigger.record.id
          transition: flagForReview
          then: end

        - name: routeToUnderwriting
          action: transition
          resource: LoanApplication
          target: $trigger.record.id
          transition: completeCreditAnalysis
          input:
            creditScore: $steps.evaluateCredit.event.record.creditScore
            appraisalValue: $steps.awaitAppraisal.event.record.appraisedValue
          then: end

      timeout:
        duration: 30d
        action: transition
        resource: LoanApplication
        target: $trigger.record.id
        transition: expire

25.3 Trigger

The trigger block defines what event starts the workflow:

trigger:
  resource: LoanApplication
  event: transition
  transition: submitForReview
  condition: "$trigger.record.amount > 50000"
PropertyTypeRequiredDescription
resourcestringyesThe resource whose event triggers the workflow
eventstringyesEvent type: create, transition, fieldChange, history
transitionstringif event is transitionSpecific transition name
fieldstringif event is fieldChangeSpecific field name
conditionstringnoAEL expression — workflow starts only when true

When the trigger event fires and the condition (if any) is met, the runtime creates a new workflow instance and begins executing at the first step.


25.4 Step Types

Action Step

Performs an operation on a resource:

- name: createInspection
  action: create
  resource: Inspection
  input:
    assetId: $trigger.record.id
    inspectionType: emergency
  then: awaitInspection
ActionDescription
createCreate a new record in the target resource
updateUpdate fields on an existing record
transitionExecute a state machine transition
notifyEmit a bus event for notification consumption
noopDo nothing — used for conditional skip patterns

Action steps execute immediately and advance to the then step.

Wait Step

Pauses the workflow until a matching event occurs:

- name: awaitApproval
  wait:
    resource: Proposal
    event: transition
    transition: approve
    filter:
      id: $trigger.record.id
  timeout:
    duration: 5d
    then: escalateApproval
  then: nextStep
PropertyTypeDescription
wait.resourcestringResource to watch for events
wait.eventstringEvent type to match: transition, create, fieldChange
wait.transitionstringTransition name to match
wait.filterobjectField match criteria for the event record
timeout.durationstringMaximum wait time: 5d, 48h, 30m
timeout.thenstringStep to execute if timeout expires
conditionstringAEL expression evaluated on the matched event
thenstringStep to execute when the event matches and condition is true
elsestringStep to execute when the event matches but condition is false

Wait steps are persisted. The workflow instance is saved to the database and resumed when a matching event arrives on the bus. The server can restart between the wait and the resume without losing workflow state.

Decide Step

Routes the workflow based on conditions:

- name: routeByScore
  decide:
    - condition: "$steps.creditCheck.event.record.creditScore >= 720"
      then: autoApprove
    - condition: "$steps.creditCheck.event.record.creditScore >= 620"
      then: enhancedReview
    - default: true
      then: decline

Conditions are evaluated in order. The first matching condition determines the next step. If no condition matches and no default is declared, the workflow fails with an error.

Parallel Step

Executes multiple branches simultaneously:

- name: complianceChecks
  parallel:
    all: true
    steps:
      - name: irbCheck
        wait:
          resource: IRBProtocol
          event: transition
          transition: approve
          filter:
            studyId: $trigger.record.id

      - name: coiCheck
        wait:
          resource: COIDisclosure
          event: transition
          transition: clear
          filter:
            personnelId: $trigger.record.piId

      - name: exportControlCheck
        wait:
          resource: ExportControlScreening
          event: transition
          transition: clear
          filter:
            proposalId: $trigger.record.id
  then: submitProposal
PropertyTypeDescription
parallel.allbooleanIf true, wait for ALL branches to complete before advancing
parallel.anybooleanIf true, advance when ANY branch completes (cancel the rest)
parallel.stepsarrayBranches to execute in parallel

ForEach Step

Iterates over a collection:

- name: notifyAllReviewers
  forEach:
    collection: $trigger.record.reviewerIds
    as: reviewerId
    step:
      action: create
      resource: ReviewAssignment
      input:
        documentId: $trigger.record.id
        reviewerId: $item
        dueDate: $trigger.record.reviewDueDate
  then: awaitAllReviews

The $item variable refers to the current element. All iterations execute before the workflow advances to then.


25.5 Data Flow

Variable Scopes

VariableDescription
$triggerThe event that started the workflow
$trigger.recordRecord data from the triggering event
$trigger.actorUser who triggered the event
$eventThe event that satisfied the current wait step
$event.recordRecord from the current wait event
$steps.{name}Data from a completed step
$steps.{name}.resultCreated/updated record (for action steps)
$steps.{name}.eventEvent that completed a wait step
$workflowWorkflow-level metadata
$workflow.idUnique workflow instance ID
$workflow.startedAtTimestamp when the workflow began
$itemCurrent item in a forEach loop

Data Accumulation

Each completed step adds its result to the $steps namespace. Later steps can reference data from any earlier step. This enables data flow across the entire process:

input:
  creditScore: $steps.evaluateCredit.event.record.creditScore
  appraisalValue: $steps.awaitAppraisal.event.record.appraisedValue

All variable references are evaluated by the AEL engine.


25.6 Timeout and Error Handling

Step-Level Timeout

- name: awaitApproval
  wait:
    resource: Proposal
    event: transition
    transition: approve
  timeout:
    duration: 5d
    then: escalate

If the wait step does not receive a matching event within duration, the workflow advances to the timeout.then step instead. The timeout step can escalate (notify), retry (loop back to the wait), or terminate the workflow.

Workflow-Level Timeout

timeout:
  duration: 30d
  action: transition
  resource: LoanApplication
  target: $trigger.record.id
  transition: expire

If the entire workflow has not completed within duration, the timeout action executes. This is a safety net — it prevents workflow instances from waiting indefinitely.

onError

onError:
  action: transition
  resource: LoanApplication
  target: $trigger.record.id
  transition: errorState
  notification:
    channels: [email]
    recipients:
      roles: [admin]
    template:
      subject: "Workflow error — loanOrigination"
      body: "Workflow instance {$workflow.id} failed at step {$workflow.failedStep}."

If any step fails with an unrecoverable error, the onError handler executes. It can transition the triggering record to an error state, send notifications, or both.


25.7 Workflow Instance

Each workflow execution creates an instance record:

ColumnTypeDescription
iduuidInstance identifier
workflowNamestringWorkflow definition name
statusstringrunning, waiting, completed, failed, cancelled, timed_out
currentStepstringName of the step currently executing or waiting
triggerDatajsonThe event that started the workflow
stepsDatajsonAccumulated data from completed steps
startedAtdatetimeWhen the instance was created
completedAtdatetimeWhen the instance finished (null if still running)
errortextError message if failed

Instance Lifecycle

Event matches trigger
  → Instance created (status: running)
    → Steps execute sequentially
      → Wait steps pause instance (status: waiting)
        → Matching event resumes instance (status: running)
          → All steps complete (status: completed)

History Events

Every step produces a history event on the workflow instance. The complete step-by-step timeline is queryable:

{
  "event": "stepCompleted",
  "label": "Credit report ordered",
  "data": {
    "step": "orderCreditReport",
    "result": { "id": "credit-report-1" }
  }
}

Steps that modify resources also produce history events on those resources (if history is configured). Together, the workflow instance timeline and resource timelines provide a complete cross-resource process audit trail.


25.8 Workflow API

List Definitions

GET /api/v1/{domain}/workflows

Returns all workflow definitions.

List Instances

GET /api/v1/{domain}/workflows/{name}/instances
    ?status=waiting&since=2026-01-01

Returns instances of a specific workflow with status filtering.

Get Instance

GET /api/v1/{domain}/workflows/{name}/instances/{id}

Returns full instance details including trigger data, current step, steps data, and timeline.

Cancel Instance

POST /api/v1/{domain}/workflows/{name}/instances/{id}/cancel
{ "reason": "Application withdrawn by borrower" }

Cancels a running or waiting instance. The onError handler does NOT fire on cancel — cancellation is intentional, not an error.


25.9 Execution Model

Bus-Driven

Workflows are entirely event-driven:

  1. The runtime subscribes to trigger events on the bus.
  2. When a trigger fires, a workflow instance is created.
  3. Action steps execute immediately.
  4. Wait steps subscribe to specific events on the bus and pause.
  5. When a matching event arrives, the instance resumes at the next step.
  6. The cycle continues until the workflow reaches end or times out.

Persistence

Workflow instances are persisted to the database after each step. If the server restarts, waiting instances are reloaded and their bus subscriptions are re-established. No workflow state is lost on restart.

Concurrency

Multiple instances of the same workflow can run simultaneously. Each instance is independent — they share the workflow definition but have separate state, separate data, and separate timelines.

Rules Integration

Actions performed by workflow steps are subject to the rules engine (§22). If a workflow step attempts a transition that violates a business rule, the rule fires and the step fails. The workflow’s onError handler is invoked.


25.10 The Workflow as Documentation

Workflow definitions serve simultaneously as:

Process documentation. The YAML IS the business process. A business analyst can read it and verify it matches the intended process. No BPMN diagram that diverges from the implementation.

Standard Operating Procedure. In regulated industries, SOPs must be documented, reviewed, and followed. The workflow definition is the SOP — machine-readable, machine-enforceable, and version-controlled.

Audit evidence. The workflow instance timeline shows every step, every decision, every timeout. When an auditor asks “was the process followed for this loan application?” the answer is the instance timeline — not a reconstruction from email threads.


25.11 Integration Points

FeatureHow It Uses Workflows
State Machines (§12)Workflow steps trigger transitions; guards enforce eligibility
History (§21)Step completions produce history events on affected resources
Rules (§22)Workflow actions are subject to business rule enforcement
Notifications (§23)Workflow steps can trigger notifications; wait steps listen for events that notifications may have prompted
Schedules (§24)Scheduled tasks can start workflow instances via action: workflow

End of Chapter 25.

Next: Chapter 26 — Reports, Views & Dashboards