> ## Documentation Index
> Fetch the complete documentation index at: https://docs.prodexlabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Modeler

> The canvas where simulation models are built: every component type, the supporting data they draw on, and the run controls.

## Overview

The **Modeler** is where a [simulation model](/product/simulation-overview) gets built: a directed graph of components on a canvas, plus the entities, expressions, reference data, and event logic that make it behave like your operation. This page is the working reference for that surface — the top-bar controls, every component type and its configuration, the expression language, the Library, and how a model actually runs.

If you haven't built a model before, walk through [Your First Simulation](/getting-started/your-first-simulation) once first. And you never have to drive the canvas by hand: [Dexter](/product/dexter/chat-and-tasks) builds and edits models conversationally, already knows which model you have open, and mixes freely with manual edits.

### What discrete event simulation is

Discrete event simulation (DES) represents an operation as a sequence of instants at which something changes — an order arrives, a machine finishes a cycle, a resource frees up. Between events nothing happens, so simulated time jumps from one event to the next; that's why a model of a month of production runs in seconds. Change a capacity, a distribution, or a buffer and you see the downstream effects immediately, without touching the real system.

You build the model visually and ProDex's simulation engine executes it directly. The canvas is the source of truth — there is no engine code to write.

## Building on the Canvas

A model is a **directed graph**: components are nodes, connections are edges, and [entities](/reference/entities) flow along the edges. Drag components out of the Modeler's left panel, drop them on the canvas, wire each component's output handle to the next component's input handle, and click any component to open its configuration panel.

The minimum viable model is **Source → Process → Sink**. Everything else — buffers, routers, stations, event logic — earns its place as your questions demand it.

## The Top Bar

* **Model selector** — the active model carries a checkmark; the menu also holds **+ New Model**, **Rename**, and **Delete**.
* **Schedule selector** — picks what drives timing: `Default (no schedule)` or any [schedule](/reference/schedules) defined for the model. With `Default (no schedule)` active, a **Duration** field (`DDD:HH:MM:SS`) sets the run length. With a named schedule active, the duration field gives way to a pencil icon that opens the schedule editor, and the schedule's own Start and End Times drive the run.
* **Save snapshot** (disk icon) — opens the *Create Snapshot* dialog: name required, description optional. A [Snapshot](/reference/snapshots) is an immutable capture of the model plus its active schedule — not a save; the Modeler autosaves continuously.
* **Snapshot history** (clock icon) — a searchable picker of existing snapshots. Picking one loads it onto the canvas.
* **Undo / Redo** — edit history for the current session.

## Component Types

Every flow component's panel also carries an **Event Hooks** and an **Event Listeners** section — the reaction logic covered in [The Event System](#the-event-system) below.

### Source

Where entities enter the model.

* **Entity type** — which [entity](/reference/entities) this Source produces.
* **Arrival logic** — the **inter-arrival time** (time between arrivals, not a rate), drawn from the [distribution picker](/reference/distributions) or a [DSL expression](/reference/expressions).
* **Initial attribute values** — per attribute, a **Strategy**: *Fixed*, *DSL Expression*, *Round Robin*, *Random Choice*, *Random* (sample a distribution), or *Weighted* (a value/weight table). The dropdown narrows to the strategies valid for the attribute's type — see the [type-by-strategy matrix](/reference/entities#assignment-strategies). Note that a DSL assignment at a Source runs before the entity exists, so it can't read the new entity's other attributes.
* **Events** — fires *entity created* per produced entity.

