Overview
Combiners, Separators, and Transformers are the three Modeler components that reshape entity flow rather than simply routing or delaying entities. They’re how you model assembly, disassembly, batching, kitting, packaging, and any operation where the entities going in aren’t the same as the entities going out. These components look simple — a box with inputs and outputs — but each has specific behavior worth understanding before you build an assembly or disassembly operation, especially around how they interact with Station capacity, attribute carryover, and the multi-entity DSL context.Combiner
A Combiner merges multiple input entities into a single output entity. Use it to model assembly (parts → product), batching (individual items → pallet), kitting (components → kit), or any operation where several items become one.Input Batches
The Combiner’sinput_batch field has exactly two modes, picked from a binary Static / DSL Expression toggle on the config panel:
- Static — a
{entity_type: quantity}map.{tube: 1, tray: 1, label: 1}means “wait until you have one of each before assembling.” This is the canonical assembly shape — different counts of different input types feeding one output. - DSL Expression — a boolean trigger expression evaluated each time an entity arrives. When the expression returns
true, every accumulated entity is combined into one output. Use this when batch composition depends on runtime state.
input_entity_types (an array of entity slugs the Combiner accepts for graph validation) and batch_expr (the boolean trigger). batch_expr runs in multi-entity context over the currently-accumulated entities — so you can write triggers like COUNT(1) >= MIN_BATCH AND SUM(weight) >= LOAD_THRESHOLD.
Multi-Entity Context for Output Attributes
A Combiner output attribute assignment runs in multi-entity context — several input entities are in scope at once. Bare attribute references likeweight are ambiguous (which input’s?), so they must be wrapped in aggregation functions:
Combiner Events
A Combiner emits three lifecycle events (UI labels):entity consumed— fires for each input entity consumed (single-entity context, the input)batch assembled— fires when the batch closes (multi-entity context, all inputs)entity created— fires when the output entity is emitted (single-entity context, the output)
entity_type filter on hooks is only valid on entity consumed (so you can react only to consumption of a specific input type).
Partial Batches at Simulation End
A simulation can end with entities sitting in a Combiner that hasn’t yet hit its batch threshold. Those entities are abandoned — they never combine, never produce an output, and never reach downstream Sinks. They don’t appear in throughput metrics.Partial-batch entities at simulation end disappear silently. For short simulations with large batch sizes, this can materially affect reported throughput. Either pick a duration that lets the last expected batch close, or design the model so unmet batches surface as an explicit event.
Separator
A Separator splits one input entity into multiple output entities. Use it to model disassembly (kit → components), unbatching (pallet → individual items), or any “one in, many out” operation.Input Entity and Output Batch
The Separator config panel has two top-level controls:- Input Entity — a single dropdown of entity types defined in the model. The Separator accepts only entities of this type.
- Output Batch — a
{output_entity_type: count_expression}map. Each row is an entity type plus a DSL expression (a string) that returns the count of that type to emit per input. A row that sayscomponent: "4"means “emit 4 entities of typecomponentper input.” Variable counts can read input attributes:component: "input_quantity".
weight, priority, etc.).
Output Attributes
Each output type has its own attribute assignments in the schema’s separateattribute_assignments field (note the plural: it’s attribute_assignments on Separator, distinct from singular attribute_assignment on Combiner and Transformer).
attribute_assignments is a nested map: {output_entity_type: {attribute_name: assignment}}. Each assignment uses the 6-strategy dropdown.
Output attribute assignments run in single-entity context with the input entity’s attributes in scope — references to weight, priority, etc. read the input, and the assignment produces values for the output entity being created. A common pattern: divide a quantity across outputs evenly (weight / output_count) or copy a classification from input to every output.
Separator Events
A Separator emits three lifecycle events:entity consumed— fires when the input is consumed (single-entity context, the input)entity created— fires for each output entity (single-entity context, the output). Only this event accepts theentity_typefilter.batch created— fires once after all outputs are produced (multi-entity context, all outputs)
Interaction with Station Capacity
Separator lineage in a station is often misunderstood. The correct model:- The input entity consumes one capacity slot when it enters the station.
- The Separator splits that input into several output entities. The outputs are descendants of the input — they share the input’s lineage.
- The single slot is freed only when all descendants have exited the station. If three outputs were produced, the slot stays occupied until all three of them leave.
Station capacity keys are entering entity types only. Including an internally-created output type in a station’s capacity map fails validation — the schema requires capacity entries match types that actually enter the station, not types created inside it.
Transformer
A Transformer produces a new output entity of a different type, replacing the input in the flow. The entity count is unchanged (one in, one out), but downstream components see a different typed entity after the Transformer. Use a Transformer to model operations where the thing being worked on becomes a different thing after processing: raw material → WIP, WIP → finished good, blank → painted, component → inspected-component.Attribute Assignment on the Output
A Transformer emits a new entity of a different type, with its ownattribute_assignment (singular) map. Each assignment uses the 6-strategy dropdown — pick DSL Expression when the value is computed. Assignments run in single-entity context with the input entity’s attributes accessible and can reference:
- The input entity’s attributes — useful for carrying context forward
- Simulation state —
SIM_TIMEto stamp when the transformation happened - State variables and constants — to apply shift- or operation-specific values
Transformer Events
A Transformer emits two lifecycle events:entity consumed— the input being consumedentity created— the output being emitted
When to Use a Transformer vs. a Process
If all you need is “the entity is delayed by X minutes, optionally holding a resource,” use a Process. Use a Transformer when:- The entity’s downstream routing depends on its type (sent to different processes for different types)
- Results analysis groups by entity type (throughput by product, not aggregated)
- The entity conceptually becomes something different as part of the operation
DSL Contexts on These Components
| Surface | Context |
|---|---|
Combiner attribute_assignment (output attrs) | Multi-entity (all inputs) |
Combiner batch_expr (DSL trigger) | Multi-entity (accumulated inputs) |
Combiner on_entity_consumed hook | Single-entity (the input) |
Combiner on_batch_assembled hook | Multi-entity (all inputs) |
Combiner on_entity_created hook | Single-entity (the output) |
Separator output_batch count expressions | Single-entity (the input) |
Separator attribute_assignments (per output) | Single-entity (the input) |
Separator on_entity_consumed hook | Single-entity (the input) |
Separator on_entity_created hook | Single-entity (the output, per output) |
Separator on_batch_created hook | Multi-entity (all outputs) |
Transformer attribute_assignment (output attrs) | Single-entity (the input) |
| Transformer hooks | Single-entity |
Required Station Placement
Combiner, Separator, and Transformer each must be placed inside a Station. Thestation_id field defaults to null in the schema, but validation rejects models where it’s unset — every batching component needs a station container.
Event Hooks and Listeners
All three components carry both an Event Hooks section and a separate Event Listeners section in the config panel, exactly like every other flow component. Listeners react to topic emissions and always run no-entity context. See Event System for the full pattern.Patterns
Assembly cell: Sources → Buffers → Combiner inside a Station → Sink. Each Source feeds a different component type; the Combiner Static mode declares{tube: 1, tray: 1, label: 1}; the Station models the physical workspace constraint.
Kit packaging: Process (pick components) → Combiner (batch into kit) → Process (seal/label) → Sink. The Combiner produces the kit entity; downstream processes operate on kits.
Disassembly for rework: Router (send rework candidates here) → Separator (split the kit back into components) → individual component buffers. Each output’s attributes are assigned explicitly (rework_reason, original_kit_id).
Paint line: Source (raw parts) → Process (paint) → Transformer (raw_part → painted_part) → Process (inspect) → Router (route by qc_passed). The type change after paint lets downstream processes specialize on painted_part.
Tips
- Combiners are where aggregation lives. If you’re trying to compute a sum or max across a group of entities anywhere else, you’re probably fighting the model — restructure so the aggregation happens at a Combiner output.
- Lineage-based capacity is the reason stations with Combiners or Separators can behave counter-intuitively. Slots are consumed on entry and freed when all lineage descendants exit — not based on batch size or output count arithmetic.
- Transformer over Process when type matters downstream. Processes change state; Transformers change identity. Use each for its purpose.
- Attribute carryover is never automatic on Transformers or Separators. Every desired output attribute must be assigned explicitly.
- Combiner DSL mode is for runtime-shaped batches. If your batch size is constant, Static mode with a
{type: count}map is clearer and faster.

