Getting Started
Your First Loop
1) Design the loop before writing code
Start with outcome first:
- Outcome ID:
expense_approved - Value unit:
expense_approved - States:
SUBMITTED -> UNDER_REVIEW -> APPROVED(orREJECTED) - Actors:
system,automation,human
1SUBMITTED -> UNDER_REVIEW -> APPROVED2 \-> REJECTED2) States and transitions
StateSpec fields from @loop-engine/core:
id(required)isTerminal?: boolean(default false)isError?: boolean(default false)
Terminal states close the loop (status: "completed").
Error states set status: "failed".
3) Actors
Loop Engine supports 5 actor types:
humanautomationai-agentwebhooksystem
Actor authorization is checked against each transition's allowedActors.
See The Actor Model for full details.
4) Start and run the loop
1"cmt">// @no-typecheck2import { aggregateId, transitionId } from '@loop-engine/core'3import { LoopBuilder, createLoopSystem } from '@loop-engine/sdk'4 5const approval = LoopBuilder6 .create('expense.approval', 'finance')7 .description('Expense report approval')8 .state('SUBMITTED')9 .state('UNDER_REVIEW')10 .state('APPROVED', { isTerminal: true })11 .state('REJECTED', { isTerminal: true })12 .initialState('SUBMITTED')13 .transition({14 id: 'start_review',15 from: 'SUBMITTED',16 to: 'UNDER_REVIEW',17 actors: ['automation']18 })19 .transition({20 id: 'approve',21 from: 'UNDER_REVIEW',22 to: 'APPROVED',23 actors: ['human'],24 guards: [25 {26 id: 'approval_obtained',27 severity: 'hard',28 evaluatedBy: 'runtime',29 description: 'Approval must be present',30 failureMessage: 'Approval not obtained'31 }32 ]33 })34 .transition({35 id: 'reject',36 from: 'UNDER_REVIEW',37 to: 'REJECTED',38 actors: ['human']39 })40 .outcome({41 id: 'expense_approved',42 description: 'Expense report approved',43 valueUnit: 'expense_approved',44 businessMetrics: [45 { id: 'm_expense_approved', label: 'Expense report approved', unit: 'count' }46 ]47 })48 .build()49 50const aggregate = aggregateId('EXP-2026-001')51const { engine } = await createLoopSystem({ loops: [approval] })52 53await engine.start({54 loopId: 'expense.approval',55 aggregateId: aggregate,56 actor: { type: 'system', id: 'system:intake' }, "cmt">// StartOptions.actor57 metadata: { source: 'expense_form' } "cmt">// optional application correlation (neutral keys only)58})59 60const reviewResult = await engine.transition({61 aggregateId: aggregate,62 transitionId: transitionId('start_review'),63 actor: { type: 'automation', id: 'system:router' }64})65 66const approveResult = await engine.transition({67 aggregateId: aggregate,68 transitionId: transitionId('approve'),69 actor: { type: 'human', id: 'manager@acme.com' },70 evidence: { approved: true, comment: 'Within policy' }71})