> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflows

> Start, inspect, and steer workflow runs from tools, jobs, webhooks, triggers, and code steps

`Workflows` starts runs of a deployed [workflow](/concepts/workflows) and steers them: read a run, list runs, cancel, resume a suspended step, deliver signals, start batches, raise a parked run's budget, and manage goals. Every member returns as soon as the platform accepts the call; none waits for a run to finish. Available in tools, jobs, webhooks, triggers, processors, and workflow code steps. From a code step, `start()` creates a detached run; to wait on a nested run, use the `workflow()` step of the [workflow builder](/reference/sdk/workflow-builder).

*Verified against lua-cli 3.33.0.*

```ts theme={null}
import { Workflows } from 'lua-cli';
```

## Quick example

```ts theme={null}
import { Workflows } from 'lua-cli';

const { runId, status } = await Workflows.start('outreach', { leadIds: ['lead_abc123'] }, {
  idempotencyKey: 'outreach:2026-09-12',
  budget: { maxCredits: 50 },
});
if (status !== 'gated') {
  await Workflows.signal(runId, 'review', { approved: true });
}
```

## Methods

### start()

Starts a run and returns its id and status.

```ts theme={null}
Workflows.start(nameOrId: string, input?: unknown, opts?: {
  idempotencyKey?: string;
  budget?: { maxCredits?: number; maxSteps?: number; maxDurationSeconds?: number };
  waitSeconds?: number;
  initialState?: Record<string, unknown>;
  correlationKey?: string;
  tags?: string[];
  replyTo?: { channel: string; threadId: string };
  onBehalfOf?: { userId: string };
  workflowVersionId?: string;
}): Promise<StartWorkflowRunResult>
```

<ParamField path="nameOrId" type="string" required>
  The workflow's `name` on this agent, resolved first, or a workflow id.
</ParamField>

<ParamField path="input" type="unknown">
  The run's input, validated against the workflow's input schema. The REST route that `lua test` calls refuses JSON over 256 KB; deployed agents don't apply that cap.
</ParamField>

<ParamField path="opts.idempotencyKey" type="string">
  A second `start()` with the same key returns the existing run with `idempotentReplay: true` and `deduplicated: true` instead of creating another.
</ParamField>

<ParamField path="opts.budget" type="{ maxCredits?: number; maxSteps?: number; maxDurationSeconds?: number }">
  Caps for this run. A run that reaches a cap parks with `nextAction: 'raise_budget'`.
</ParamField>

<ParamField path="opts.waitSeconds" type="number">
  From 0 to 55. The platform holds the request for up to this long; when the run reaches a terminal or suspended state inside the window, the result is the run detail with `output`, otherwise the accepted `{ runId, status }`. The execution model is unchanged either way.
</ParamField>

<ParamField path="opts.initialState" type="Record<string, unknown>">
  Seed for the run's `state`.
</ParamField>

<ParamField path="opts.correlationKey" type="string">
  A key of your own that `signalByKey()` can address the run by.
</ParamField>

<ParamField path="opts.tags" type="string[]">
  Labels for `list({ tags })`.
</ParamField>

<ParamField path="opts.replyTo" type="{ channel: string; threadId: string }">
  Where the run's replies go.
</ParamField>

<ParamField path="opts.onBehalfOf" type="{ userId: string }">
  The end user the run acts for.
</ParamField>

<ParamField path="opts.workflowVersionId" type="string">
  Pins a version. Defaults to the active version.
</ParamField>

**Returns**

<ResponseField name="result" type="StartWorkflowRunResult">
  The accepted run.

  <Expandable title="properties">
    <ResponseField name="runId" type="string">The run's id.</ResponseField>
    <ResponseField name="status" type="WorkflowRunStatus">`queued`, or `gated` when the run holds no organization slot yet (quota, billing, or consent); a `waitSeconds` result carries the run's current status.</ResponseField>
    <ResponseField name="watchHint" type="string">How to follow the run.</ResponseField>
    <ResponseField name="idempotentReplay" type="boolean">`true` when an idempotency key matched.</ResponseField>
    <ResponseField name="deduplicated" type="boolean">`true` when no run was created; `workflowId`, `workflowName`, `startedAt`, and `status` then describe the existing run.</ResponseField>
    <ResponseField name="output" type="unknown">The result, on a `waitSeconds` call that observed a terminal run.</ResponseField>
  </Expandable>
</ResponseField>

**Example**

