Skip to content
Looms
Documentation
On this page

API reference

The embed API runs within one process. Actor hosts expose a similar HTTP contract with one writer per run. Signatures below are extracted from the runtime source; the client returns HTTP response envelopes rather than exactly the same shapes as embed methods.

Create a runtime#

ts
import { createLooms } from '@swirls/looms/runtime'import { modules, release } from './review-definition'const looms = createLooms({ modules })try {  const { runId, state } = await looms.start(release, { topic: 'Launch' })  console.log(runId, state.status)} finally {  await looms.stop()}

This uses the quickstart definition and an in-memory store. Pass a persistent store before relying on restart recovery. Nothing is installed by default: modules defaults to an empty list, store to memory, and serve to off.

ts
export interface CreateLoomsOptions {  /** Runtime modules to enable. Pass `agent()`, `workflow()`, `approval()`, and/or your own. */  readonly modules?: readonly AnyRuntimeModule[]  readonly store?: EventStore | Promise<EventStore> | (() => Promise<EventStore>)  readonly serve?: boolean | { port?: number; hostname?: string }  readonly maxWakeIterations?: number  /** Maximum active and queued mutation operations per run. Default 256. */  readonly maxPendingRunOperations?: number  /** Durable events between mid-wake snapshots. Default 200; `0` disables. Parking always snapshots. */  readonly snapshotEvery?: number  /** Defaults to the EventStore's attached store (S2) or an in-memory SnapshotStore. */  readonly snapshotStore?: SnapshotStore  readonly trimAfterSnapshot?: {    keepSnapshots: number    coverage?: (runId: string) => EventStoreTrimCoverage | Promise<EventStoreTrimCoverage>  }  /** In-process cursor cache size. Default `0` (stateless between calls). */  readonly runCacheSize?: number  /** Scan existing runs for pending timers on boot. Default true. */  readonly rescanTimers?: boolean  /** Pluggable wake scheduler for timer waits. Defaults to an in-process Effect fiber scheduler. */  readonly scheduler?: WakeScheduler  readonly worker?: EffectWorker  /** Failure-isolated runtime observation sink. */  readonly observer?: RuntimeObserver  /**   * Authorizes HTTP API requests. Without it, run reads and writes are open while worker callback   * and operations routes return 503; `bearerAuth({ worker, operations })` enables them.   *   * A mounted debugger is authorized as `{ access: 'read', name: 'debugger' }`. Bearer tokens on   * reads block browser navigation, so hosts that need auth should use a cookie or session policy.   */  readonly authorize?: Authorize  /**   * Serves a debugger UI under `debugger.basePath` (for example `/debugger`). The runtime does not   * bundle a UI; pass `debuggerUi()` from `@swirls/looms/debugger/server`.   */  readonly debugger?: DebuggerHost}

snapshotEvery defaults to 200 durable events; zero disables mid-wake snapshots, while parking still snapshots. runCacheSize defaults to zero. maxWakeIterations defaults to 100 and bounds a wake cycle, not application cost. The default scheduler uses process-local timers. trimAfterSnapshot is opt-in and can remove historical replay data.

Embed methods and return types#

ts
export interface StartResult {  readonly runId: string  readonly threadId: string  readonly state: RunState}export interface Looms {  readonly runtime: Promise<LoomsRuntime>  readonly store: Promise<EventStore>  ready(): Promise<{ store: EventStore; runtime: LoomsRuntime }>  /** Start a run from a definition object; the input type follows the definition's schema. */  start<TDef extends DefinitionRef>(    definition: TDef,    input?: DefinitionInput<TDef>,    options?: { runId?: string; threadId?: string; idempotencyKey?: string },  ): Promise<StartResult>  /** Start a run by `{ kind, definitionName }` when you only have names (HTTP bodies, CLIs). */  startRun(args: {    kind: string    definitionName: string    definitionVersion?: string    input?: JsonValue    runId?: string    threadId?: string    idempotencyKey?: string  }): Promise<StartResult>  getRun(runId: string): Promise<RunState>  getEvents(runId: string, options?: { fromSeq?: number; limit?: number }): Promise<EventEnvelope[]>  signal(    runId: string,    events: ReadonlyArray<EventInput>,    options?: { idempotencyKey?: string },  ): Promise<RunState>  wake(runId: string): Promise<RunState>  rescanTimers(): Promise<number>  recoverDeadlines(): Promise<number>  inspectRun(runId: string): Promise<RunOperationalStatus>  retryEffect(runId: string, effectId: string): Promise<RunState>  listRuns(): Promise<string[]>  project<S>(runId: string, definition: ProjectionDefinition<S>): Promise<S>  replayTo(runId: string, seq: number): Promise<ReplayStep | null>  cancel(runId: string, threadId?: string): Promise<RunState>  workerCallback(runId: string, input: WorkerCallbackInput): Promise<RunState>  fetch(req: Request): Promise<Response | null>  serve(options?: { port?: number; hostname?: string }): RunningServer  stop(): Promise<void>}

start and startRun#

start infers input from a definition; startRun accepts registered names. Both run a wake cycle and resolve with runId, threadId, and state. A gate or child may still be pending when either method resolves. They reject on invalid input, missing definitions, storage failures, or execution limits.

