Step context
Every loop’s run() receives a step object as its second argument. step exposes two methods — do for checkpointed work, waitForScan for suspending until a scan completes. Inside a step.do callback, you also receive a context object (ctx) that gives that step access to the platform’s built-in connectors.
Step
type Step = {
do<T>(
name: string,
fn: (args: { ctx: StepCtx }) => Promise<T> | T,
): Promise<T>;
waitForScan<T = unknown>(opts: { timeout: string }): Promise<T>;
};Methods
| Method | Description |
|---|---|
do | Run a piece of work as a named, checkpointed unit. The callback’s return value is persisted and replayed if the step has already succeeded. |
waitForScan | Suspend the run until the scan provisioned in an earlier step completes, or the timeout fires. Returns the scan payload as the return value. |
Step names must be unique within a single run() and stable across deploys — they’re the identifier the platform uses to match a step against its replay record. For the full durability model, see Runtime and organizations.
StepCtx
type StepCtx = {
FACIAL_SCAN: FacialScanConnector;
};Properties
| Property | Type | Description |
|---|---|---|
FACIAL_SCAN | FacialScanConnector | The built-in facial scan connector, scoped to the current run. See Facial scan. |
ctx is only available inside a step.do callback. Outside of a step — at the top of run(), between steps, after return — it doesn’t exist.
Access the context
Destructure ctx from the step callback’s argument:
await step.do("Generate a health scan", async ({ ctx }) => {
await ctx.FACIAL_SCAN.create({
expiresIn: 3600,
metadata: { patientId },
});
});If your step doesn’t need a connector, you can ignore the argument entirely:
await step.do("Compute total", async () => {
return items.reduce((sum, item) => sum + item.price, 0);
});Examples
Provision a scan, then wait for it
step.do kicks off the scan; step.waitForScan parks the run until results land:
await step.do("Generate a health scan", async ({ ctx }) => {
await ctx.FACIAL_SCAN.create({
expiresIn: 3600,
metadata: { patientId, phoneNumber },
});
});
const scan = await step.waitForScan({ timeout: "30 minutes" });While suspended, the loop isn’t consuming compute. When the scan completes, the run resumes from the waitForScan line with the payload as its return value.
Peek at scan state without waiting
For flows that want to short-circuit when a scan has already failed, read state directly with getResults:
const { status } = await step.do("Check scan", async ({ ctx }) => {
return ctx.FACIAL_SCAN.getResults();
});
if (status === "failed") {
return { error: "scan failed before processing" };
}Carry per-run context through the scan
Anything passed as metadata to FACIAL_SCAN.create is echoed back on completion, so use it for IDs you’ll need on the other side instead of refetching them:
await step.do("Generate a health scan", async ({ ctx }) => {
await ctx.FACIAL_SCAN.create({
expiresIn: 3600,
metadata: { patientId, phoneNumber, source: event.body.source },
});
});
const scan = await step.waitForScan<{ metadata: { patientId: string } }>({
timeout: "30 minutes",
});
console.log(scan.metadata.patientId); // same patientId you put in