SDK reference

A workflow program imports everything from @boardwalk-labs/workflow (MIT, source). These imports are facades over the engine running the program, so the same imports work identically under self-hosting and on Boardwalk. The package also exports the TypeScript types (Context, AgentOptions, and more), the manifest schema, and the workflow.jsonc descriptor parser for tooling. Python mirrors the whole surface as the boardwalk package on PyPI (snake_case: human_input, auth.id_token).

ExportPurpose
runThe entry function you write; the platform calls it.
contextRead-only metadata about this run.
agent()Runs one model call to completion.
computer.openBrowser()Opens a real browser inside the run's machine.
secrets.get()Resolves a secret to its plaintext value, fail-closed.
authMints short-lived API bearers and OIDC id-tokens.
usage.get()The run's live budget state.
sleep()Holds the run; a long sleep suspends and releases its machine.
humanInput()Pauses the run for a person to answer, then resumes.
workflows.call() / run() / schedule()Durable child runs, fire-and-forget runs, and future schedules.
parallel()Runs thunks concurrently, fault-tolerantly, results in order.
phase()Marks the current section of the run for the live tail and log.
artifacts.write()Stores a file with the run and resolves to a download URL.
installTestHost()An in-process fake host for unit tests.

The run function

export default async function run(input: I, context?: Context): Promise<O>

You write it; the SDK doesn't export it. The entry file exports a default runfunction the platform calls with the trigger's payload. Params are positional, Lambda-style: input is param 0, context is param 1, and declaring fewer is fine (run(), run(input), run(input, context)). Annotate input and the return type and the deploy derives their schemas; a bare run(input)receives the raw JSON untouched. The return value is the run's output, validated against the derived output schema and persisted; a void return persists null. Throwing fails the run. See Typed input and output.

context

context.runId            // string, a bare 26-char ULID
context.workflowId       // string
context.workflowVersion  // sequential int; the version this run pinned to
context.orgId            // string
context.environment      // { id, name } | null (null = the org base)
context.actor            // who invoked the run: user | workflow | webhook | cron | event
context.trigger          // kind: "cron" | "webhook" | "manual", plus firedAt and a source
context.attempt          // 1-based; increments on crash-restart-from-top
context.workspaceDir     // absolute /workspace root (also cwd + HOME)
context.signal           // AbortSignal; aborts when the run is cancelled

Read-only metadata about this run, and nothing that acts: no capabilities, no secrets. Data comes in through context; actions come in through imports, which is why the credential mints live on auth below. trigger.kind is the transport (a cron timer, a webhook delivery, or a direct invocation); actor is the initiator, so a workflows.call child arrives as manual with actor.type: "workflow".

agent()

function agent<T = string>(prompt: string, opts?: AgentOptions): Promise<T>

Runs one model call to completion. Without a schema it resolves to the final text; with one, to a validated object (pass the type: agent<Bug[]>(prompt, { schema })).

interface AgentOptions {
  model?: string;       // omit → the managed Auto lane; or an opaque "<vendor>/<model>" id
  provider?: string;    // default "boardwalk" (managed); name your own for BYO keys
  reasoning?: ReasoningEffort | ReasoningOptions;  // "none".."xhigh", or { effort, maxTokens, exclude }
  schema?: JsonSchema;  // JSON Schema → structured, validated output
  name?: string;        // a label for this agent in the run log
  builtins?: "all" | "read-only" | "none" | string[];  // which built-in tools (read/write/edit/bash/grep)
  cwd?: string;         // the workspace subdirectory this agent works from (default: the root)
  tools?: ToolDef[];    // extra program-defined tools, added on top of the built-ins
  mcp?: McpServerRef[]; // MCP servers this agent may call
  skills?: string[];    // skills from the package's skills/ dir to load
  memory?: string;      // a workspace dir for this agent's persistent memory
  humanInput?: boolean; // let the agent pause mid-loop to ask a person (off by default)
  session?: BrowserSession;        // a browser from computer.openBrowser() this agent may drive
  attachments?: AgentAttachment[]; // images or PDFs to put in front of the model
  maxIterations?: number;          // ceiling on this leaf's tool-calling turns (default: a backstop)
}

