Runs & streaming
Run lifecycle, streamed events, and cost control.
Starting a run executes a workflow graph. Because runs can take a while (model inference, multi-step graphs), the API streams progress events instead of blocking on a single response.
Run events
A run emits a stream of events, a discriminated union on type:
| Event | Payload | Meaning |
|---|---|---|
step.started | nodeId | A node began executing |
step.progress | nodeId, message | Progress within a node |
step.completed | nodeId, usage: { costUsd, ms } | Node finished, with cost/time |
step.failed | nodeId, error: { code, message } | Node failed |
run.completed | outputs: Record<string, Artifact> | Terminal — the results |
run.failed | error: { code, message } | Terminal — the run failed |
Read the stream to completion and collect run.completed.outputs. Treat
run.failed and step.failed as terminal: surface the error message — don't
retry blindly.
Over HTTP the stream is Server-Sent Events; the SDK parses it into an async iterable for you:
const handle = await startRun(client, "abc123@latest", inputs);
for await (const event of handle.events()) {
if (event.type === "step.completed") {
console.log(
`${event.nodeId}: $${event.usage.costUsd} in ${event.usage.ms}ms`,
);
}
if (event.type === "run.completed") return event.outputs;
if (event.type === "run.failed") throw new Error(event.error.message);
}Cost control
Runs spend money (model inference). Two layers protect you:
- Your cap — pass
maxCostUsdon every run. If the accumulated step cost would exceed it, the run fails instead of overspending. - The server ceiling — the platform enforces its own limits regardless of
what the client asks for: currently a $0.50 default when
maxCostUsdis omitted, and a $1.00 per-run ceiling that client requests are capped to.
{ "workflowRef": "abc123@latest", "inputs": { … }, "maxCostUsd": 0.25 }Practical habits:
- Set your own
maxCostUsdlower than the ceiling so a bug can't burn budget. - Generate at the smallest useful size / fewest variations; scale up winners.
- Persist output
refURLs and don't regenerate assets you already have. - Watch
step.completed.usage.costUsdto learn what each node actually costs.
Single-node runs
For one-off inference you don't need a workflow at all — runs.node executes a
single node by id with plain inputs and returns its outputs directly (no
stream). Available through the SDK and the
runs_node MCP tool.