Skip to main content

runWorkflow

Use workflow.runWorkflow() to invoke an imported WorkflowDefinition<Input, Output> from another workflow. It reuses the parent WorkflowContext, making it suitable for composing reusable workflows as part of a larger workflow.

This is distinct from a project entrypoint. An entrypoint is a flat target that Desktop, CLI, and Server can start directly. runWorkflow() composes business logic inside one run. Running an entrypoint never runs the default or another entrypoint automatically. When a main flow needs to invoke a child flow, the selected entrypoint's workflow explicitly calls runWorkflow(childWorkflow, ...).

Signature

workflow.runWorkflow<Input, Output>(
workflow: WorkflowDefinition<Input, Output>,
input: Input,
context: WorkflowContext,
options?: {
name?: string;
metadata?: Partial<WorkflowNodeMetadata>;
conversationMode?: "shared" | "shared-readonly";
kvMode?: "shared" | "isolated";
outputVisibility?: "visible" | "hidden";
emitNode?: boolean;
maxDepth?: number;
onOutputChunk?: (chunk: unknown, event: NodeExecutionHookChunkEvent) => void | Promise<void>;
onOutput?: (output: Output, event?: NodeExecutionHookFinishEvent) => void | Promise<void>;
},
): Promise<Output>

Parameters

ParameterTypeDescription
workflowWorkflowDefinition<Input, Output>The child workflow to reuse.
inputInputInput passed to the child workflow.
contextWorkflowContextThe current context of the parent workflow.
options.namestringDisplay title for the child-workflow call in the parent trace.
options.metadataPartial<WorkflowNodeMetadata>Overrides trace-node metadata. The default type is workflow.
options.conversationMode"shared" | "shared-readonly"How the child workflow uses the parent conversation. Defaults to shared.
options.kvMode"shared" | "isolated"How the child workflow uses parent KV. Defaults to shared; isolated prefixes project and conversation KV keys with the child workflow name.
options.outputVisibility"visible" | "hidden"Whether child-workflow output enters the parent's visible output. Defaults to visible.
options.emitNodebooleanWhether to add a run-workflow wrapper node to the parent trace. Defaults to true; top-level executor entries do not use this wrapper path.
options.maxDepthnumberMaximum nested runWorkflow depth. Defaults to 16 to prevent recursive or cyclic calls.
options.onOutputChunk(chunk, event) => voidReceives child output-stream chunks, which a parent output node can forward unchanged.
options.onOutput(output, event?) => voidReceives non-streaming child output, or its final return value when the child has no output node.

Context reuse

By default, the child workflow uses the same storage, conversation, tokenUsage, files, providers, abortSignal, and nodeHooks. It does not create a standalone run report: the runWorkflow call and child-node events are both included in the parent workflow trace. The ordinary top-level execution entry, safeRunWorkflow, calls workflow.run(input, context) directly and does not add a synthetic run-workflow wrapper node at the root.

Workflows may nest runWorkflow() calls, but the default total depth is limited to 16. This depth exists only in the execution trace. It does not create a nested entrypoint path or change the entrypoint identity used by APIs, permissions, or history.

conversationMode: "shared-readonly" gives a child workflow a read-only snapshot of the parent conversation. appendMessage, setValue, and setTitle do not write back to the parent session. Let the parent workflow append user and assistant transcripts and set the title when you need to avoid duplicate writes.

conversationMode controls only context.conversation. When a child workflow writes local or server project/conversation KV, also use kvMode: "isolated" to prefix keys at both locations with the child workflow name. This option isolates ordinary KV only; it does not change PersistentValue or related-project KV.

Execution records and output

The runtime adds a run-workflow wrapper node to the parent trace by default. name and metadata only override that node's display title and metadata. The completion event's executionInfo contains:

{
"workflowName": "openai-agents",
"conversationMode": "shared-readonly",
"kvMode": "isolated",
"outputVisibility": "hidden",
"outputBridge": true
}

Both runWorkflow(child, input, context) and runWorkflow(child, input, context, { name }) keep conversationMode: "shared" and outputVisibility: "visible". Adding a trace label alone does not change session writes or output visibility. Pass conversationMode: "shared-readonly" and outputVisibility: "hidden" explicitly when the parent must manage the conversation and output itself.

To display a called workflow's output unchanged, invoke runWorkflow from a parent output node's stream function and provide onOutputChunk and onOutput. onOutputChunk runs for every output chunk from a streaming child node. onOutput runs when a non-streaming child output node finishes, or when a child without an output node returns its final payload.

onOutputChunk and onOutput are part of execution semantics. If either handler throws, runWorkflow fails early and stops bridging later output. The error metadata includes outputBridgeFailed: true.

Static structural analysis recognizes workflow.runWorkflow(childWorkflow, input, context, options) as a run-workflow execution node. Dynamically assembled workflow references still run, but Diagram can show them only as expressions.

Example

import { openaiAgentsWorkflow } from "../openai-agents/index";

const agentOutput = await workflow.runWorkflow(
openaiAgentsWorkflow,
{
message: input.query,
defaultTools: false,
memoryTools: false,
testTool: false,
writeConversation: false,
writeAgentState: false,
customTools: deltaforceTools,
},
context,
{
name: "OpenAI Agents core",
conversationMode: "shared-readonly",
kvMode: "isolated",
outputVisibility: "hidden",
},
);

return workflow.runNode(parentOutputNode, {
query: input.query,
answer: agentOutput.items[0]?.content,
}, context);

Example: streaming bridge

const outputNode = workflow.createOutputNode({
name: "output",
async *stream(input, context) {
// createAsyncQueue is an illustrative helper. Implement it in your
// workflow or replace it with an existing async iterable or queue utility.
const queue = createAsyncQueue();

const childRun = workflow.runWorkflow(childWorkflow, input, context, {
conversationMode: "shared-readonly",
kvMode: "isolated",
outputVisibility: "hidden",
onOutputChunk(chunk) {
queue.push(chunk);
},
onOutput(output) {
queue.push(output);
},
}).finally(() => queue.end());

yield* queue;
await childRun;
},
format(stream) {
return workflow.createOutputPayload({ items: stream.items });
},
});

Errors

SituationError typeDescription
Child workflow throwsworkflow_executionThe runtime wraps the original error and retains the child workflow name in metadata.
Execution was aborted before it beganexecution_abortedDerived from the parent context.abortSignal.
Nesting is too deepworkflow_executionAborts when maxDepth is exceeded, preventing stack overflow from recursive or cyclic calls.
Output-bridge handler throwsstream_interrupted or workflow_executionmetadata.outputBridgeFailed is true, and the handler error is retained as the cause.