Model and provider are chosen per call; the workflow declares none. See Inference. Capabilities are per call too: each agent() brings its own tools, MCP servers, skills, and memory; there are no workflow-level capability fields.

reasoning controls how hard the model thinks before answering. A bare string is an effort level, "none" through "xhigh" (reasoning: "high" is shorthand for { effort: "high" }); the object form also takes maxTokens (a direct cap, for providers that take one) and exclude(think internally but keep the trace out of the response). Omit it for the provider's adaptive default. The one neutral control maps to each provider's native knob, so a model that doesn't support a level surfaces an error rather than a silent downgrade.

The capability options equip the leaf; each is per call, and Equipping agents is the guide to scoping and combining them:

OptionWhat it does
builtinsWhich built-in tools the agent carries: "all" (default), "read-only", "none", or an explicit list of names.
toolsExtra program-defined tools, typed functions that run in your program, added on top of the built-ins.
mcpExternal MCP servers this agent may call (http on hosted runners; stdio under self-hosting).
skillsReusable instructions from the package's skills/ directory to load.
memoryA workspace directory the agent reads and writes, persisted across runs automatically.
cwdThe workspace subdirectory the agent's file tools resolve and stay inside.
attachmentsImages or documents the model can see, inline base64 or a URL.
maxIterationsA ceiling on the leaf's tool-calling turns; a cost guardrail.
sessionA browser from computer.openBrowser() this agent may drive, below.

computer.openBrowser()

function computer.openBrowser(opts?: {
  startUrl?: string;
  viewport?: { width: number; height: number };
  grounding?: "auto" | "a11y" | "vision" | "none";
}): Promise<BrowserSession>

Opens a real browser inside the run's own machine and returns a session handle your program owns. Drive it in deterministic code (navigate, url, title, eval, screenshot, console, network, close), hand it to a leaf with agent(prompt, { session }), or both, since they share one browser. eval is deliberately program-only: arbitrary page JavaScript is the injection jackpot, so the model gets structured actions instead. The session survives a suspend, so a run can log in, park on a human gate for a day, and resume still signed in. See Browser use.

computer.openDesktop()

function computer.openDesktop(opts?: {
  grounding?: "auto" | "none";
}): Promise<DesktopSession>

Opens the run machine's whole screen, for work that lives in an application rather than a page. The handle is thin (id, screenshot, close) because launching applications is shell()'s job; the session exists to bind the agent's tools. Handing it to a leaf with agent(prompt, { session }) gives it screenshot, click, type, key, scroll and drag, working in screen pixels, so the call must name a model that can ground on an image. A run has one desktop, so a second call errors while the first is open. See Desktop use.

secrets.get()

function secrets.get(name: string): Promise<string>

Resolves a secret to its plaintext value, fail-closed against permissions.secrets. The value is redacted from everything the model sees. See Secrets & environments.

auth

auth.apiToken()          // Promise<string>: a short-lived bearer for the Boardwalk API / MCP
auth.idToken(audience)   // Promise<string>: a per-run OIDC id-token for cloud federation

Short-lived credentials, minted on demand and never placed in process.env. They are actions, so they are imports, not context fields. apiToken()returns a bearer scoped to the descriptor's permissions, for calling the Boardwalk API or MCP server from inside a run. idToken(audience) mints a signed OIDC id-token your own cloud can verify, the keyless alternative to storing AWS/GCP/Azure keys as secrets; it requires permissions.id_token: "write". Both are redacted from everything the model sees. See Cloud access (OIDC).

usage.get()

function usage.get(): Promise<UsageSnapshot>
// { spent, cap, remaining } per budget dimension: usd, tokens, compute_seconds
// (cap and remaining are null when a dimension is uncapped)

