Skip to Content
Evaluate Get Started

Subprocess

A subprocess is a child process instance that runs as part of a parent process's execution. The parent creates it through a special transition, and the child runs independently. A finishing transition can copy selected parameters back and continue the parent.

What a subprocess is

A subprocess is defined in the same scheme XML as its parent. WorkflowRuntime builds the child process definition from the activities and transitions between two subprocess boundaries:

  • A ForkStart transition goes from a parent activity to the subprocess's first activity. When its trigger fires, WorkflowRuntime creates a child ProcessInstance with IsSubprocess = true and ParentProcessId pointing to the parent. The parent stays in place while the child runs.
  • A ForkEnd transition goes from the subprocess's last activity to a parent-level merge activity. It finalizes the child, merges parameters according to the transition's strategy, and continues the parent according to the selected merge mode.

The following scheme models an estimate that needs two independent approvals - one from a manager and one from a financial director:

EstimateApproval.xml
<Process Name="EstimateApproval">    <Commands>        <Command Name="Submit"/>        <Command Name="ManagerSign"/>        <Command Name="FinDirectorSign"/>        <Command Name="Approve"/>        <Command Name="Reject"/>    </Commands>    <Activities>        <Activity Name="Created" IsInitial="True">            <Designer X="50" Y="150"/>        </Activity>        <Activity Name="AwaitingApprovals">            <Designer X="330" Y="150"/>        </Activity>        <Activity Name="ManagerSignOff">            <Designer X="210" Y="330"/>        </Activity>        <Activity Name="FinDirectorSignOff">            <Designer X="440" Y="330"/>        </Activity>        <Activity Name="CollectingSignOffs">            <Designer X="320" Y="540"/>        </Activity>        <Activity Name="ReadyForDecision">            <Designer X="300" Y="690"/>        </Activity>        <Activity Name="Approved" IsFinal="True">            <Designer X="510" Y="800"/>        </Activity>        <Activity Name="Rejected" IsFinal="True">            <Designer X="60" Y="800"/>        </Activity>    </Activities>    <Transitions>        <Transition Name="SubmitForApproval"                    From="Created" To="AwaitingApprovals" Classifier="Direct">            <Triggers>                <Trigger Type="Command" NameRef="Submit"/>            </Triggers>            <Conditions>                <Condition Type="Always"/>            </Conditions>            <Designer X="265" Y="179"/>        </Transition>        <Transition Name="FinalApprove"                    From="ReadyForDecision" To="Approved" Classifier="Direct">            <Triggers>                <Trigger Type="Command" NameRef="Approve"/>            </Triggers>            <Conditions>                <Condition Type="Always"/>            </Conditions>        </Transition>        <Transition Name="FinalReject"                    From="ReadyForDecision" To="Rejected" Classifier="Direct">            <Triggers>                <Trigger Type="Command" NameRef="Reject"/>            </Triggers>            <Conditions>                <Condition Type="Always"/>            </Conditions>        </Transition>        <Transition Name="StartManagerReview" From="AwaitingApprovals"                    To="ManagerSignOff" Classifier="Direct" IsFork="true"                    SubprocessInOutDefinition="Start"                    DisableParentStateControl="false">            <Triggers>                <Trigger Type="Auto"/>            </Triggers>            <Conditions>                <Condition Type="Always"/>            </Conditions>            <Designer X="277" Y="269"/>        </Transition>        <Transition Name="StartFinDirectorReview"                    From="AwaitingApprovals" To="FinDirectorSignOff"                    Classifier="Direct" IsFork="true"                    SubprocessInOutDefinition="Start"                    DisableParentStateControl="false">            <Triggers>                <Trigger Type="Auto"/>            </Triggers>            <Conditions>                <Condition Type="Always"/>            </Conditions>            <Designer X="504" Y="277"/>        </Transition>        <Transition Name="ManagerSigned" From="ManagerSignOff"                    To="CollectingSignOffs" Classifier="Direct" IsFork="true"                    SubprocessInOutDefinition="Finalize"                    MergeViaSetState="false"                    SubprocessFinalizeParameterMergeStrategy="OverwriteAll">            <Triggers>                <Trigger Type="Command" NameRef="ManagerSign"/>            </Triggers>            <Conditions>                <Condition Type="Always"/>            </Conditions>            <Designer X="276" Y="484"/>        </Transition>        <Transition Name="FinDirectorSigned" From="FinDirectorSignOff"                    To="CollectingSignOffs" Classifier="Direct" IsFork="true"                    SubprocessInOutDefinition="Finalize"                    MergeViaSetState="false"                    SubprocessFinalizeParameterMergeStrategy="OverwriteAll">            <Triggers>                <Trigger Type="Command" NameRef="FinDirectorSign"/>            </Triggers>            <Conditions>                <Condition Type="Always"/>            </Conditions>            <Designer X="510" Y="487"/>        </Transition>        <Transition Name="AllApprovalsCollected"                    From="CollectingSignOffs" To="ReadyForDecision"                    Classifier="Direct">            <Triggers>                <Trigger Type="Auto"/>            </Triggers>            <Conditions>                <Condition Type="Action"                           NameRef="CheckAllSubprocessesCompleted"/>            </Conditions>        </Transition>    </Transitions></Process>

