Workflow SDK

@alineo-labs/workflow adds a lazy, declarative layer over the Core SDK — describe a pipeline, then run it with one await, with sandbox lifecycles managed for you.

@alineo-labs/workflow lets you describe what should happen in a sandbox, then run all of it with a single await at the end:

import { workflow } from "@alineo-labs/workflow";

await workflow(client)
  .sandbox({ image: "node:22", resources: { cpu: "500m", memory: "512Mi" } }, (sb) => {
    sb.exec("npm ci");
    sb.retry(3, (sb) => sb.exec("npm test"), { backoff: "exponential" });
  })
  .pipe(process.stdout);

client is an ordinary Core SDK Sandbox client. The callback runs synchronously and only queues operations; nothing reaches a container until .pipe() or .result() is awaited. Then the sandbox is created, the queue is flushed in order, and the sandbox is closed — no try/finally. Quickstart →

Control flow without awaits

sb.exec("test -f /tmp/cache.tar.gz", { strict: false });
sb.when(
  (ctx) => ctx.exitCode === 0,
  (sb) => sb.exec("tar -xzf /tmp/cache.tar.gz"),
  (sb) => sb.exec("npm ci"),
);
sb.forEach(["lint", "test", "build"], (sb, task) => sb.exec(`npm run ${task}`));

retry, when and forEach take a callback that queues more operations, so a pipeline with branches and loops is still one synchronous description — control flow.

Many containers, one call

await workflow(client)
  .parallel(
    [
      { image: "node:20", resources: { cpu: "500m", memory: "512Mi" } },
      { image: "node:22", resources: { cpu: "500m", memory: "512Mi" } },
    ],
    (sb) => sb.exec("npm test"),
  )
  .pipe(process.stdout);

.parallel() runs the same operations in several containers at once; .sequence() runs different ones in order, each step able to use what the last one produced — parallel and sequence.

Values come back as files

Queued operations don't return anything, so a value leaves the sandbox the way a shell script would hand one over: write it to a file, then readFile(path, as). After the workflow resolves it is in vars[as] — capturing values.

When to use it

Core SDK (@alineo-labs/sandbox)Workflow SDK (@alineo-labs/workflow)
StyleImperative — hold objects, call methodsDeclarative — describe ops, flush once
AwaitPer operationOnce at the end
LifecycleManual try/finally sb.close()Managed automatically
Many sandboxesSeveral variables.parallel(), .sequence()
StreamingExecHandle.pipe().pipe(writable) on the workflow
Valuesawait sb.exec(...)readFile(path, as) → vars

Reach for the Core SDK directly when you need each exec's result mid-flow, or are fitting sandbox work into imperative code you already have. Everything here runs on the same SandboxHandle underneath, and writes the same ledger.