The run's live budget state, for programs that want to wind down gracefully (summarize what's done, skip the optional tail) before the platform's budget pause kicks in. A breached budget cap parks the run for approval; it never hard-kills.

sleep()

function sleep(arg: number | { durationMs: number } | { until: string | Date }): Promise<void>

Holds the run; a bare number is milliseconds. It really waits and your locals survive. A short sleep holds in place; a long one suspends the run and releases its machine, so the wait is free, then it resumes where it left off.

humanInput()

function humanInput(opts: {
  prompt: string;
  input:
    | { kind: "text"; multiline?: boolean; placeholder?: string; required?: boolean }
    | { kind: "choice"; options: string[]; allowOther?: boolean }
    | { kind: "multiselect"; options: string[]; min?: number; max?: number };
  key?: string;          // stable id for the gate (defaults to its position)
  assignees?: string[];  // who may answer, e.g. "role:admin", "user:<id>"
  timeout?: string;      // e.g. "48h"
  onTimeout?: "fail" | { value: HumanInputResult };
}): Promise<HumanInputResult>

Pauses the run for a person to answer, then resumes with their validated response. The input form is a discriminated union: a text gate resolves to { value: string }, a choice to { value, isOther }, and a multiselect to { values, other? }. While it waits the run is suspended (it does not burn compute); a person answers from the dashboard or with boardwalk respond. An answered gate is never re-asked, even if the run restarts. See Human-in-the-loop.

workflows.call() / run() / schedule()

function workflows.call(slug: string, input: unknown, opts?: CallOptions): Promise<unknown>
function workflows.run(slug: string, input: unknown, opts?: CallOptions): Promise<string>
function workflows.schedule(
  slug: string,
  input: unknown,
  opts: { cron?: string; rate?: string; at?: string | Date; timezone?: string },
): Promise<string>

call starts another workflow as a durable child run, holds for it, and resolves to its output. runis fire-and-forget and resolves to the new run's id. Both are idempotent on (parent, target, input), so a restarted parent re-attaches instead of double-firing. schedule registers a future run (exactly one of cron, rate, or at) and resolves to a schedule id.

A callresult arrives revived by the callee's declared output schema: a child returning a Date hands you a Date (same for bigint, Uint8Array, Set). An untyped callee returns plain JSON.

parallel()

function parallel<T>(thunks: readonly (() => Promise<T>)[]): Promise<(T | null)[]>

Runs thunks concurrently and resolves to their results in order. It is fault-tolerant: a thunk that throws is isolated to null in its slot (and the failure is logged) rather than rejecting the whole batch, so one stuck agent()doesn't discard its siblings' work. Filter the nulls to use the successes. The one exception is a run-fatal error (budget exhausted, run cancelled), which still rejects after the other thunks settle.

phase()

function phase(name: string, opts?: { id?: string }): void

Marks the current section of the run for the live tail and run log. Observability only; it does not checkpoint or skip code on restart.

artifacts.write()

function artifacts.write(
  name: string,
  contentType: string,
  body: string | Uint8Array,
  metadata?: Record<string, unknown>,
): Promise<{ id: string; name: string; url: string }>

Stores a file with the run and resolves to a download URL. Use it for outputs you want to keep and link to: a report, a generated image, a diff.

installTestHost()

import { installTestHost } from "@boardwalk-labs/workflow";
import run from "./index.js";

const host = installTestHost({
  agent: async () => "LGTM",
  secrets: { GITHUB_TOKEN: "test-token" },
});
const out = await run({ pr: 7 }, host.context());

Installs an in-process fake host, making run(input, context) a plain function call over stubs: no socket, no engine, so unit tests run under any test runner. Stub only what the code under test uses (a called-but-unstubbed capability throws a clear error); sleep resolves immediately and phase is a no-op by default. host.context() returns a plausible Context you can override field by field, and host.cancel() simulates cancellation. For live execution, deploy and trigger a real run with boardwalk run, selecting a dev or test environment.