Use a stable runId and idempotencyKey when retrying creation of the same logical run. Generating a new runId on every retry creates a different run. Register all definitions before starting or spawning them.

signal, wake, and cancel#

signal validates and appends inputs, then wakes execution and returns state. Its optional idempotencyKey identifies retries of the same request within a run. wake processes pending work without appending a new domain signal. cancel records cancellation for a run or thread; it cannot undo an external action.

Read and inspect#

getRun returns folded RunState. getEvents returns recorded envelopes ordered by sequence, with optional fromSeq and limit. project folds a projection and returns its state. replayTo returns before/after state at a sequence, or null when no matching step is available; it does not execute effects.

ready initializes storage and modules. rescanTimers re-registers persisted timers on the configured scheduler. fetch returns a Response or null for unhandled paths. serve starts the Bun HTTP server. stop disposes the runtime scheduler and any server it started; it does not cancel runs or back up storage.

HTTP contract#

Put authentication and authorization ahead of every route, including event streams. JSON requests use content-type: application/json.

text
GET  /health                         → { ok: true }POST /runs                           → { runId, threadId, state }GET  /runs                           → { runIds } (embed only)GET  /runs/summaries                 → { runs }GET  /runs/:id                       → { runId, state }GET  /runs/:id/events                → { runId, events }POST /runs/:id/events                → { runId, state }GET  /runs/:id/threads               → { runId, threads }GET  /runs/:id/status                → run, deadline, and effect statusGET  /runs/:id/effects               → { runId, effects }POST /runs/:id/effects/:effect/retry → operator-reviewed retryPOST /runs/:id/wake                  → { runId, state }GET  /runs/:id/replay?seq=10          → { runId, step }GET  /runs/:id/projections/:name      → { runId, name, value }GET  /runs/:id/events?live=true       → SSEPOST /operations/deadlines/rescan     → rebuild wake deadlines

POST /runs accepts kind, definitionName, input, and optionally runId and idempotencyKey. POST /runs/:id/events accepts an events array and optionally idempotencyKey. The embed handler also recognizes the Idempotency-Key header. GET event history supports fromSeq and limit. Request the same resource with Accept: text/event-stream or live=true for SSE. Streams resume after Last-Event-ID, or begin at fromSeq when that header is absent.

bash
curl -X POST http://localhost:8787/runs \  -H 'content-type: application/json' \  -d '{"kind":"workflow","definitionName":"release-v1","input":{"topic":"Launch"}}'

Actor hosts return 501 for global GET /runs; use an application registry or shared index. The embed handler maps InvalidInputError and InvalidEventError to 400, unknown projections to 404, and other caught execution failures to 500. Routing behavior differs by host; inspect status and body. Client request helpers reject non-success responses. Cancellation is currently an embed runtime method, not a client HTTP endpoint.

Client and streaming#

createLoomsClient accepts baseUrl and an optional fetch implementation. getRun and signal resolve to objects containing state; getEvents resolves to an object containing events. subscribeEvents returns an unsubscribe function and reconnects using its last cursor. stream(definition, input) or streamRun(args) returns an async iterable with a runId for events during the initial wake cycle. A stream ending does not mean a parked workflow has finished.

The current client start/signal helpers do not expose every embed idempotency option. For retry-sensitive requests, use an application endpoint or explicit HTTP bodies/headers with stable operation identifiers. See integration.

Package modules and extension APIs#

  • @swirls/looms/core: defineModule, defineKind, createKind, defineEventCatalog, defineThread, defineEffect, defineProjection; invoke, spawn, wait, emit, complete, fail. The bun-sqlite entry supplies Bun storage.
  • @swirls/looms/runtime: createLooms and lower-level runtime APIs.
  • @swirls/looms/agent: agent, defineAgent, defineTool, asThreadTool, asAgentTool, asWorkflowTool, asEvaluatorTool, defineAgentSession, submitAgentMessage, conversation, and userMessage.
  • @swirls/looms/workflow: workflow, defineWorkflow, graph validation, failure policies, and map, fan-out, and while helpers.
  • @swirls/looms/approval: approval, gate, decision, pendingApprovals.
  • @swirls/looms/evaluator: evaluator, defineEvaluator, evaluate, evaluations.
  • @swirls/looms/actor: createActorCell and createLocalActorHost.
  • @swirls/looms/cloudflare: LoomsDurableObject and routeToDurableObject.
  • @swirls/looms/client and @swirls/looms/react: HTTP clients, streams, providers, and projection hooks.
  • @swirls/looms/ai-vercel: vercelLlm for AI SDK chat models; vercelEvaluator for evaluation models such as TypeSafe Jev.
  • @swirls/looms/projectors: post-commit indexes and fan-out. @swirls/looms/s2: stream replication.
  • @swirls/looms/testing: deterministic in-memory helpers and conformance checks.
  • @swirls/looms/otel: OpenTelemetry spans and metrics for runtime observations.
  • @swirls/looms/cli and @swirls/looms/debugger: inspection tools.

For module authoring, see custom modules. For agent options and tools, see agents. Package type declarations remain the exhaustive reference for lower-level interfaces.