Estimate approval scheme with two parallel subprocesses that merge before the final decision

Eight transitions exist in this scheme. Two are ForkStart, two are ForkEnd:

  • StartManagerReview and StartFinDirectorReview are Auto-triggered ForkStart transitions. When the process enters AwaitingApprovals, the runtime creates one child at ManagerSignOff and another at FinDirectorSignOff.
  • ManagerSigned and FinDirectorSigned are command-triggered ForkEnd transitions with MergeViaSetState="false". Each completed child is removed after its parameters are merged. The runtime then evaluates Auto transitions from CollectingSignOffs without forcing the parent into that activity.
  • AllApprovalsCollected uses the Basic Plugin condition CheckAllSubprocessesCompleted. It cannot move the parent to ReadyForDecision while either child still exists. After the second approval finishes, the condition passes and Approve and Reject become available to the parent.

One subprocess can therefore complete without deciding the parent's outcome. The parent reaches ReadyForDecision only after both approvals finish.

Two or more subprocesses can run in parallel from the same parent activity. Each is a fully independent process instance with its own process ID.

Merge modes and parent state control

MergeViaSetState is configured on a ForkEnd transition:

  • With MergeViaSetState="false" (the default), the runtime merges parameters, removes the completed child, and evaluates Auto transitions outgoing from the ForkEnd target activity. The parent changes activity only if one of those transitions executes. This mode supports merge conditions such as CheckAllSubprocessesCompleted.
  • With MergeViaSetState="true", the runtime merges parameters, removes the completed child, and directly sets the parent to the ForkEnd target activity. It does not wait for sibling subprocesses, so use it only when that child is allowed to determine the parent's next activity.

DisableParentStateControl is a separate ForkStart setting. With its default value, false, the runtime calculates which parent activities allow the child to exist and removes the child if the parent leaves them. Setting it to true disables that automatic cleanup, so changing the parent activity does not remove the child. It does not change how the child merges.

How subprocesses connect

A subprocess relates a parent process instance to one or more child instances. Each child has its own process ID, which you use with methods such as GetAvailableCommandsAsync and ExecuteCommandAsync.

The lifecycle:

  1. The parent fires a ForkStart transition (auto, command, or timer).
  2. WorkflowRuntime creates a child ProcessInstance and copies parameters according to the ForkStart strategy.
  3. The child runs independently. Users or systems interact with it via its own process ID.
  4. A ForkEnd transition finalizes the child and merges parameters according to the ForkEnd strategy.
  5. The runtime either evaluates an Auto transition for the parent or sets its activity directly, depending on MergeViaSetState.

A child does not need a ForkEnd transition. Without one, it remains active until it reaches an activity marked IsFinal, when the runtime removes it without merging parameters into the parent. It can also be removed earlier when the parent leaves its allowed activities, unless DisableParentStateControl="true" disables that cleanup.

Each child has ParentProcessId (the immediate parent's ID) and RootProcessId (the top-level root process ID, useful when subprocesses are nested).

When subprocesses matter

  • Parallel approval: multiple approvers each get their own subprocess, each runs independently, the parent waits for all to complete.
  • Delegated task: a manager delegates a review to a subordinate; the subordinate's work runs as a subprocess.
  • Nested workflows: a top-level order process spawns subprocesses for fulfillment, invoicing, and shipping.

See also

Frequently asked questions

Does a subprocess use a separate scheme or share the parent's scheme?

You define the subprocess's activities and transitions in the parent scheme XML. ForkStart and ForkEnd transitions delimit its boundaries, and the runtime derives the child process definition from that region. You do not author a separate subprocess scheme.

Can I nest subprocesses inside other subprocesses?

Yes. A subprocess can have its own ForkStart and ForkEnd transitions, creating additional child instances. Each child tracks ParentProcessId and RootProcessId for navigation through the tree.

How do parameters flow from a subprocess back to the parent?

The ForkStart parameter copy strategy controls which parent parameters the child receives. When a ForkEnd transition finishes the child, its merge strategy can overwrite all parent values, overwrite only missing or null values, overwrite a specified list, or overwrite all except a specified list. MergeViaSetState controls the parent's next activity, not which parameters are copied.

How is a ForkStart transition triggered?

A ForkStart transition can use any trigger type: auto (fires immediately when the activity is entered), command (requires a user or system to execute it), or timer (fires after a delay). Auto-triggered ForkStart transitions are the most common pattern for creating subprocesses without user interaction.

Can a parent activity spawn multiple subprocesses?

Yes. Multiple ForkStart transitions from the same parent activity create independent child instances. Each child receives its own process ID and runs separately. The parent stays in place and can wait for all children to complete before advancing.