@loopstack/run-sub-workflow-example
A module for the Loopstack AI automation framework.
This module provides an example demonstrating how to execute child workflows from within a parent workflow for hierarchical workflow composition.
Overview
The Run Sub Workflow Example shows how to build workflows that spawn and manage child workflows asynchronously. It demonstrates a parent workflow that starts sub-workflows, tracks their completion through a callback mechanism, and receives result data from the child workflows.
By using this example as a reference, you’ll learn how to:
- Use constructor injection and
.run()to start child workflows asynchronously - Set up callback transitions to handle sub-workflow completion
- Define callback schemas with
CallbackSchema.extend()to type callback payloads - Return structured data from sub-workflows via the return value of the final
@Transitionmethod - Control how a child appears in the parent’s run view via the
showoption ('inline','link','hidden') - Compose complex workflows from smaller, reusable workflow components
- Chain multiple sequential sub-workflow executions
This example is essential for developers building workflows that need to orchestrate multiple workflow executions or break down complex processes into manageable sub-workflows.
Installation
npm install @loopstack/run-sub-workflow-exampleThen register the module in your app:
import { StudioApp } from '@loopstack/common';
import { RunSubWorkflowExampleModule, RunSubWorkflowExampleParentWorkflow } from '@loopstack/run-sub-workflow-example';
@StudioApp({
title: 'Sub-Workflow Example',
workflows: [RunSubWorkflowExampleParentWorkflow],
})
@Module({
imports: [RunSubWorkflowExampleModule],
})
export class MyAppModule {}How It Works
Workflows
Parent Workflow
The parent workflow injects the sub-workflow class and calls .run() to start it:
@Workflow({ uiConfig: __dirname + '/run-sub-workflow-example-parent.ui.yaml' })
export class RunSubWorkflowExampleParentWorkflow extends BaseWorkflow {
constructor(private readonly subWorkflow: RunSubWorkflowExampleSubWorkflow) {
super();
}
}Sub-Workflow
The sub-workflow returns structured data from its final @Transition method. This data is delivered to the parent workflow via the callback payload:
@Workflow({ uiConfig: __dirname + '/run-sub-workflow-example-sub.ui.yaml' })
export class RunSubWorkflowExampleSubWorkflow extends BaseWorkflow {
@Transition({ to: 'end' })
async message(): Promise<{ message: string }> {
await this.documentStore.save(MessageDocument, {
role: 'assistant',
text: 'Sub workflow completed.',
});
return { message: 'Hi mom!' };
}
}Key Concepts
1. Running a Sub-Workflow with Callbacks
Use .run() on the injected workflow to start it asynchronously. Pass a callback option specifying which transition to trigger when the sub-workflow completes, and a show option to control how the child appears in the parent’s run view:
@Transition({ to: 'sub_workflow_started' })
async runWorkflow() {
await this.subWorkflow.run(
{},
{ callback: { transition: 'subWorkflowCallback' }, show: 'link', label: 'Sub-Workflow' },
);
}2. The show Option
show controls how the child sub-workflow is rendered inside the parent’s run view:
'inline'(default) — embed the child as an inline iframe in the parent’s view. Best for HITL/OAuth/interactive children.'link'— show a status link card; clicking opens the child in a separate window. Best for autonomous children the parent just tracks.'hidden'— no UI at all. Best for background fan-out.
This example uses show: 'link' because the sub-workflows run autonomously without needing user interaction.
3. Defining Callback Schemas
Extend CallbackSchema to type the callback payload. The base schema includes workflowId, and you add your custom data shape:
const SubWorkflowCallbackSchema = CallbackSchema.extend({
data: z.object({ message: z.string() }),
});
type SubWorkflowCallback = z.infer<typeof SubWorkflowCallbackSchema>;4. Handling the Callback
Define a transition with wait: true and the callback schema. The payload contains both the workflowId and the data returned by the sub-workflow:
@Transition({ from: 'sub_workflow_started', to: 'sub_workflow_ended', wait: true, schema: SubWorkflowCallbackSchema })
async subWorkflowCallback(payload: SubWorkflowCallback) {
await this.documentStore.save(MessageDocument, {
role: 'assistant',
text: `A message from sub workflow 1: ${payload.data.message}`,
});
}5. Chaining Multiple Sub-Workflows
The parent workflow demonstrates running two sub-workflows sequentially. After the first callback completes, a second sub-workflow is started with its own callback. The second callback uses a terminal @Transition to end the parent workflow:
@Transition({ from: 'sub_workflow_ended', to: 'sub_workflow2_started' })
async runWorkflow2() {
await this.subWorkflow.run(
{},
{ callback: { transition: 'subWorkflow2Callback' }, show: 'link', label: 'Sub-Workflow 2' },
);
}
@Transition({ from: 'sub_workflow2_started', to: 'end', wait: true, schema: SubWorkflowCallbackSchema })
async subWorkflow2Callback(payload: SubWorkflowCallback) {
await this.documentStore.save(MessageDocument, {
role: 'assistant',
text: `A message from sub workflow 2: ${payload.data.message}`,
});
}Dependencies
This workflow uses the following Loopstack modules:
@loopstack/common- Core workflow/runtime APIs (BaseWorkflow,@Workflow,@Transition,CallbackSchema,MessageDocument)
About
Author: Jakob Klippel
License: MIT
Additional Resources
- Loopstack Documentation
- Getting Started with Loopstack
- Find more Loopstack examples in the Loopstack Registry