diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 93607de3..3b7bfa6f 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -228,14 +228,16 @@ sleep but a durable pause.
await step.sleep("wait-one-hour", "1h");
```
-**`step.invokeWorkflow(name, options)`**: Starts a child workflow and waits for
-it durably. When the timeout is reached (default 7d), the parent step fails but
-the child workflow continues running independently.
-
-All step APIs (`step.run`, `step.sleep`, and `step.invokeWorkflow`) share the
-same collision logic for durable keys. If duplicate base names are encountered
-in one execution pass, OpenWorkflow auto-indexes them as `name`, `name:1`,
-`name:2`, and so on so each step call maps to a distinct step attempt.
+**`step.runWorkflow(spec, input?, options?)`**: Starts a child workflow and
+waits for it durably. `options.name` sets the durable step name (defaults to the
+target workflow name in `spec`) and `options.timeout` controls the wait timeout
+(default 7d). When the timeout is reached, the parent step fails but the child
+workflow continues running independently.
+
+All step APIs (`step.run`, `step.sleep`, and `step.runWorkflow`) share the same
+collision logic for durable keys. If duplicate base names are encountered in one
+execution pass, OpenWorkflow auto-indexes them as `name`, `name:1`, `name:2`,
+and so on so each step call maps to a distinct step attempt.
## 4. Error Handling & Retries
diff --git a/openworkflow/hello-world-parent.ts b/openworkflow/hello-world-parent.ts
index 25f66087..0d3bbdf9 100644
--- a/openworkflow/hello-world-parent.ts
+++ b/openworkflow/hello-world-parent.ts
@@ -2,16 +2,14 @@ import { helloWorld } from "./hello-world.js";
import { defineWorkflow } from "openworkflow";
/**
- * Example workflow that invokes hello-world as a child workflow.
+ * Example workflow that runs hello-world as a child workflow.
*/
export const helloWorldParent = defineWorkflow(
{ name: "hello-world-parent" },
async ({ step, run }) => {
console.log(`[run ${run.id}]`);
- const childResult = await step.invokeWorkflow("hello-world-child", {
- workflow: helloWorld,
- });
+ const childResult = await step.runWorkflow(helloWorld.spec);
return { childResult, parentMessage: "Hello from the parent workflow!" };
},
diff --git a/packages/dashboard/src/routes/runs/$runId.tsx b/packages/dashboard/src/routes/runs/$runId.tsx
index ed48f14b..67488ba8 100644
--- a/packages/dashboard/src/routes/runs/$runId.tsx
+++ b/packages/dashboard/src/routes/runs/$runId.tsx
@@ -53,7 +53,7 @@ export const Route = createFileRoute("/runs/$runId")({
...new Set(
steps
.map((step) =>
- step.kind === "invoke" ? step.childWorkflowRunId : null,
+ step.kind === "workflow" ? step.childWorkflowRunId : null,
)
.filter((childRunId): childRunId is string => childRunId !== null),
),
@@ -186,7 +186,7 @@ function RunDetailsPage() {
const stepTypeLabel =
step.kind === "function" ? "run" : step.kind;
const childRunId =
- step.kind === "invoke" ? step.childWorkflowRunId : null;
+ step.kind === "workflow" ? step.childWorkflowRunId : null;
const childRun = childRunId
? (childRunsById[childRunId] ?? null)
: null;
diff --git a/packages/docs/docs/child-workflows.mdx b/packages/docs/docs/child-workflows.mdx
index 45cdd159..e2a09578 100644
--- a/packages/docs/docs/child-workflows.mdx
+++ b/packages/docs/docs/child-workflows.mdx
@@ -1,10 +1,10 @@
---
title: Child Workflows
-description: Invoke workflows from other workflows and wait for their results
+description: Run workflows from other workflows and wait for their results
---
A parent workflow can start a child workflow and durably wait for its result
-using `step.invokeWorkflow()`. This lets you compose complex processes from
+using `step.runWorkflow()`. This lets you compose complex processes from
smaller, reusable workflows — like splitting an order pipeline into separate
payment and shipping workflows.
@@ -39,9 +39,8 @@ const processOrder = defineWorkflow(
});
// Start the report workflow and wait for it to finish
- const report = await step.invokeWorkflow("generate-report", {
- workflow: generateReport,
- input: { reportId: input.orderId },
+ const report = await step.runWorkflow(generateReport.spec, {
+ reportId: input.orderId,
});
await step.run({ name: "send-confirmation" }, async () => {
@@ -58,29 +57,28 @@ const processOrder = defineWorkflow(
## Timeout
By default, the parent waits up to **7 days** for the child to finish. You can
-customize this with the `timeout` option:
+customize this with `options.timeout`:
```ts
-const result = await step.invokeWorkflow("quick-task", {
- workflow: quickTaskWorkflow,
- input: { taskId: "abc" },
- timeout: "5m", // wait at most 5 minutes
-});
+const result = await step.runWorkflow(
+ quickTaskWorkflow.spec,
+ { taskId: "abc" },
+ { timeout: "5m" }, // wait at most 5 minutes
+);
```
-`timeout` accepts a [duration string](/docs/sleeping#duration-formats), a
-number of milliseconds, or a `Date`:
+`timeout` accepts a [duration string](/docs/sleeping#duration-formats), a number
+of milliseconds, or a `Date`:
```ts
// Duration string
-await step.invokeWorkflow("task", { workflow: w, timeout: "1h" });
+await step.runWorkflow(w.spec, undefined, { timeout: "1h" });
// Milliseconds
-await step.invokeWorkflow("task", { workflow: w, timeout: 60_000 });
+await step.runWorkflow(w.spec, undefined, { timeout: 60_000 });
// Absolute deadline
-await step.invokeWorkflow("task", {
- workflow: w,
+await step.runWorkflow(w.spec, undefined, {
timeout: new Date("2026-03-01"),
});
```
@@ -91,43 +89,34 @@ await step.invokeWorkflow("task", {
canceled.
-## Workflow Target
+## Workflow Spec
-The `workflow` option accepts a workflow definition, a workflow spec, or a plain
-string name:
+The first argument accepts a workflow spec:
```ts
-// Workflow definition (recommended — type-safe input/output)
-await step.invokeWorkflow("run-child", {
- workflow: myWorkflow,
- input: { key: "value" },
-});
-
-// Workflow spec
-await step.invokeWorkflow("run-child", {
- workflow: myWorkflow.spec,
- input: { key: "value" },
-});
+// From a defined workflow
+await step.runWorkflow(myWorkflow.spec, { key: "value" });
-// String name (useful when the child is defined in another package)
-await step.invokeWorkflow("run-child", {
- workflow: "my-workflow",
- input: { key: "value" },
-});
+// Or any WorkflowSpec-compatible object
+await step.runWorkflow({ name: "my-workflow" }, { key: "value" });
```
+## Step Name
+
+Set `options.name` to control the durable step name. If omitted, OpenWorkflow
+uses the target workflow name.
+
## Error Handling
-If the child workflow **fails**, the parent invoke step also fails:
+If the child workflow **fails**, the parent workflow step also fails:
```ts
const orderPipeline = defineWorkflow(
{ name: "order-pipeline" },
async ({ input, step }) => {
try {
- const result = await step.invokeWorkflow("charge", {
- workflow: chargeWorkflow,
- input: { orderId: input.orderId },
+ const result = await step.runWorkflow(chargeWorkflow.spec, {
+ orderId: input.orderId,
});
return result;
} catch (error) {
@@ -140,13 +129,13 @@ const orderPipeline = defineWorkflow(
);
```
-If the child workflow is **canceled**, the parent invoke step fails with an
+If the child workflow is **canceled**, the parent workflow step fails with an
error indicating the child was canceled.
- Invoke steps do not retry automatically. The child workflow is responsible for
- its own retries. If the child fails permanently, the error propagates to the
- parent.
+ Workflow steps do not retry automatically. The child workflow is responsible
+ for its own retries. If the child fails permanently, the error propagates to
+ the parent.
## Parallel Child Workflows
@@ -155,13 +144,11 @@ Start multiple child workflows concurrently with `Promise.all`:
```ts
const [payment, shipping] = await Promise.all([
- step.invokeWorkflow("process-payment", {
- workflow: paymentWorkflow,
- input: { orderId: input.orderId },
+ step.runWorkflow(paymentWorkflow.spec, {
+ orderId: input.orderId,
}),
- step.invokeWorkflow("prepare-shipping", {
- workflow: shippingWorkflow,
- input: { orderId: input.orderId },
+ step.runWorkflow(shippingWorkflow.spec, {
+ orderId: input.orderId,
}),
]);
```
diff --git a/packages/docs/docs/roadmap.mdx b/packages/docs/docs/roadmap.mdx
index 354822fa..f97bc187 100644
--- a/packages/docs/docs/roadmap.mdx
+++ b/packages/docs/docs/roadmap.mdx
@@ -19,7 +19,7 @@ description: What's coming next for OpenWorkflow
- ✅ Configurable retry policies
- ✅ Idempotency keys
- ✅ Prometheus `/metrics` endpoint
-- ✅ Child workflows (`step.invokeWorkflow`)
+- ✅ Child workflows (`step.runWorkflow`)
## Coming Soon
diff --git a/packages/docs/docs/steps.mdx b/packages/docs/docs/steps.mdx
index 05843a5d..3163bac3 100644
--- a/packages/docs/docs/steps.mdx
+++ b/packages/docs/docs/steps.mdx
@@ -136,16 +136,16 @@ Pauses the workflow until a specified duration has elapsed. See
await step.sleep("wait-one-hour", "1h");
```
-### `step.invokeWorkflow()`
+### `step.runWorkflow()`
Starts a child workflow and waits for its result durably:
```ts
-const childOutput = await step.invokeWorkflow("generate-report", {
- workflow: generateReportWorkflow,
- input: { reportId: input.reportId },
- timeout: "5m", // optional, defaults to 7 days
-});
+const childOutput = await step.runWorkflow(
+ generateReportWorkflow.spec,
+ { reportId: input.reportId },
+ { timeout: "5m" }, // optional, defaults to 7 days
+);
```
## Retry Policy (Optional)
diff --git a/packages/docs/docs/workflows.mdx b/packages/docs/docs/workflows.mdx
index 73db03d4..833e55c4 100644
--- a/packages/docs/docs/workflows.mdx
+++ b/packages/docs/docs/workflows.mdx
@@ -213,12 +213,12 @@ create a separate run.
The workflow function receives an object with four properties:
-| Parameter | Type | Description |
-| --------- | --------------------- | ------------------------------------------------------------------------ |
-| `input` | Generic | The input data passed when starting the workflow |
-| `step` | `StepApi` | API for defining steps (`step.run`, `step.sleep`, `step.invokeWorkflow`) |
-| `version` | `string \| null` | The workflow version, if specified |
-| `run` | `WorkflowRunMetadata` | Read-only run metadata snapshot (`run.id`, etc.) |
+| Parameter | Type | Description |
+| --------- | --------------------- | --------------------------------------------------------------------- |
+| `input` | Generic | The input data passed when starting the workflow |
+| `step` | `StepApi` | API for defining steps (`step.run`, `step.sleep`, `step.runWorkflow`) |
+| `version` | `string \| null` | The workflow version, if specified |
+| `run` | `WorkflowRunMetadata` | Read-only run metadata snapshot (`run.id`, etc.) |
```ts
defineWorkflow({ name: "example" }, async ({ input, step, version, run }) => {
@@ -240,7 +240,7 @@ A workflow run progresses through these states:
| ----------- | -------------------------------------------------------------- |
| `pending` | Created and waiting for a worker to claim it |
| `running` | Actively being executed by a worker |
-| `sleeping` | Paused while waiting for `step.sleep` or `step.invokeWorkflow` |
+| `sleeping` | Paused while waiting for `step.sleep` or `step.runWorkflow` |
| `completed` | Finished successfully |
| `failed` | Failed after exhausting retries, hitting deadline, or step cap |
| `canceled` | Explicitly canceled and will not continue |
diff --git a/packages/openworkflow/client/client.ts b/packages/openworkflow/client/client.ts
index 24844d6a..61b09a0a 100644
--- a/packages/openworkflow/client/client.ts
+++ b/packages/openworkflow/client/client.ts
@@ -86,7 +86,7 @@ export class OpenWorkflow {
* @returns Handle for awaiting the result
* @example
* ```ts
- * const handle = await ow.runWorkflow(emailWorkflow, { to: 'user@example.com' });
+ * const handle = await ow.runWorkflow(emailWorkflow.spec, { to: 'user@example.com' });
* const result = await handle.result();
* ```
*/
diff --git a/packages/openworkflow/core/step-attempt.test.ts b/packages/openworkflow/core/step-attempt.test.ts
index 62d4f38a..ce5f207f 100644
--- a/packages/openworkflow/core/step-attempt.test.ts
+++ b/packages/openworkflow/core/step-attempt.test.ts
@@ -6,7 +6,7 @@ import {
normalizeStepOutput,
calculateDateFromDuration,
createSleepContext,
- createInvokeContext,
+ createWorkflowContext,
} from "./step-attempt.js";
import type { StepAttempt, StepAttemptCache } from "./step-attempt.js";
import { describe, expect, test } from "vitest";
@@ -318,22 +318,22 @@ describe("createSleepContext", () => {
});
});
-describe("createInvokeContext", () => {
- test("creates invoke context with timeout", () => {
+describe("createWorkflowContext", () => {
+ test("creates workflow context with timeout", () => {
const timeoutAt = new Date("2025-06-15T10:30:00.000Z");
- const context = createInvokeContext(timeoutAt);
+ const context = createWorkflowContext(timeoutAt);
expect(context).toEqual({
- kind: "invoke",
+ kind: "workflow",
timeoutAt: "2025-06-15T10:30:00.000Z",
});
});
- test("creates invoke context with null timeout", () => {
- const context = createInvokeContext(null);
+ test("creates workflow context with null timeout", () => {
+ const context = createWorkflowContext(null);
expect(context).toEqual({
- kind: "invoke",
+ kind: "workflow",
timeoutAt: null,
});
});
diff --git a/packages/openworkflow/core/step-attempt.ts b/packages/openworkflow/core/step-attempt.ts
index dd3aae6f..888120ac 100644
--- a/packages/openworkflow/core/step-attempt.ts
+++ b/packages/openworkflow/core/step-attempt.ts
@@ -7,7 +7,7 @@ import { err, ok } from "./result.js";
/**
* The kind of step in a workflow.
*/
-export type StepKind = "function" | "sleep" | "invoke";
+export type StepKind = "function" | "sleep" | "workflow";
/**
* Status of a step attempt through its lifecycle.
@@ -27,10 +27,10 @@ export interface SleepStepAttemptContext {
}
/**
- * Context for an invoke step attempt.
+ * Context for a workflow step attempt.
*/
-export interface InvokeStepAttemptContext {
- kind: "invoke";
+export interface WorkflowStepAttemptContext {
+ kind: "workflow";
timeoutAt: string | null;
}
@@ -39,7 +39,7 @@ export interface InvokeStepAttemptContext {
*/
export type StepAttemptContext =
| SleepStepAttemptContext
- | InvokeStepAttemptContext;
+ | WorkflowStepAttemptContext;
/**
* StepAttempt represents a single attempt of a step within a workflow.
@@ -159,15 +159,15 @@ export function createSleepContext(
}
/**
- * Create the context object for an invoke step attempt.
+ * Create the context object for a workflow step attempt.
* @param timeoutAt - Parent wait timeout deadline, or null for no timeout
- * @returns The context object for an invoke step
+ * @returns The context object for a workflow step
*/
-export function createInvokeContext(
+export function createWorkflowContext(
timeoutAt: Readonly | null,
-): InvokeStepAttemptContext {
+): WorkflowStepAttemptContext {
return {
- kind: "invoke" as const,
+ kind: "workflow" as const,
timeoutAt: timeoutAt?.toISOString() ?? null,
};
}
diff --git a/packages/openworkflow/core/workflow-function.ts b/packages/openworkflow/core/workflow-function.ts
index 79be0a65..01745679 100644
--- a/packages/openworkflow/core/workflow-function.ts
+++ b/packages/openworkflow/core/workflow-function.ts
@@ -1,9 +1,5 @@
import type { DurationString } from "./duration.js";
-import type {
- RetryPolicy,
- Workflow,
- WorkflowSpec,
-} from "./workflow-definition.js";
+import type { RetryPolicy, WorkflowSpec } from "./workflow-definition.js";
import type { WorkflowRun } from "./workflow-run.js";
/**
@@ -31,41 +27,35 @@ export type StepFunction