Loop Engine

Packages

@loop-engine/core

@loop-engine/core defines the type contract used by every other @loop-engine/* package and ships no runtime implementations.

Install

1npm install @loop-engine/core

Overview

Core provides branded identifiers, actor and guard primitives, and canonical loop lifecycle interfaces. Runtime, DSL, adapters, and SDK layers all import these contracts.

Branded IDs

1export type LoopId = string & { readonly __brand: "LoopId" }
2export type StateId = string & { readonly __brand: "StateId" }
3export type TransitionId = string & { readonly __brand: "TransitionId" }
4export type AggregateId = string & { readonly __brand: "AggregateId" }
5export type ActorId = string & { readonly __brand: "ActorId" }
6export type GuardId = string & { readonly __brand: "GuardId" }
7export type SignalId = string & { readonly __brand: "SignalId" }
8export type OutcomeId = string & { readonly __brand: "OutcomeId" }
9export type CorrelationId = string & { readonly __brand: "CorrelationId" }

Helper constructors cast incoming strings to branded IDs:

1import { aggregateId, transitionId } from "@loop-engine/core"
2 
3const aggregate = aggregateId("EXP-2026-001")
4const transition = transitionId("approve")

Loop definition types

1export type ActorType = "human" | "automation" | "ai-agent" | "webhook" | "system"
2export type LoopStatus = "OPEN" | "IN_PROGRESS" | "CLOSED" | "ERROR" | "CANCELLED"
3 
4export interface GuardSpec {
5 id: GuardId
6 description: string
7 failureMessage: string
8 severity: "hard" | "soft"
9 evaluatedBy: "runtime" | "module" | "external"
10}
11 
12export interface StateSpec {
13 id: StateId
14 description?: string
15 isTerminal?: boolean
16 isError?: boolean
17}
18 
19export interface TransitionSpec {
20 id: TransitionId
21 from: StateId
22 to: StateId
23 allowedActors: ActorType[]
24 guards?: GuardSpec[]
25 sideEffects?: SideEffectSpec[]
26 description?: string
27}
28 
29export interface LoopDefinition {
30 id: LoopId
31 version: string
32 description: string
33 domain: string
34 states: StateSpec[]
35 initialState: StateId
36 transitions: TransitionSpec[]
37 outcome: OutcomeSpec
38 participants?: string[]
39 spawnableLoops?: LoopId[]
40 metadata?: Record<string, unknown>
41}

Runtime state types

1export interface ActorRef {
2 type: ActorType
3 id: ActorId
4 displayName?: string
5 sessionId?: string
6 agentId?: string
7}
8 
9export interface LoopInstance {
10 loopId: LoopId
11 aggregateId: AggregateId
12 orgId: string
13 currentState: StateId
14 status: LoopStatus
15 startedAt: string
16 closedAt?: string
17 correlationId: CorrelationId
18 metadata?: Record<string, unknown>
19}
20 
21export interface TransitionRecord {
22 id: string
23 loopId: LoopId
24 aggregateId: AggregateId
25 transitionId: TransitionId
26 fromState: StateId
27 toState: StateId
28 actor: ActorRef
29 evidence: Evidence
30 occurredAt: string
31 durationMs?: number
32}

Signal and outcome types

1export interface OutcomeSpec {
2 id: OutcomeId
3 description: string
4 valueUnit: string
5 measurable: boolean
6 businessMetrics?: BusinessMetric[]
7}
8 
9export interface Signal {
10 id: SignalId
11 type: string
12 subject: string
13 confidence?: number
14 observedAt: string
15 payload: Record<string, unknown>
16 triggeredLoopId?: LoopId
17}

Design principles

  • Type-first boundaries keep packages interoperable without runtime coupling.
  • Zero runtime dependencies keep core safe for shared libraries and edge bundles.
  • Branded IDs reduce accidental string mixing across loop, actor, transition, and signal contexts.