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: "CLOSED").
Error states set status: "ERROR".
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
1import { aggregateId, transitionId } from '@loop-engine/core'2import { LoopBuilder, createLoopSystem } from '@loop-engine/sdk'3 4const approval = LoopBuilder5 .create('expense.approval', 'finance')6 .description('Expense report approval')7 .state('SUBMITTED')8 .state('UNDER_REVIEW')9 .state('APPROVED', { isTerminal: true })10 .state('REJECTED', { isTerminal: true })11 .initialState('SUBMITTED')12 .transition({13 id: 'start_review',14 from: 'SUBMITTED',15 to: 'UNDER_REVIEW',16 actors: ['automation']17 })18 .transition({19 id: 'approve',20 from: 'UNDER_REVIEW',21 to: 'APPROVED',22 actors: ['human'],23 guards: [24 {25 id: 'approval_obtained',26 severity: 'hard',27 evaluatedBy: 'runtime',28 description: 'Approval must be present',29 failureMessage: 'Approval not obtained'30 }31 ]32 })33 .transition({34 id: 'reject',35 from: 'UNDER_REVIEW',36 to: 'REJECTED',37 actors: ['human']38 })39 .outcome({40 id: 'expense_approved',41 description: 'Expense report approved',42 valueUnit: 'expense_approved'43 })44 .build()45 46const aggregate = aggregateId('EXP-2026-001')47const { engine } = createLoopSystem({ loops: [approval] })48 49await engine.start({50 loopId: 'expense.approval',51 aggregateId: aggregate,52 orgId: 'acme',53 actor: { type: 'system', id: 'system:intake' }, "cmt">// StartOptions.actor54 metadata: { source: 'expense_form' } "cmt">// optional StartOptions.metadata55})56 57const reviewResult = await engine.transition({58 aggregateId: aggregate,59 transitionId: transitionId('start_review'),60 actor: { type: 'automation', id: 'system:router' }61})62 63const approveResult = await engine.transition({64 aggregateId: aggregate,65 transitionId: transitionId('approve'),66 actor: { type: 'human', id: 'manager@acme.com' },67 evidence: { approved: true, comment: 'Within policy' }68})