Examples
Expense Approval
Expense approval demonstrates the minimal Loop Engine flow with explicit actor attribution and guard outcomes.
Loop behavior
- States:
SUBMITTED -> UNDER_REVIEW -> APPROVED | REJECTED - Actors:
system,automation,human - Guard:
approval_obtainedonapprove - Outcome:
expense_approved
Loop definition
1import { LoopBuilder } from "@loop-engine/sdk"2 3const approval = LoopBuilder4 .create("expense.approval", "finance")5 .description("Expense report approval")6 .state("SUBMITTED")7 .state("UNDER_REVIEW")8 .state("APPROVED", { isTerminal: true })9 .state("REJECTED", { isTerminal: true })10 .initialState("SUBMITTED")11 .transition({ id: "start_review", from: "SUBMITTED", to: "UNDER_REVIEW", actors: ["automation"] })12 .transition({13 id: "approve",14 from: "UNDER_REVIEW",15 to: "APPROVED",16 actors: ["human"],17 guards: [18 {19 id: "approval_obtained" as never,20 description: "Manager approval required",21 failureMessage: "Approval missing",22 severity: "hard",23 evaluatedBy: "runtime"24 }25 ]26 })27 .transition({ id: "reject", from: "UNDER_REVIEW", to: "REJECTED", actors: ["human"] })28 .outcome({ id: "expense_approved", description: "Expense approved", valueUnit: "expense_approved" })29 .build()Runtime path
1import { aggregateId, transitionId } from "@loop-engine/core"2import { createLoopSystem } from "@loop-engine/sdk"3 4const aggregate = aggregateId("EXP-2026-001")5const { engine } = await createLoopSystem({ loops: [approval] })6 7await engine.start({8 loopId: "expense.approval",9 aggregateId: aggregate,10 orgId: "acme",11 actor: { type: "system", id: "system:intake" }12})13 14await engine.transition({15 aggregateId: aggregate,16 transitionId: transitionId("start_review"),17 actor: { type: "automation", id: "system:router" }18})Guard outcomes
1const passed = await engine.transition({2 aggregateId: aggregate,3 transitionId: transitionId("approve"),4 actor: { type: "human", id: "manager@acme.com" },5 evidence: { approved: true }6})7"cmt">// passed.status === "executed"8 9const failed = await engine.transition({10 aggregateId: aggregate,11 transitionId: transitionId("approve"),12 actor: { type: "human", id: "manager@acme.com" },13 evidence: { approved: false }14})15"cmt">// failed.status === "guard_failed"Full source
- https://github.com/loopengine/loop-examples/tree/main/expense-approval