Structure
Structural elements group flow nodes into reusable scopes, invoke other processes, and iterate over collections.
| Element | When to use it |
|---|---|
| Embedded sub-process | Group steps into an inline scope with isolated variables |
| Event sub-process | React to events inside a scope without disrupting the main flow |
| Ad-hoc sub-process | Activate a dynamic set of activities chosen at runtime |
| Call activity | Invoke another, separately-defined process as a child |
| Multi-instance loop | Run an activity once per item in a collection |
| Standard loop | Repeat an activity while a condition holds |
Embedded sub-process
An inline scope. The sub-process has its own start and end events and contains any flow elements, including nested sub-processes.
Attributes
| Attribute | Default | Notes |
|---|---|---|
id | — | Required |
name | — | Optional |
triggeredByEvent | false | true makes this an event sub-process |
default | — | ID of the outgoing flow taken when every other outgoing condition is false. See Sequence Flow → Default flows |
Child loop elements
| Element | Effect |
|---|---|
<multiInstanceLoopCharacteristics> | Run the body once per item in a collection (or loopCardinality times) — see Multi-instance loop |
<standardLoopCharacteristics> | Re-run the entire body in series until loopMaximum is hit or loopCondition becomes false — see Standard loop |
The two are mutually exclusive on the same activity.
Scope behavior
- The sub-process scope has isolated variables. I/O mappings on the sub-process control what crosses the boundary.
- Boundary events on the sub-process element interrupt or extend the entire sub-process — see Boundary events.
Example
<bpmn:subProcess id="payment-sub" name="Process Payment">
<bpmn:extensionElements>
<quantum:ioMapping>
<quantum:input source="=orderId" target="order_id" />
<quantum:output source="=paymentStatus" target="status" />
</quantum:ioMapping>
</bpmn:extensionElements>
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
<bpmn:startEvent id="sub-start"><bpmn:outgoing>sub-f1</bpmn:outgoing></bpmn:startEvent>
<bpmn:serviceTask id="charge" name="Charge Card">
<bpmn:extensionElements>
<quantum:taskDefinition type="payment-worker" />
</bpmn:extensionElements>
<bpmn:incoming>sub-f1</bpmn:incoming>
<bpmn:outgoing>sub-f2</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:endEvent id="sub-end"><bpmn:incoming>sub-f2</bpmn:incoming></bpmn:endEvent>
<bpmn:sequenceFlow id="sub-f1" sourceRef="sub-start" targetRef="charge" />
<bpmn:sequenceFlow id="sub-f2" sourceRef="charge" targetRef="sub-end" />
</bpmn:subProcess>
Validation
| Check | Severity |
|---|---|
| Sub-process contains no start event | Error |
| Sub-process contains no end event | Warning |
Event sub-process
A <subProcess triggeredByEvent="true"> lives inside a parent scope but isn't connected by sequence flows. It's started by its own typed start event when a matching event happens in the parent scope.
Differences from a normal sub-process
| Property | Normal | Event sub-process |
|---|---|---|
triggeredByEvent | false | true |
| Sequence flow connection | Has incoming/outgoing flows | None — triggered by its start event |
| Start event type | None | Typed (message, timer, signal, error, escalation, conditional) |
Start event isInterrupting | N/A | true cancels the parent, false runs in parallel |
Compensation start events are not supported in event sub-processes — see Events FAQ.
Examples
<!-- Interrupting message-triggered event sub-process -->
<bpmn:subProcess id="esp-cancel" triggeredByEvent="true">
<bpmn:startEvent id="esp-start" name="Order Cancelled" isInterrupting="true">
<bpmn:outgoing>esp-f1</bpmn:outgoing>
<bpmn:messageEventDefinition messageRef="Msg_Cancel" />
</bpmn:startEvent>
<bpmn:serviceTask id="esp-cleanup" name="Cleanup Order">
<bpmn:extensionElements>
<quantum:taskDefinition type="cleanup-worker" />
</bpmn:extensionElements>
<bpmn:incoming>esp-f1</bpmn:incoming>
<bpmn:outgoing>esp-f2</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:endEvent id="esp-end"><bpmn:incoming>esp-f2</bpmn:incoming></bpmn:endEvent>
<bpmn:sequenceFlow id="esp-f1" sourceRef="esp-start" targetRef="esp-cleanup" />
<bpmn:sequenceFlow id="esp-f2" sourceRef="esp-cleanup" targetRef="esp-end" />
</bpmn:subProcess>
<!-- Non-interrupting conditional event sub-process: fires on each false→true transition -->
<bpmn:subProcess id="esp-watch" triggeredByEvent="true">
<bpmn:startEvent id="esp-cond-start" isInterrupting="false">
<bpmn:outgoing>esp-c1</bpmn:outgoing>
<bpmn:conditionalEventDefinition>
<bpmn:condition xsi:type="bpmn:tFormalExpression">priority = "high"</bpmn:condition>
</bpmn:conditionalEventDefinition>
</bpmn:startEvent>
<!-- ... -->
</bpmn:subProcess>
Validation
| Check | Severity |
|---|---|
| Event sub-process has incoming or outgoing sequence flows | Error |
| Event sub-process has more or fewer than one typed start event | Error |
Conditional start event has empty condition | Error |
| Error / escalation start event placed outside an event sub-process (BPMN 2.0 §10.4.2) | Error |
| Compensation start event used anywhere | Error (not supported) |
Ad-hoc sub-process
An ad-hoc sub-process contains activities whose activation is dynamic — the set of activities to spawn is chosen at runtime by a FEEL expression rather than entered through a start event. Sequence flows between inner activities are still supported, so once activated, activities can flow into each other in the usual way.
Attributes and extensions
| Attribute / Extension | Notes |
|---|---|
default (attribute) | ID of the outgoing flow taken when every other outgoing condition is false. See Sequence Flow → Default flows |
<quantum:adHoc activeElementsCollection="..."> | FEEL expression returning the list of activity IDs to activate |
<completionCondition> child | FEEL expression evaluated after each activity completes, the sub-process ends when it returns true |
<multiInstanceLoopCharacteristics> | Run the entire ad-hoc once per item in a collection |
<standardLoopCharacteristics> | Re-run the entire ad-hoc until loopMaximum / loopCondition stop. Mutually exclusive with multi-instance |
Each activity inside an ad-hoc sub-process must carry a quantum:taskDefinition (or be one of the supported task types).
Behavior with end events
End events behave differently inside an ad-hoc sub-process:
| End event variant | Effect |
|---|---|
| None / message / signal / escalation | Consumes the token, the ad-hoc keeps running until completionCondition is satisfied |
| Terminate | Ends the ad-hoc scope immediately, cancelling all sibling tokens, the parent process continues from the ad-hoc's outgoing flow |
Example
<bpmn:adHocSubProcess id="ad-hoc-tasks" name="Dynamic Tasks">
<bpmn:extensionElements>
<quantum:adHoc activeElementsCollection="=["task-review", "task-approve"]" />
<quantum:ioMapping>
<quantum:output source="=allCompleted" target="allDone" />
</quantum:ioMapping>
</bpmn:extensionElements>
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
<bpmn:serviceTask id="task-review" name="Review">
<bpmn:extensionElements>
<quantum:taskDefinition type="review-worker" />
</bpmn:extensionElements>
</bpmn:serviceTask>
<bpmn:serviceTask id="task-approve" name="Approve">
<bpmn:extensionElements>
<quantum:taskDefinition type="approve-worker" />
</bpmn:extensionElements>
</bpmn:serviceTask>
<bpmn:completionCondition xsi:type="bpmn:tFormalExpression">=reviewDone and approveDone</bpmn:completionCondition>
</bpmn:adHocSubProcess>
Validation
| Check | Severity |
|---|---|
| Ad-hoc sub-process contains no activities | Error |
| Ad-hoc sub-process contains a start event | Error |
ordering="Sequential" (or any value other than "Parallel") | Error |
Call activity
Invokes a separately-defined process as a child. The child runs in its own scope, the parent waits until the child completes.
Attributes and extensions
| Attribute / Extension | Required | Notes |
|---|---|---|
calledElement (attribute) | — | Standard BPMN attribute, ID of the called process. Overridden by the extension when both are present |
default (attribute) | no | ID of the outgoing flow taken when every other outgoing condition is false. See Sequence Flow → Default flows |
quantum:calledElement processId="..." | yes | ID of the process to invoke |
quantum:calledElement bindingType="..." | no | "" or "latest" (default) picks the newest deployed version, "version" pins to a specific integer version. |
quantum:calledElement version="N" | no | Required when bindingType="version". Positive integer |
quantum:calledElement propagateAllChildVariables="..." | no | true merges all child output variables into the parent scope. Default is false |
quantum:ioMapping | no | Inputs are passed to the child, outputs are pulled back |
multiInstanceLoopCharacteristics | no | Run the called process once per item — see Multi-instance loop |
standardLoopCharacteristics | no | Re-call the child process serially up to loopMaximum times. Mutually exclusive with multi-instance — see Standard loop |
A call activity cannot be a compensation handler.
isForCompensation="true"is rejected at deployment — only tasks and embedded sub-processes are valid compensation handlers.
Variable propagation
| Mode | What flows back |
|---|---|
propagateAllChildVariables="false" (default) | Only variables explicitly mapped via output mappings |
propagateAllChildVariables="true" | All child variables are merged into the parent scope on completion |
Boundary events on the call activity are supported. An interrupting boundary cancels the child workflow.
Compensation
A call activity participates in compensation in one of two ways, and they are mutually exclusive:
| The call activity has… | Compensating the call activity runs… |
|---|---|
| A compensation boundary event + handler (the handler lives in the parent) | Only that parent-side handler. The handler defines the call activity's black-box compensation; the child's own internal compensation is not invoked. |
| No compensation boundary | Compensation propagates into the called process and runs the child's own internal compensation handlers. |
Propagating compensation into a called process is an intentional difference from Camunda 8, which stops compensation at the call activity. To get the Camunda behavior, attach a compensation boundary handler to the call activity itself.
A compensation throw inside the child is independent of both — it runs during the child's own execution whenever its flow reaches the throw.
Because a compensable child must stay available until the parent might compensate it, the engine keeps it alive after it finishes its own flow. It is released and reaches Completed as soon as the parent no longer needs it — when the parent reaches a terminal state, or when the call activity's output mapping raises an incident. A compensable child never outlives its parent.
Example
<bpmn:callActivity id="run-sub-process" name="Run Sub-Process">
<bpmn:extensionElements>
<quantum:calledElement processId="child-process-id" propagateAllChildVariables="false" />
<quantum:ioMapping>
<quantum:input source="=customerId" target="customer_id" />
<quantum:output source="=result" target="subProcessResult" />
</quantum:ioMapping>
</bpmn:extensionElements>
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
</bpmn:callActivity>
Validation
| Check | Severity |
|---|---|
processId and calledElement are both empty | Error |
isForCompensation="true" set on the call activity | Error (only tasks and embedded sub-processes can be compensation handlers) |
bindingType="version" without a positive integer version | Error |
Multi-instance loop
Attached as <multiInstanceLoopCharacteristics> to a task, sub-process, or call activity. Runs the host activity multiple times — in parallel or sequentially.
Attributes and child elements
| Attribute / Element | Notes |
|---|---|
isSequential (attribute) | true runs iterations one after another, false (default) runs them in parallel |
<loopCardinality> child | FEEL expression returning the number of iterations (e.g. "5", "=items.size()") |
<completionCondition> child | FEEL expression, the loop exits early when it returns true |
The quantum:loopCharacteristics extension holds collection-based iteration:
| Attribute | Notes |
|---|---|
inputCollection | FEEL expression returning the collection to iterate over (e.g. "=orderItems") |
inputElement | Variable name for the current item in each iteration (e.g. "item") |
outputCollection | Variable name to collect per-iteration results into |
outputElement | FEEL expression extracting the result from each iteration scope (e.g. "=result") |
<loopCardinality> and inputCollection are mutually exclusive — pick one. outputCollection / outputElement only make sense with inputCollection.
Parallel cardinality — static safety net
A literal-integer loopCardinality greater than 1000 on a parallel multi-instance activity is rejected at deployment. This is a static safety net to catch obvious mistakes like loopCardinality="100000" — every parallel instance becomes a live token plus scope in workflow state, and very large counts blow past the workflow engine's per-payload size limits.
A loopCardinality that is a FEEL expression (e.g. =items.size()) bypasses this static check — the engine cannot know the result at deploy time and there is no runtime cap. If the expression resolves to a very large number, the engine will faithfully create that many instances and the workflow will eventually fail with a payload-size or resource error rather than a clean cardinality-exceeded error. In practice this means:
- Keep parallel multi-instance bounded. A few dozen to a few hundred instances is fine, thousands risks instability.
- For collections of unknown size, prefer
isSequential="true"(one instance at a time, no fan-out cost). - For large known fan-out, break the collection into batches with two nested multi-instance activities (outer loops over chunks, inner runs the chunk in parallel), or push the iteration into a streaming worker that loops internally.
Variables visible inside the loop
The engine injects spec-standard counters that any FEEL expression inside the activity can read:
| Variable | Scope | Description |
|---|---|---|
numberOfInstances | Outer | Total inner instances created |
numberOfActiveInstances | Outer | Currently executing instances (always 0 or 1 for sequential) |
numberOfCompletedInstances | Outer | Instances completed normally so far |
numberOfTerminatedInstances | Outer | Instances cancelled or interrupted |
loopCounter | Per instance | 1-based sequence number of the current instance |
A completionCondition can use any of these to exit early:
numberOfCompletedInstances > 0 // exit after the first finishes
numberOfCompletedInstances >= 2 // exit once at least two have finished
loopCounter = 1 and numberOfInstances > 3 // exit after instance 1 in a large batch
Variable visibility after the loop completes
The injected counters and loopCounter live on the multi-instance scopes — they are never propagated into the activity's outer scope when the loop finishes. The only thing that flows out is the configured outputCollection.
outputCollection is positional and always the same length as the cardinality. The engine pre-allocates one slot per planned instance and only normally-completed instances write into their slot — terminated, cancelled, errored, or never-started instances leave their slot as null. The list shape mirrors the input one-for-one, with null marking absent results.
If you need counts after the activity, derive them from non-null entries:
| Want | FEEL on the next activity |
|---|---|
Total slots (equals the cardinality / inputCollection size) | =count(outputCollection) |
| Instances that completed normally | =count(outputCollection[item != null]) |
| Instances that produced no result (cancelled, terminated, errored, or never started) | =count(outputCollection[item = null]) |
The positional alignment also means you can correlate input and output by index: outputCollection[i] is the result of running the activity on inputCollection[i], or null if that instance didn't complete normally.
Boundary events on a multi-instance activity
A boundary event attached to an MI activity sees the counters via the normal scope chain — at the moment the boundary fires, the firing instance's parent is the MI wrapper, so the boundary handler's input/output FEEL can read numberOfCompletedInstances, numberOfActiveInstances, loopCounter, etc. directly. Once the MI scope cleans up these values are gone, so capture anything you need into the parent scope via the boundary handler's <output> mapping.
<bpmn:boundaryEvent id="mi-cancel" attachedToRef="process-items">
<bpmn:extensionElements>
<quantum:ioMapping>
<quantum:output source="=numberOfCompletedInstances" target="completedBeforeCancel" />
<quantum:output source="=numberOfTerminatedInstances" target="cancelledCount" />
</quantum:ioMapping>
</bpmn:extensionElements>
<bpmn:errorEventDefinition errorRef="ItemErr" />
</bpmn:boundaryEvent>
Examples
<!-- Parallel multi-instance service task iterating a collection -->
<bpmn:serviceTask id="process-items" name="Process Each Item">
<bpmn:extensionElements>
<quantum:taskDefinition type="item-processor" />
<quantum:ioMapping>
<quantum:output source="=itemResult" target="itemResult" />
</quantum:ioMapping>
</bpmn:extensionElements>
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
<bpmn:multiInstanceLoopCharacteristics>
<bpmn:extensionElements>
<quantum:loopCharacteristics
inputCollection="=orderItems"
inputElement="item"
outputCollection="processedItems"
outputElement="=itemResult" />
</bpmn:extensionElements>
</bpmn:multiInstanceLoopCharacteristics>
</bpmn:serviceTask>
<!-- Sequential multi-instance call activity -->
<bpmn:callActivity id="call-each" name="Call Sub Per Item">
<bpmn:extensionElements>
<quantum:calledElement processId="item-sub-process" propagateAllChildVariables="false" />
<quantum:ioMapping>
<quantum:input source="=item" target="currentItem" />
<quantum:output source="=subResult" target="subResult" />
</quantum:ioMapping>
</bpmn:extensionElements>
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
<bpmn:multiInstanceLoopCharacteristics isSequential="true">
<bpmn:extensionElements>
<quantum:loopCharacteristics
inputCollection="=batches"
inputElement="item"
outputCollection="results"
outputElement="=subResult" />
</bpmn:extensionElements>
</bpmn:multiInstanceLoopCharacteristics>
</bpmn:callActivity>
Validation
| Check | Severity |
|---|---|
Both loopCardinality and inputCollection are set | Error |
Neither loopCardinality nor inputCollection is set | Error |
multiInstanceLoopCharacteristics and standardLoopCharacteristics are both set on the same activity | Error |
Literal-integer loopCardinality exceeds 1000 on a parallel multi-instance | Error (static safety net, FEEL-expression cardinalities bypass this check) |
Standard loop
A standard loop re-executes the same activity sequentially while a FEEL condition holds, capped by a maximum iteration count. Supported on:
- Tasks (service / user / script / business-rule / send / receive / manual / generic)
- Embedded sub-processes — the entire body re-runs each iteration in a fresh scope
- Ad-hoc sub-processes
- Call activities — each iteration starts a fresh child workflow
Attributes and child elements
| Attribute / Element | Notes |
|---|---|
loopMaximum (attribute) | Required. Maximum number of iterations (≥ 1). The loop ends when loopCounter reaches this value, regardless of the condition |
testBefore (attribute) | If true, the condition is checked before the first iteration — the activity is skipped entirely if the condition is false at that point. Default false |
<loopCondition> child | Optional FEEL expression. The loop continues as long as it returns true. Without it, the activity runs exactly loopMaximum times |
Each iteration sets a loopCounter variable in the activity's surrounding scope (1-based). For sub-processes, the body re-enters in a freshly minted scope each iteration, for call activities, a fresh child workflow is started each time.
Behavior
| Setting | Behavior |
|---|---|
testBefore="false" (default) | Run at least once, check the condition after each completion |
testBefore="true" | Check first, skip entirely if the condition is false |
loopMaximum="N" | Hard cap — after N executions the loop exits |
No loopCondition | Run exactly loopMaximum times |
Example
<!-- Run while counter < 5, capped at 10 iterations -->
<bpmn:serviceTask id="retry-task" name="Retry Until Done">
<bpmn:extensionElements>
<quantum:taskDefinition type="retry-processor" />
<quantum:ioMapping>
<quantum:output source="=counter" target="counter" />
</quantum:ioMapping>
</bpmn:extensionElements>
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
<bpmn:standardLoopCharacteristics loopMaximum="10">
<bpmn:loopCondition>=counter < 5</bpmn:loopCondition>
</bpmn:standardLoopCharacteristics>
</bpmn:serviceTask>
Validation
| Check | Severity |
|---|---|
loopMaximum is missing or ≤ 0 | Error |
Both standardLoopCharacteristics and multiInstanceLoopCharacteristics are set | Error |
When to use which
Use a standard loop when iterations are sequential and condition-driven, and the count isn't known up front. Use a multi-instance loop when you need parallel execution or are iterating a known collection.
FAQ
What's the difference between a sub-process and a call activity in BPMN?
A sub-process is defined inline inside a parent process — its activities live within the parent's BPMN file and share its deployment lifecycle. A call activity invokes a separately-defined process by ID, the called process is deployed independently, runs in its own workflow instance, and the parent waits for the child to complete before advancing. Use a sub-process to group related steps for clarity or to scope variables and boundary events, use a call activity to factor out a reusable process that can be invoked from multiple places, deployed and versioned separately, and monitored as its own instance.
What is a BPMN event sub-process and when should I use one?
An event sub-process is a sub-process inside a parent scope that is not connected by sequence flows — it is set up by adding triggeredByEvent="true" to a <subProcess> element. It starts when its own typed start event fires in response to a matching event in the parent scope: a message arrives, a timer expires, a signal is broadcast, an error is thrown, an escalation is raised, or a conditional FEEL expression becomes true. Use it to react to events that can occur at any point inside the parent scope — for example, an order-cancelled message while the order is being processed — without disrupting the main flow. Do not confuse it with the event-based gateway, which is a routing construct that picks one of several events to wait for at a specific point in the flow.
What's the difference between interrupting and non-interrupting event sub-processes?
It is set by the isInterrupting attribute on the event sub-process's start event. With isInterrupting="true" (the default), the parent scope is cancelled when the event sub-process fires — every active token in the parent stops, and only the event sub-process's flow continues. With isInterrupting="false", the parent scope keeps running and the event sub-process runs in parallel on every matching event. Use interrupting for events that should abort the main flow (order cancellation, fatal error), and non-interrupting for parallel reactions that should not disrupt the main flow (periodic reminders, escalations to a supervisor).
What is an ad-hoc sub-process in BPMN?
An ad-hoc sub-process contains a set of activities that are not connected by fixed sequence flows. Instead, which activities run is decided at runtime by the <quantum:adHoc activeElementsCollection> FEEL expression, which returns the IDs of activities to activate. The sub-process completes when its <completionCondition> FEEL expression evaluates to true. Use it to model dynamic work where the order or selection of steps depends on data rather than a predefined flow — for example, a case-management workflow where the case worker picks which optional tasks to perform from a menu of allowed actions.
Does QuantumBPM support sequential ad-hoc sub-processes?
It depends on what you mean. The ordering="Sequential" attribute on an ad-hoc sub-process — the engine-level scheduling mode that would serialise activations of independent activities — is not supported and is rejected at deployment, only the default ordering="Parallel" is accepted. But you can absolutely express sequential behaviour by drawing sequence flows between activities inside the ad-hoc. BPMN 2.0 §10.3.5 includes sequence flows in the ad-hoc content model, and the engine applies normal sequence-flow mechanics to whatever you connect — so a chain A → B → C inside an ad-hoc runs sequentially even though the attribute itself is unsupported. So: no on the attribute, yes via internal flow modelling.
Is there a limit on the number of parallel multi-instance activity instances?
There is a static safety net: a literal-integer loopCardinality greater than 1000 on a parallel multi-instance is rejected at deployment, because every parallel instance becomes a live token plus scope in workflow state and very large counts exceed the workflow engine's per-payload size limits. A FEEL-expression loopCardinality (e.g. =items.size()) bypasses this check — the engine cannot know the result at deploy time and there is no equivalent runtime cap, so if the expression resolves to a very large number the engine will create that many instances and the workflow will eventually fail with a payload-size or resource error rather than a clean cardinality-exceeded error. Best practice: keep parallel multi-instance bounded, for unknown-size collections prefer sequential multi-instance, and for large known fan-out break the collection into batches with two nested multi-instance activities or process the iteration inside a streaming worker.
What's the difference between a multi-instance loop and a standard loop in BPMN?
A multi-instance loop runs the host activity a known number of times — either loopCardinality (an explicit count) or once per item in inputCollection — and can run iterations in parallel (isSequential="false", the default) or sequentially. A standard loop runs the host activity repeatedly while a FEEL loopCondition returns true, capped at loopMaximum iterations and always sequential. Use multi-instance when you know the iteration count or are processing a collection, especially when iterations can run in parallel. Use a standard loop for condition-driven repetition where the count isn't known up front — retry until success, poll until ready, page until empty. The two characteristics are mutually exclusive on the same activity.
What does propagateAllChildVariables do on a BPMN call activity?
It controls what flows back from the child process when it completes. With propagateAllChildVariables="false" (the default), only variables that the parent explicitly pulls back through <quantum:output> mappings on the call activity are merged into the parent scope. With propagateAllChildVariables="true", every variable the child accumulated in its root scope is merged into the parent scope on completion, overwriting parent variables of the same name. The default is safer for loose coupling — the parent declares exactly which child outputs it cares about — and is recommended for reusable child processes, the true setting is convenient for tightly-coupled processes where the parent expects to inherit child state wholesale.
What happens when a terminate end event fires inside a sub-process?
It cancels the nearest enclosing activity scope — the sub-process that contains the terminate, not the entire process instance. Every active token inside that sub-process stops, and execution continues from the sub-process's outgoing sequence flow as if the sub-process had completed normally. A terminate inside an embedded sub-process ends only that sub-process, a terminate at the root of the process ends the entire instance, a terminate inside a called process (a child workflow started by a call activity) ends only the called process, and the calling parent advances normally as if the child had completed.