An **event-only Source** is valid on purpose: leave arrival logic empty and the Source produces nothing on its own, releasing entities only when a *release* action or a [schedule material release](/reference/schedules#material-releases) triggers it. When a schedule is active, its material releases run **in parallel with** the Source's own arrival logic — both fire — so decide deliberately which mechanism (or both) drives each entry point.

### Process

A step that takes time and may consume resources.

* **Entity Type** — scopes the rest of the panel to one entity type.
* **Processing time** — a [distribution](/reference/distributions) or DSL expression.
* **Resource requirements** — a multi-row table (*+ Add Row*) of resource → quantity. Quantities accept expressions, and a requirement whose quantity evaluates to **0 is skipped for that entity** — the mechanism behind cross-training and substitution patterns.
* **Station** — optionally places the Process inside a [Station](#station).
* **Events** — *process started*, *process completed*.

### Resource

A capacity pool — workers, machines, fixtures — that Processes draw from. Resources aren't draggable canvas nodes: they're **model-scoped pools** managed in the side panel and referenced from Process requirement tables.

* **Capacity** — number of parallel slots.
* **Allocation Discipline** — **FIFO**, **LIFO**, or **Priority** (a DSL key with ascending/descending ordering).
* **Changeover Times** — a table of *(from process, to process)* → duration rows. Same-process pairs are allowed, and each duration can be a fixed value, a distribution, **or a DSL expression**. Because that expression can read the [changeover-only identifiers](/reference/expressions#changeover-only-identifiers) for the exiting and entering entities — their types and their attributes — a single changeover time can vary with the *(exiting entity, entering entity)* pair rather than being keyed only by process.
* **Events** — *slot freed*, *slot working*. A *set capacity* action reacting to shift topics is the standard [shift scheduling pattern](/reference/schedules#the-shift-scheduling-pattern); the same mechanism models breakdowns, maintenance windows, and staffing changes.

<Note>
  **Changeover keys are processes, not entity types.** A resource that handles two products through the same process incurs no changeover; switching between two processes (even on the same entity type) does. If your changeover model is keyed by entity type today, rework it around the processes that actually demand the setup.
</Note>

### Buffer

A waiting area between components.

* **Entity Type**, and **Capacity** with an explicit **Infinite** toggle.
* **Queue Discipline** — FIFO, LIFO, or Priority.
* **Release Mode** — *Push (auto-release)* sends entities downstream as soon as there's room; *Hold (manual release)* keeps them until a *release* action fires — the kanban/pull pattern.
* **Events** — *entity entered*, *entity exited*, *contents changed*. Buffers and Stations are the components that emit the per-entity entered/exited pair; Processes signal through *process started/completed* instead.

<Tip>
  **Hold-mode Buffers are the canonical batching recipe.** A *contents changed* hook whose condition inspects the queue and fires *release* with a quantity (and optional per-entity filter) often beats a Combiner + Separator sandwich, because it preserves per-entity lineage. See [Combiners, Separators & Transformers](/reference/batching).
</Tip>

### Router

Splits entity flow across outgoing connections based on its **Logic Type**:

| Logic Type        | Behavior                                                                                                                                                                                                                                                                               |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Conditional**   | IF/THEN rules over entity attributes, simulation state, or component queries. Rules evaluate **first-match-wins**, with a configurable **Default Route** as the fallback — ordering matters.                                                                                           |
| **Probabilistic** | A weight per outgoing connection; weights are relative (they don't need to sum to 1 — the engine resolves them to fractions, and [validation](/reference/validation) surfaces the resolved split as an advisory warning). Weights can themselves be DSL expressions. No default route. |
| **Round Robin**   | Cycles through outgoing connections in order.                                                                                                                                                                                                                                          |
| **Type-based**    | Maps each entity type to a destination connection.                                                                                                                                                                                                                                     |

Routers fire *entity routed* per entity. Each Conditional or Probabilistic rule row has a *See expression / AI explanation* toggle that flips between the raw DSL and a plain-English explanation — the fastest way to confirm a rule does what you think it does.

### Combiner, Separator, and Transformer

The batching components, covered in depth in [Combiners, Separators & Transformers](/reference/batching):

* **Combiner** — merges multiple inputs into one output (assembly). Batches form either from static per-input quantities or from a dynamic batch expression evaluated on each arrival. Output attribute assignments run in multi-entity context, so [aggregations](/reference/expressions#aggregation-functions) apply — `SUM`, `MEAN`, `MAX`, `MIN`, `COUNT`, `ANY`, `ALL`, `MODE`, `N_UNIQUE` over the batch (e.g. `SUM(weight)`, `MAX(priority)`, `ALL(qc_passed)`). An aggregated attribute must exist on **every** input type — give a default to inputs that don't naturally carry it rather than leaving it absent.
* **Separator** — splits one input into several outputs: an Output Batch table pairs each output entity type with a count expression, with per-output attribute assignments (*Fixed* or *DSL*).
* **Transformer** — one in, one out. Changing the entity type is only half its job: configuring the **same** input and output type is the standard way to mutate attributes mid-flow (incrementing a rework counter, stamping a QC result).

All three are typically placed inside a Station rather than standalone.

### Station

A capacity-constrained container representing a physical space or work center. Capacity is a **per-entity-type table** (e.g., Tube = 500, Tray = 100), so one station can hold different quantities of different items at once; leave a type unbounded when the station isn't the constraint.

Stations also group related components, which keeps large models navigable — and emit *entity entered*, *entity exited*, and *contents changed* (multi-entity context over the current contents, the usual seat of WIP gauges).

<Warning>
  **Station capacity is tracked by entity lineage, not throughput.** A slot is tied to the *entry* entity and freed only when it and all its descendants have exited — a Separator producing three outputs per input holds the parent's slot until every output leaves, so a station can be fully blocked at what looks like low utilization. Capacity represents physical space, not flow rate.
</Warning>

### Sink

The exit point. **Entity Type** is required — only matching entities may enter — and entities that arrive are consumed, counted, and fire *entity terminated*.

### Model Node

Embeds another model from the same factory as a single component — the key to hierarchical modeling. Model Nodes aren't in the component palette: from the Modeler's left panel, use **+ Import Model** to pick another model in the factory, then drag its card onto the canvas.

Each instance maps its outer connections to the sub-model's internal sources and sinks, and can alias resources, topics, and state variables per instance; nested components are addressed with `parent::child` qualified paths. See [Model Nodes & Hierarchical Modeling](/reference/model-nodes).

## Connections

Drag from a component's output handle to another's input handle to define flow. Entities move along a connection when the downstream component can accept them — or, for Hold-mode Buffers, when explicitly released. Two topology rules worth knowing up front: **connections can't start or end at a Station** (wire the components inside it instead), and a Router's rules attach to its outgoing connections. [Validation](/reference/validation) enforces the full ruleset as you edit.

## The Event System

Flow connections describe how entities move; the event system describes how the model *reacts*. Two primitives, both configured on every flow component's panel:

* **Event Hooks** fire on the component's **own lifecycle events** — the *entity created* / *process completed* / *contents changed* events listed per component above. The hook's expression context follows the event: single-entity for per-entity lifecycles, multi-entity for batch and contents-changed events, no-entity for resource slot events.
* **Event Listeners** fire when a subscribed [topic](/reference/events#topics) is emitted anywhere in the model — and always run in **no-entity context**: a listener can read state variables, constants, lookups, and component queries, but never the emitting entity's attributes. When cross-component logic needs per-entity data, the pattern is: a hook writes what it needs into state and emits; the listener reads state.

Both take an optional boolean **condition** and a list of actions: *assign* (write a state variable), *emit* (publish to a topic), *release* (Sources and Buffers only), *pause* / *resume* (Sources and Buffers only), and *set capacity* (Resources only). This is how shift logic, kanban, backpressure, and every other behavior that spans components gets wired — see [Event System](/reference/events) for the full per-component event and action contract.

## Expressions and the DSL

Most numeric and logical fields accept either a literal or an expression in ProDex's DSL, which is how models become dynamic and data-driven: routing conditions over attributes, per-entity resource needs, durations that depend on state.

**Identifiers available everywhere:** `SIM_TIME` (current simulation time) and `SIM_DURATION` (the configured run length, not the time remaining). **In entity contexts:** `ENTITY_TYPE` (the entity's type slug) and `ENTITY_AGE`. **In hooks and listeners:** `SELF`, the hosting component's id — which makes rules copy-pasteable across components.

**Component queries**, usable in every context: `BUFFER_LEVEL(id)`, `BUFFER_CAPACITY(id)`, `RESOURCE_AVAILABLE(id)`, `RESOURCE_CAPACITY(id)`, `STATION_WIP(id)` — and `LOOKUP(table, key1, key2, ..., default:=value)` reads a [lookup table](/reference/constants-and-lookups) by one or more keys.

Which fields run in which context — and the full catalog of functions and aggregations — lives in [The Expression Language](/reference/expressions).

<Tip>
  **Time fields use a `DDD:HH:MM:SS` widget by default** — typing `5` in the day box means 5 days, not 5 minutes. The `</>` toggle next to a field flips it into the DSL editor. That extends into distribution parameters too: each parameter accepts a literal number or a DSL expression directly, so a computed value doesn't need any special wrapper. And every DSL-mode value has the *See expression / AI explanation* toggle, so you can audit any expression in plain English.
</Tip>

Distributions are picked from the eleven-option dropdown (Fixed, Normal, Exponential, Uniform, Lognormal, Lognormal from mean/CV, Weibull, Triangular, Erlang, Beta, Gamma), not written as DSL calls. *Lognormal from mean/CV* is a distinct entry from *Lognormal* — it takes a real-world mean plus a coefficient of variation rather than the underlying normal's μ and σ, the form to reach for when a duration is stated as "mean X with CV Y." Every parameter of every distribution accepts a literal number or a per-sample DSL expression, so any of them can vary by entity or state. See [Distributions](/reference/distributions) for parameter conventions and which to reach for.

## The Library

The Library is the shared reference data your models draw from. Everything in it is **factory-scoped** — defined once, available to every model in the factory. You'll meet it in two places: the Modeler's left panel (the in-context view for dragging entities and components while you build) and the **Data** page (the factory-wide management surface). The **Metrics** tab overlays live KPI values onto canvas components after a run — useful for spotting where the slowdown actually happens without leaving the graph.

### Entities

The items that flow through your simulation: raw materials, WIP, finished goods. Every entity referenced by a Source, Transformer, or BOM must be defined in the Library first — open the **Entities** modal from the button at the bottom of the left panel.

Each entity type carries [typed attributes](/reference/entities#attribute-types) (Boolean, Number, Text, Text List, Number List), referenced by bare name in [single-entity contexts](/reference/expressions#expression-contexts) — no `self.` prefix. Attributes persist with the entity through the whole flow. Note that the two list types are **discrete choice sets** — despite the name, each holds a single member of its declared `choices`, not a collection. They accept only a fixed or sampled value; a DSL-computed assignment is rejected in validation. If a downstream node needs to compute or thread the value, make the attribute a scalar `Number` or `Text` instead.

<Warning>
  **Entities are factory-scoped, not model-scoped.** Renaming, retyping, or deleting an entity affects *every* model in the factory that references it — including expressions that name its slug. Need a one-off variant for one model? Create a new entity rather than mutating a shared one.
</Warning>

### Constants

Named, factory-scoped parameters — **Text**, **Number**, or **Boolean** — referenced by bare name in any expression: `PROCESSING_TIME_MINUTES * SHIFT_EFFICIENCY_FACTOR`. Names use SCREAMING\_SNAKE\_CASE by convention and can't collide with reserved words, component ids, or entity slugs. Constants are the right home for any value that appears in several places or that you'll sweep in an [experiment](/product/experiments): change it once, and every referencing field updates.

### Lookup Tables

Keyed tables queryable from any expression — `LOOKUP(cycle_times, ENTITY_TYPE)` — with one or more key columns and Text/Number/Boolean values. A missing key returns the `default:=` you pass, or a type-specific zero (`0`, `""`, `FALSE`) if you don't. Typical uses: cycle time by product, yield by material, routing weight by order class. See [Constants & Lookup Tables](/reference/constants-and-lookups).

### State Variables

Model-level mutable values that persist across entities: current shift, an inventory level, a rolling average. Read them by bare name in **any** context; write them with an *assign* action on a hook, listener, or [scheduled action](/reference/schedules#scheduled-actions). Names follow the same SCREAMING\_SNAKE\_CASE rule. Use a state variable when the value belongs to the *system*; use an [entity attribute](/reference/entities) for per-entity data.

### Topics

Named pub/sub channels: a component publishes with an *emit* action, and any component whose Event Listener subscribes to the topic reacts — coordination without a flow connection between them.

Constants, Lookup Tables, State Variables, and Topics share one four-tab modal, opened from the **Lookups** button at the bottom of the left panel (next to **Entities**).

## Schedules

A model runs in abstract time until you pair it with a [schedule](/reference/schedules), which anchors execution to wall-clock time: **material releases** inject specific entities at specific moments (in parallel with Source arrival logic, not instead of it), and **scheduled actions** fire *assign* and *emit* at specific times — the component-control actions remain hook-only. A model can hold multiple schedules — baseline week, stress scenario, historical replay — and you pick the active one in the top bar. A snapshot saved while a schedule is active **embeds** that schedule for downstream experiment and Monte Carlo runs.

## Running a Simulation

The run controls sit at the bottom of the canvas:

* **Run simulation** (green play) — executes the model to completion.
* **Fast forward** (skip-forward icon) — jumps the on-canvas playback straight to the end of the run; available once a run has produced data to replay.
* **Playback speed** — controls the on-canvas animation only. The engine always runs at full speed.

The play button doubles as the [validation](/reference/validation) indicator: **green** means ready; **red** means a specific blocker — hover for the error count, the verbatim error text, and a **Fix with Assistant** action that hands the failure to Dexter for a one-click proposal. The Modeler autosaves as you work, so there's no manual save step: validation re-runs against that saved state after each edit — checking DSL typing, entity-flow compatibility, hooks, schedules, and nested models — and the play button always reflects the current model.

Below the canvas, a horizontal **timeline** shows every material release, shift transition, and scheduled action. With `Default (no schedule)` the axis follows the Duration field; with a schedule active it switches to wall-clock dates spanning the schedule's Start and End Times. Scan it before running to confirm the schedule lines up with what you expect.

**Runs execute against the live working model** — tweak, run, repeat. Snapshots come in when you need a frozen configuration to compare: [Experiments](/product/experiments) and [Monte Carlo](/product/monte-carlo) both replay snapshots, not the live model. Each execution creates a [run](/product/runs) whose KPIs and charts compute in a second pass after the simulation finishes — a run showing *pending* isn't broken, just still computing. Read the output in [Results and Analytics](/product/results-and-analytics).

The bottom-right of the canvas holds the view controls: **Zoom In** / **Zoom Out**, **Fit View**, **Auto format layout** (re-arranges nodes), and **Find node** (`⌘F` / `Ctrl+F`).

## Tips and Best Practices

* **Start simple.** Source → Process → Sink validates your setup before you add complexity.
* **Use constants** for anything you'll change between runs — scenario sweeps become one-field edits.
* **Name everything in the operator's terms.** Component names surface in results, event logs, and charts; good names pay off every time you read output.
* **Use stations** to group related components — large models get hard to navigate without structure.
* **Watch the play button.** Green means valid; red names a specific blocker, and **Fix with Assistant** hands it to Dexter.
* **Snapshot at milestones.** [Snapshots](/reference/snapshots) are what experiments and Monte Carlo replay — you can't compare against a configuration you didn't capture.