```ts theme={null}
import { Workflows } from 'lua-cli';

const result = await Workflows.start('triage-ticket', { ticketId: 'entry_abc123' }, {
  correlationKey: 'ticket:entry_abc123',
  tags: ['support'],
  waitSeconds: 30,
});
console.log(result.runId, result.status, result.output);
```

**Errors** — `RUNS_IN_FLIGHT` when the workflow's `concurrencyPolicy` is `forbid` and a run is in flight (under `lua test` the error carries `blockingRunId`); `WORKFLOW_NOT_FOUND`; `PAYLOAD_TOO_LARGE` (413) under `lua test` when `input` exceeds 256 KB; `CONTROL_UNAVAILABLE` (503) while the control plane is unavailable; `waitSeconds must be 0..55 (got <n>)` before any request is made.

<Info>
  Local runs only. In deployed agents `start` forwards only `idempotencyKey`, `budget`, `correlationKey`, `tags`, and `workflowVersionId` and resolves `{ runId, status }`; see [Deployed runtime differences](#deployed-runtime-differences) for `get`, `list`, and `cancel`.
</Info>

### get()

Returns one run's summary.

```ts theme={null}
Workflows.get(runId: string): Promise<WorkflowRun>
```

**Returns** — a [`WorkflowRun`](#workflowrun) summary. `hasOutput` and a 2 KB `outputPreview` are included; `output` itself is not. A run whose outputs you may not read comes back with `restricted: true` and no output, never as an error. Read a run's steps and full output with [`lua workflows`](/reference/cli/workflows).

**Example**

```ts theme={null}
import { Workflows } from 'lua-cli';

const run = await Workflows.get('run_abc123');
console.log(run.status, run.nextAction, run.budget?.remaining, run.outputPreview);
```

**Errors** — `RUN_NOT_FOUND`.

### list()

Returns run summaries, optionally filtered.

```ts theme={null}
Workflows.list(opts?: {
  limit?: number;
  status?: WorkflowRunStatus | string;
  workflow?: string;
  correlationKey?: string;
  tags?: string[];
  sort?: string;
}): Promise<WorkflowRun[]>
```

<ParamField path="opts.limit" type="number">Maximum number of runs.</ParamField>
<ParamField path="opts.status" type="WorkflowRunStatus">Only runs in this status.</ParamField>
<ParamField path="opts.workflow" type="string">Only runs of this workflow, by name or id.</ParamField>
<ParamField path="opts.correlationKey" type="string">Only runs started with this key.</ParamField>
<ParamField path="opts.tags" type="string[]">Only runs carrying these tags.</ParamField>
<ParamField path="opts.sort" type="string">Sort order accepted by the platform.</ParamField>

**Returns** — an array of [`WorkflowRun`](#workflowrun) summaries, with the same output rules as `get()`.

**Example**

```ts theme={null}
import { Workflows } from 'lua-cli';

const suspended = await Workflows.list({ workflow: 'triage-ticket', status: 'suspended', limit: 20 });
console.log(suspended.map((run) => run.runId ?? run.id));
```

**Errors** — none beyond platform refusals.

### cancel()

Requests or forces a cancellation.

```ts theme={null}
Workflows.cancel(runId: string, opts?: { mode?: 'request' | 'force'; reason?: string }): Promise<CancelRunVerdict>
```

<ParamField path="opts.mode" type="'request' | 'force'" default="request">
  `request` asks the run to stop at its next checkpoint; `force` abandons it once `forceAvailableAt` has passed.
</ParamField>

<ParamField path="opts.reason" type="string">Recorded on the run.</ParamField>

**Returns**

<ResponseField name="verdict" type="CancelRunVerdict">
  What the cancellation did. A `force` before `forceAvailableAt` returns `transitioned: false` with `nextAction: 'cancel_again'` and the time; a run that is already terminal answers `state: 'terminal'`. Neither is an error.

  <Expandable title="properties">
    <ResponseField name="status" type="WorkflowRunStatus">The run's status after the call.</ResponseField>
    <ResponseField name="nextAction" type="string">`cancel_again`, `force`, or `none`.</ResponseField>
    <ResponseField name="forceAvailableAt" type="string">When `force` becomes available.</ResponseField>
    <ResponseField name="cancelRequested" type="boolean">Whether a request is pending.</ResponseField>
    <ResponseField name="state" type="'running' | 'cancellation_requested' | 'abandoned' | 'terminal'">The run's cancellation state.</ResponseField>
    <ResponseField name="transitioned" type="boolean">Whether this call changed the run.</ResponseField>
  </Expandable>
</ResponseField>

**Example**

```ts theme={null}
import { Workflows } from 'lua-cli';

const verdict = await Workflows.cancel('run_abc123', { reason: 'Duplicate ticket' });
if (verdict.nextAction === 'force') {
  await Workflows.cancel('run_abc123', { mode: 'force' });
}
```

**Errors** — `RUN_NOT_FOUND`.

### resume()

Resumes a suspended step with the data it is waiting for.

```ts theme={null}
Workflows.resume(runId: string, stepId: string, resumeData: unknown): Promise<ResumeStepResult>
```

<ParamField path="runId" type="string" required>The run.</ParamField>
<ParamField path="stepId" type="string" required>The suspended step's id.</ParamField>
<ParamField path="resumeData" type="unknown" required>Validated against the step's resume schema.</ParamField>

**Returns** — `{ resumed: true, runStatus }`, or `{ resumed: false, reason: 'already_resumed', recorded: { at, by? }, runStatus }` when another caller resumed the step first. The loser of the race gets the recorded outcome, never an error.

**Example**

```ts theme={null}
import { Workflows } from 'lua-cli';

const outcome = await Workflows.resume('run_abc123', 'awaitQuote', { amount: 48000, currency: 'USD' });
console.log(outcome.resumed, outcome.runStatus);
```

**Errors** — `RUN_NOT_FOUND`, `STEP_NOT_FOUND`.

### signal()

Delivers a named signal to a run.

```ts theme={null}
Workflows.signal(runId: string, name: string, payload?: unknown, opts?: { dedupeKey?: string }): Promise<SignalRunResult>
```

<ParamField path="name" type="string" required>The signal name a `waitForSignal` step listens for.</ParamField>
<ParamField path="payload" type="unknown">Delivered to the step.</ParamField>
<ParamField path="opts.dedupeKey" type="string">A repeated key returns `duplicate: true` instead of delivering twice.</ParamField>

**Returns**

<ResponseField name="result" type="SignalRunResult">
  <Expandable title="properties">
    <ResponseField name="accepted" type="boolean">Whether the platform stored the signal.</ResponseField>
    <ResponseField name="signalId" type="string">The stored signal's id.</ResponseField>
    <ResponseField name="consumed" type="boolean">Whether a waiting step took it at once.</ResponseField>
    <ResponseField name="stepId" type="string">The step that consumed it.</ResponseField>
    <ResponseField name="duplicate" type="boolean">`true` on a replayed `dedupeKey`.</ResponseField>
    <ResponseField name="reason" type="string">Why the signal was not accepted.</ResponseField>
  </Expandable>
</ResponseField>

**Example**

```ts theme={null}
import { Workflows } from 'lua-cli';

await Workflows.signal('run_abc123', 'payment-received', { invoiceId: 'inv_abc123' }, { dedupeKey: 'inv_abc123' });
```

**Errors** — `RUN_NOT_FOUND`.

### signalByKey()

Delivers a signal to the live run that carries a correlation key.

```ts theme={null}
Workflows.signalByKey(
  nameOrId: string,
  correlationKey: string,
  name: string,
  payload?: unknown,
  opts?: { dedupeKey?: string; allowMultiple?: boolean }
): Promise<{ runIds: string[]; delivered: number }>
```

<ParamField path="nameOrId" type="string" required>The workflow, by name or id.</ParamField>
<ParamField path="correlationKey" type="string" required>The key the run was started with.</ParamField>
<ParamField path="name" type="string" required>The signal name.</ParamField>
<ParamField path="opts.allowMultiple" type="boolean">Deliver to every live run with the key. Without it, more than one match is an error.</ParamField>

**Returns** — `{ runIds, delivered }`. At run time the object also carries a `results` array with one `{ runId, accepted, consumed?, duplicate?, signalId?, code? }` per run; it isn't in the typings.

**Example**

```ts theme={null}
import { Workflows } from 'lua-cli';

const { delivered } = await Workflows.signalByKey('triage-ticket', 'ticket:entry_abc123', 'customer-replied', { text: 'Fixed, thanks' });
```

**Errors** — `WORKFLOW_NOT_FOUND`, `CORRELATION_KEY_NOT_FOUND`, `CORRELATION_KEY_AMBIGUOUS` (several live runs carry the key and `allowMultiple` is not set).

### startBatch()

Starts up to 200 runs of one workflow in a single call.

```ts theme={null}
Workflows.startBatch(
  nameOrId: string,
  items: Array<{
    input: unknown;
    idempotencyKey: string;
    budget?: { maxCredits?: number; maxSteps?: number; maxDurationSeconds?: number };
    tags?: string[];
    correlationKey?: string;
    initialState?: Record<string, unknown>;
  }>,
  opts?: { mode?: 'reject' | 'gate'; budget?: { maxCredits?: number; maxSteps?: number; maxDurationSeconds?: number } }
): Promise<{ batchId: string; accepted: number; items: Array<{ idempotencyKey: string; runId?: string; status: 'queued' | 'gated' | 'replayed' | 'rejected'; code?: string }> }>
```

<ParamField path="items" type="array" required>One entry per run, up to 200; `idempotencyKey` is required on each. A malformed item refuses the whole batch.</ParamField>
<ParamField path="opts.mode" type="'reject' | 'gate'" default="gate">What happens to runs that don't get a slot: `gate` parks them, `reject` refuses them.</ParamField>
<ParamField path="opts.budget" type="object">A budget applied to each run that has none of its own.</ParamField>

**Returns** — `batchId`, the number `accepted`, and one item per input with its `runId` and `status` (`queued`, `gated`, `replayed` for an idempotency match, or `rejected` with a `code`).

**Example**

```ts theme={null}
import { Workflows } from 'lua-cli';

const batch = await Workflows.startBatch(
  'outreach',
  ['lead_abc123', 'lead_def456'].map((leadId) => ({ input: { leadId }, idempotencyKey: `outreach:${leadId}` })),
  { mode: 'gate', budget: { maxCredits: 20 } },
);
console.log(batch.accepted, batch.items.filter((item) => item.status === 'rejected'));
```

**Errors** — `WORKFLOW_NOT_FOUND`, `BATCH_ITEM_INVALID`, `BATCH_TOO_LARGE` (400) past 200 items, `RUNS_IN_FLIGHT` (the whole batch is refused).

### raiseBudget()

Raises the caps of a parked run so it can continue. Increases only.

```ts theme={null}
Workflows.raiseBudget(runId: string, patch: {
  maxCredits?: number;
  maxSteps?: number;
  maxJobSeconds?: number;
  maxDurationSeconds?: number;
  note?: string;
}): Promise<
  | { raised: true; budget: Record<string, number>; runStatus: WorkflowRunStatus; resumed: boolean }
  | { raised: false; reason: 'already_raised' }
>
```

**Returns** — `{ raised: true, budget, runStatus, resumed }`, or `{ raised: false, reason: 'already_raised' }` when another caller raised it first.

**Example**

```ts theme={null}
import { Workflows } from 'lua-cli';

const run = await Workflows.get('run_abc123');
if (run.nextAction === 'raise_budget') {
  await Workflows.raiseBudget('run_abc123', { maxCredits: 200, note: 'Approved by finance' });
}
```

**Errors** — `VALIDATION_FAILED` and `CAP_EXCEEDED` (400), `NOT_RUN_CREATOR` (403), `BUDGET_NOT_RAISABLE` (409).

<Info>
  No runtime serves this member in lua-cli 3.33.0: `lua test` throws `WorkflowApiError` with code `WORKFLOWS_API_UNAVAILABLE`, and deployed agents throw `WorkflowsApiError` with code `not_implemented` (501). Raise a budget with `lua workflows raise-budget` instead.
</Info>

### setGoal()

Creates a goal: the platform runs the workflow repeatedly until a judge decides the objective is met.

```ts theme={null}
Workflows.setGoal(agentId: string, goal: Record<string, unknown>): Promise<Record<string, unknown>>
```

<ParamField path="agentId" type="string" required>
  The id of the agent the code runs in. Goals are scoped to that agent; any other id is refused with `FORBIDDEN`.
</ParamField>

<ParamField path="goal" type="object" required>
  `workflowId` or `workflow` (a name), `objective`, `judge`, and `maxRuns`, plus optional `cadence`, `evaluation`, `budget`, `maxTotalCredits`, `initialState`, `input`, `idempotencyKey`, and `workflowVersionId`. The judge is `{ agentId?, role?: { name, instructions, tools }, schema?, predicate?: { path, op, value? } }`.
</ParamField>

**Returns** — the goal record: `goalId`, `status` (`active`, `paused`, `done`, or `closed`), `runsUsed`, `iteration`, `currentRunId`, `verdict`, `pauseReason`, and the fields you set. Typed loosely as `Record<string, unknown>`.

**Example**

```ts theme={null}
import { Workflows } from 'lua-cli';

const goal = await Workflows.setGoal('agent_abc123', {
  workflow: 'reduce-backlog',
  objective: 'Fewer than 20 open tickets older than 7 days',
  judge: { predicate: { path: 'openOlderThan7Days', op: 'lt', value: 20 } },
  maxRuns: 10,
  cadence: [{ type: 'cron', expression: '0 * * * *' }],
});
console.log(goal.goalId, goal.status);
```

**Errors** — `FORBIDDEN` (403) for another agent's id, `VALIDATION_FAILED`, `GOAL_MAX_RUNS_INVALID`, `GOAL_CAP`.

### goals

Reads and controls goals. Every member takes the bound agent's id first and refuses any other id with `FORBIDDEN`.

```ts theme={null}
Workflows.goals.list(agentId: string, opts?: { status?: 'active' | 'paused' | 'done' | 'closed'; workflowId?: string }): Promise<Record<string, unknown>[]>
Workflows.goals.get(agentId: string, goalId: string): Promise<Record<string, unknown>>
Workflows.goals.pause(agentId: string, goalId: string): Promise<Record<string, unknown>>
Workflows.goals.resume(agentId: string, goalId: string): Promise<Record<string, unknown>>
Workflows.goals.close(agentId: string, goalId: string, opts?: { note?: string }): Promise<Record<string, unknown>>
```

**Returns** — goal records as `setGoal()`; `get()` adds the goal's `runs` history. `pause()` needs an `active` goal, `resume()` a `paused` one, and `close()` is terminal.

**Example**

```ts theme={null}
import { Workflows } from 'lua-cli';

const active = await Workflows.goals.list('agent_abc123', { status: 'active' });
for (const goal of active) {
  await Workflows.goals.pause('agent_abc123', String(goal.goalId));
}
```

**Errors** — `FORBIDDEN`, `GOAL_NOT_FOUND`, `GOAL_NOT_ACTIVE`.

## Deployed runtime differences

Under `lua test` every member calls the platform's REST routes and returns the typed shapes documented earlier. In deployed agents the members are served in process, and several return less than the typings declare.

* `start()` forwards `idempotencyKey`, `budget`, `workflowVersionId`, `correlationKey` (up to 200 characters), and `tags` (up to 10, each up to 64 characters). It drops `waitSeconds`, `initialState`, `replyTo`, and `onBehalfOf`, skips the 256 KB `input` check, and resolves `{ runId, status }`, with `status` `failed` when the run was recorded but could not start, for example because `input` failed the workflow's input schema.
* `get()` resolves `runId`, `workflowId`, `workflowVersionId`, `status`, `trigger`, `createdAt`, and, when set, `startedAt`, `completedAt`, `durationMs`, `gate`, `reason`, `output`, and `error`. There is no `nextAction`, `budget`, `outputPreview`, or `restricted`.
* `list()` filters by `status` and an untyped `workflowId` only; `workflow`, `correlationKey`, `tags`, and `sort` are ignored. `limit` is clamped to 1 to 200 and defaults to 50.
* `cancel()` resolves `{ status, nextAction, forceAvailableAt? }`.
* `resume()` throws code `resume_unavailable` (501) on a platform without the resume handler.
* `signal()` and `signalByKey()` throw code `signal_unavailable` (501) on a platform without the signal handler. When served, `signal()` resolves `{ accepted, reason? }` and `signalByKey()` resolves `{ runIds, delivered }` with no `results` array; a delivery the platform refuses comes back as `{ accepted: false, reason }`, not as an error.
* `startBatch()`, `setGoal()`, and `goals.*` throw code `not_implemented` (501) when the platform's optional batch or goal provider is absent, so handle that error. `startBatch()` also resolves `nameOrId` as a workflow id only, never a name.
* `raiseBudget()` always throws code `not_implemented` (501). Raise a budget with [`lua workflows raise-budget`](/reference/cli/workflows) or the [`POST /workflows/:agentId/runs/:runId/budget` route](/reference/rest/workflows) instead.

## Errors

A refusal throws an `Error` with a `code` (the platform's discriminator, as listed per method) and a `statusCode`. Under `lua test` the error's `name` is `WorkflowApiError` and it carries the detail fields the platform sent, such as `blockingRunId`. In deployed agents the `name` is `WorkflowsApiError`; detail fields are attached only when the platform's refusal carried them (`startBatch` and goals), so a `RUNS_IN_FLIGHT` from `start` has no `blockingRunId`. Neither class is exported; branch on `code`.

```ts theme={null}
import { Workflows } from 'lua-cli';

try {
  await Workflows.start('outreach', {});
} catch (error) {
  const { code, blockingRunId } = error as { code?: string; blockingRunId?: string };
  if (code === 'RUNS_IN_FLIGHT') console.log(`Already running${blockingRunId ? ` as ${blockingRunId}` : ''}`);
  else throw error;
}
```

A runtime without workflow support throws `Workflows API is not available in this runtime` with code `workflows_unavailable`.

## Types

`Workflows`, `WorkflowsApi`, and `WorkflowRunTrigger` are exported. The result shapes are not; name them from the method that returns them.

```ts theme={null}
import { Workflows } from 'lua-cli';
import type { WorkflowRunTrigger } from 'lua-cli';

type WorkflowRun = Awaited<ReturnType<typeof Workflows.get>>;
type WorkflowRunStatus = WorkflowRun['status'];
type StartResult = Awaited<ReturnType<typeof Workflows.start>>;

export function isSettled(status: WorkflowRunStatus): boolean {
  return ['completed', 'failed', 'cancelled', 'abandoned', 'timed_out'].includes(status);
}

export function startedBy(run: WorkflowRun): WorkflowRunTrigger {
  return run.trigger;
}

export function runIdOf(result: StartResult): string {
  return result.runId;
}
```

### WorkflowRunStatus

`queued`, `running`, `cancellation_requested`, `gated`, `suspended`, `waiting`, `completed`, `failed`, `cancelled`, `abandoned`, or `timed_out`.

### WorkflowRun

<ResponseField name="runId" type="string">The run's id; older responses spell it `id`.</ResponseField>
<ResponseField name="workflowId, workflowVersionId, agentId, orgId" type="string">Where the run belongs.</ResponseField>
<ResponseField name="status" type="WorkflowRunStatus">Current status.</ResponseField>
<ResponseField name="trigger" type="WorkflowRunTrigger">`chat`, `sdk`, `api`, `schedule`, `webhook`, `template`, `workflow`, or `device`.</ResponseField>
<ResponseField name="correlationKey, tags, lineageId, parentRunId" type="string | string[]">Optional identity you or a parent run gave it.</ResponseField>
<ResponseField name="gate" type="{ kind: string; reason?: string; code?: string; stepId?: string; expiresAt?: number }">Why a `gated` run holds no slot.</ResponseField>
<ResponseField name="nextAction" type="'cancel_again' | 'force' | 'none' | 'raise_budget' | 'top_up'">What the platform suggests next.</ResponseField>
<ResponseField name="cancel" type="{ requestedAt: number; requestedBy: string; forceAvailableAt: number; forcedAt?: number; forcedBy?: string; forceReason?: string }">The pending or applied cancellation.</ResponseField>
<ResponseField name="failureReason" type="string">Why a `failed` run failed.</ResponseField>
<ResponseField name="restricted" type="boolean">`true` when outputs are withheld from the caller.</ResponseField>
<ResponseField name="hasOutput, outputPreview, output" type="boolean | string | unknown">Whether a result exists, its 2 KB preview, and the full value when the view includes it.</ResponseField>
<ResponseField name="usage" type="{ creditsUsed?: number; actionsUsed?: number; steps?: number; inputTokens?: number; outputTokens?: number; metering?: 'flat' | 'priced' }">Settled spend.</ResponseField>
<ResponseField name="budget" type="{ maxCredits?: number; maxSteps?: number; maxDurationSeconds?: number; spent?: object; remaining?: number }">Caps and remaining allowance.</ResponseField>
<ResponseField name="createdAt, startedAt, completedAt, updatedAt" type="string">ISO 8601 timestamps.</ResponseField>

## See also

* [Workflow builder](/reference/sdk/workflow-builder) — define the workflows you start here
* [Operate runs](/build/workflows/operate-runs) — watch, approve, retry, and cancel runs from the CLI and the desktop
* [Approvals and signals](/build/workflows/approvals-and-signals) — how-to
* [Goals and schedules](/build/workflows/goals-and-schedules) — how-to
* [`lua workflows`](/reference/cli/workflows) — the CLI twin of every member
