Skip to content

Writing workflows

The workflow definition and the context passed to every run.

A workflow is one TypeScript file exporting a defineWorkflow call. The trigger lives with the handler — there is no separate registration step.

import { defineWorkflow, manual } from "steerium";
export default defineWorkflow({
name: "summarize",
on: manual(),
timeoutMs: 5 * 60_000, // optional; default 5 min
concurrency: 1, // optional; default 1
queue: 10, // optional; events past this bound are dropped
tags: ["content"], // optional; shown in listings
async run(ctx) {
// ordinary Node — import whatever you need
},
});

Workflows are loaded at runtime with jiti — you never build them.

Inside run(ctx):

MemberWhat it is
ctx.eventthe typed, persisted event that caused this run
ctx.agent.run(opts)AI/agent call: provider, prompt, system, model, maxTokens, cwd, and for agent providers permissionMode, allowedTools, outputSchema
ctx.step(name, fn)wrap a stage; status, output, and logs land in the run record and UI
ctx.artifactwriteText / writeJSON / writeBytes — files under this run’s artifact directory
ctx.connector(name)resolved connector config (secrets included)
ctx.statepersistent key/value state private to this workflow — cursors, “last processed” markers
ctx.kv(name)a named key/value namespace shared across the scope — how workflows (and triggers) hand state to each other; approvals are built on it
ctx.scopewhere you’re running — scope.cwd is the repo for project workflows
ctx.loggerstructured logger bound to this run
ctx.runIdthe run’s id
ctx.signalAbortSignal — aborts on timeout or cancellation

ctx.step is bookkeeping, not durability: each step records its status, JSON-serialized return value, and captured logs to the run record, which is what the UI renders live during long runs. Steps are not checkpoints — a replayed run starts from the top.

const plan = await ctx.step("plan", () =>
ctx.agent.run({ provider: "openai", prompt: "..." }),
);
await ctx.step("implement", () =>
ctx.agent.run({ provider: "claude", prompt: plan.text }),
);

Concurrency and queueing. concurrency caps simultaneous runs of a workflow in its scope (default 1). Events over the cap wait in a bounded per-workflow FIFO (queue, default 10) and run as slots free; only events past the bound are dropped, with a warning. queue: 0 means strict drop-on-overlap.

Timeouts. timeoutMs (default 5 min) aborts a run via its AbortSignal; providers forward it so in-flight HTTP calls and agent subprocesses actually stop. Give coding-agent workflows a longer leash (e.g. 45 min).

Cancellation. steerium cancel <runId> (or the UI’s cancel button) aborts an executing run through the same signal path as the timeout.

Crash recovery. On startup the daemon marks runs left running by a crashed process as error: interrupted — no phantom in-progress runs.

No automatic retries — deliberately. Agents mutate state (commits, comments, branches); blind retries duplicate side effects. steerium replay <runId> is the deliberate, manual re-run against the exact stored event.

Multi-stage pipelines, structured output, parameterized manual runs, chaining workflows, and tuning concurrency are covered in advanced workflows.