> {
+ /**
+ * The optional position filter applied to this content context.
+ *
+ * If set, content is only applied to nodes at positions matching this filter.
+ */
+ val filter: PositionBasedFilter?
+
+ /**
+ * The molecule name to inject into nodes.
+ *
+ * The molecule is created using the incarnation's molecule factory.
+ *
+ * @see [it.unibo.alchemist.model.Incarnation.createMolecule]
+ */
+ var molecule: String?
+
+ /**
+ * The concentration value for the molecule.
+ *
+ * The concentration is created using the incarnation's concentration factory.
+ *
+ * @see [it.unibo.alchemist.model.Incarnation.createConcentration]
+ */
+ var concentration: T?
+}
diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/DeploymentsContextImpl.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/DeploymentsContextImpl.kt
new file mode 100644
index 0000000000..ec1690371d
--- /dev/null
+++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/DeploymentsContextImpl.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2010-2025, Danilo Pianini and contributors
+ * listed, for each module, in the respective subproject's build.gradle.kts file.
+ *
+ * This file is part of Alchemist, and is distributed under the terms of the
+ * GNU General Public License, with a linking exception,
+ * as described in the file LICENSE in the Alchemist distribution's top directory.
+ */
+
+package it.unibo.alchemist.boundary.dsl.model
+
+import it.unibo.alchemist.boundary.dsl.util.LoadingSystemLogger.logger
+import it.unibo.alchemist.model.Actionable
+import it.unibo.alchemist.model.Deployment
+import it.unibo.alchemist.model.Environment
+import it.unibo.alchemist.model.Node
+import it.unibo.alchemist.model.Position
+import it.unibo.alchemist.model.PositionBasedFilter
+import it.unibo.alchemist.model.linkingrules.CombinedLinkingRule
+import it.unibo.alchemist.model.linkingrules.NoLinks
+import org.apache.commons.math3.random.RandomGenerator
+
+/**
+ * Context for managing deployments in a simulation.
+ *
+ * @param T The type of molecule concentration.
+ * @param P The type of position.
+ * @param ctx The simulation context.
+ */
+open class DeploymentsContextImpl>(override val ctx: SimulationContext) :
+ DeploymentsContext {
+
+ override val generator: RandomGenerator
+ get() = ctx.scenarioGenerator
+
+ private val inc = ctx.incarnation
+
+ override fun deploy(deployment: Deployment<*>, block: context(DeploymentContext) () -> Unit) {
+ logger.debug("Deploying deployment: {}", deployment)
+ @Suppress("UNCHECKED_CAST")
+ val d = DeploymentContextImpl(deployment as Deployment).apply(block)
+ // populate
+ populateDeployment(d)
+ }
+
+ context(environment: Environment)
+ override fun deploy(deployment: Deployment<*>) {
+ @Suppress("UNCHECKED_CAST")
+ this.deploy(deployment) {}
+ }
+ private fun populateDeployment(deploymentContext: DeploymentContextImpl) {
+ val deployment = deploymentContext.deployment
+ // Additional linking rules
+ deployment.getAssociatedLinkingRule()?.let { newLinkingRule ->
+ val composedLinkingRule =
+ when (val linkingRule = ctx.environment.linkingRule) {
+ is NoLinks -> newLinkingRule
+ is CombinedLinkingRule -> CombinedLinkingRule(linkingRule.subRules + listOf(newLinkingRule))
+ else -> CombinedLinkingRule(listOf(linkingRule, newLinkingRule))
+ }
+ ctx.environment.linkingRule = composedLinkingRule
+ }
+ deployment.stream().forEach { position ->
+ logger.debug("visiting position: {} for deployment: {}", position, deployment)
+ logger.debug("creaing node for deployment: {}", deployment)
+ val node = deploymentContext.nodeFactory?.invoke(deploymentContext)
+ ?: inc.createNode(
+ ctx.simulationGenerator, // Match YAML loader: uses simulationRNG for node creation
+ ctx.environment,
+ null,
+ )
+ // load properties
+ deploymentContext.propertiesContext.applyToNode(node, position)
+ // load contents
+ val contents = deploymentContext.contents
+ for (content in contents) {
+ deploymentContext.applyToNodes(node, position, content)
+ }
+ // load programs
+ val programs = deploymentContext.programsContext.programs
+ val createdPrograms = mutableListOf?, Actionable>>()
+ for (programEntry in programs) {
+ val pp = deploymentContext.programsContext.applyToNodes(
+ node,
+ position,
+ programEntry.program,
+ programEntry.filter,
+ )
+ createdPrograms.add(pp)
+ }
+ logger.debug("programs={}", createdPrograms)
+ logger.debug("Adding node to environment at position: {}", position)
+ ctx.environment.addNode(node, position)
+ }
+ }
+
+ /**
+ * Context for configuring a single deployment.
+ *
+ * @param deployment The deployment being configured.
+ */
+ inner class DeploymentContextImpl(val deployment: Deployment) : DeploymentContext {
+ override val ctx: DeploymentsContext = this@DeploymentsContextImpl
+
+ /**
+ * The list of content contexts for this deployment.
+ */
+ val contents: MutableList = mutableListOf()
+
+ /**
+ * Optional factory for creating custom nodes.
+ */
+ var nodeFactory: (
+ context(DeploymentContext)
+ () -> Node
+ )? = null
+
+ /**
+ * The properties context for this deployment.
+ */
+ var propertiesContext: PropertiesContextImpl = PropertiesContextImpl(this@DeploymentContextImpl)
+
+ /**
+ * The programs context for this deployment.
+ */
+ val programsContext: ProgramsContextImpl = ProgramsContextImpl(this@DeploymentContextImpl)
+ init {
+ logger.debug("Visiting deployment: {}", deployment)
+ }
+
+ override fun all(block: ContentContext.() -> Unit) {
+ logger.debug("Adding content for all positions")
+ val c = ContentContextImpl().apply(block)
+ contents.add(c)
+ }
+
+ override fun inside(filter: PositionBasedFilter<*>, block: ContentContext.() -> Unit) {
+ @Suppress("UNCHECKED_CAST")
+ val typedFilter = filter as PositionBasedFilter
+ logger.debug("Adding content for positions inside filter: {}", typedFilter)
+ val c = ContentContextImpl(typedFilter).apply(block)
+ contents.add(c)
+ }
+
+ override fun programs(block: ProgramsContext.() -> Unit) {
+ programsContext.apply(block)
+ }
+
+ override fun nodes(factory: DeploymentContext.() -> Node) {
+ nodeFactory = factory
+ }
+
+ override fun properties(block: PropertiesContext.() -> Unit) {
+ propertiesContext.apply(block)
+ }
+
+ /**
+ * Applies content to nodes at a specific position.
+ *
+ * @param node The node to apply content to.
+ * @param position The position of the node.
+ * @param content The content context to apply.
+ */
+ fun applyToNodes(node: Node, position: P, content: ContentContextImpl) {
+ logger.debug("Applying node to nodes for position: {}, deployment {}", position, deployment)
+ if (content.filter == null || content.filter.contains(position)) {
+ logger.debug("Creating molecule for node at position: {}", position)
+ val mol = ctx.ctx.incarnation.createMolecule(
+ content.molecule
+ ?: error("Molecule not specified"),
+ )
+ logger.debug("Creating concentration for molecule: {}", mol)
+ val conc = ctx.ctx.incarnation.createConcentration(content.concentration)
+ logger.debug("Setting concentration for molecule: {} to node at position: {}", mol, position)
+ node.setConcentration(mol, conc)
+ }
+ }
+
+ /**
+ * Context for configuring content (molecules and concentrations) for nodes.
+ *
+ * @param filter Optional position filter for applying content.
+ */
+ inner class ContentContextImpl(override val filter: PositionBasedFilter? = null) : ContentContext {
+ /**
+ * The molecule name.
+ */
+ override var molecule: String? = null
+
+ /**
+ * The concentration value.
+ */
+ override var concentration: T? = null
+ }
+ }
+}
diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/ExporterContext.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/ExporterContext.kt
new file mode 100644
index 0000000000..68e67625c7
--- /dev/null
+++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/ExporterContext.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2010-2025, Danilo Pianini and contributors
+ * listed, for each module, in the respective subproject's build.gradle.kts file.
+ *
+ * This file is part of Alchemist, and is distributed under the terms of the
+ * GNU General Public License, with a linking exception,
+ * as described in the file LICENSE in the Alchemist distribution's top directory.
+ */
+
+package it.unibo.alchemist.boundary.dsl.model
+
+import it.unibo.alchemist.boundary.Exporter
+import it.unibo.alchemist.boundary.Extractor
+import it.unibo.alchemist.boundary.dsl.AlchemistDsl
+import it.unibo.alchemist.model.Position
+
+/**
+ * Context interface for configuring data exporters in a simulation.
+ *
+ * Exporters define how simulation data is extracted and exported, supporting formats
+ * such as CSV, MongoDB, and custom formats.
+ * Data can be exported per-node or aggregated
+ * using statistical functions.
+ *
+ * ## Usage Example
+ *
+ * ```kotlin
+* exporter {
+* type = CSVExporter("output", 4.0)
+* data(Time(), moleculeReader("moleculeName"))
+* }
+ * ```
+ *
+ * @param T The type of molecule concentration.
+ * @param P The type of position, must extend [Position].
+ *
+ * @see [SimulationContext.exporter] for adding exporters to a simulation
+ * @see [Exporter] for the exporter interface
+ * @see [Extractor] for data extraction
+ */
+@AlchemistDsl
+interface ExporterContext> {
+
+ /** The parent simulation context. */
+ val ctx: SimulationContext
+
+ /**
+ * The exporter instance that handles data output.
+ *
+ * @see [Exporter]
+ */
+ var type: Exporter?
+
+ /**
+ * Sets the data extractors for this exporter.
+ *
+ * Extractors define which data should be exported from the simulation.
+ *
+ * ```kotlin
+ * data(Time(), moleculeReader("moleculeName"))
+ * ```
+ *
+ * @param extractors The extractors to use for data extraction.
+ * @see [Extractor]
+ */
+ fun data(vararg extractors: Extractor<*>)
+}
diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/ExporterContextImpl.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/ExporterContextImpl.kt
new file mode 100644
index 0000000000..45d3a2c853
--- /dev/null
+++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/ExporterContextImpl.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010-2025, Danilo Pianini and contributors
+ * listed, for each module, in the respective subproject's build.gradle.kts file.
+ *
+ * This file is part of Alchemist, and is distributed under the terms of the
+ * GNU General Public License, with a linking exception,
+ * as described in the file LICENSE in the Alchemist distribution's top directory.
+ */
+
+package it.unibo.alchemist.boundary.dsl.model
+
+import it.unibo.alchemist.boundary.Exporter
+import it.unibo.alchemist.boundary.Extractor
+import it.unibo.alchemist.model.Position
+
+/**
+ * Context for configuring exporters in a simulation.
+ *
+ * @param T The type of molecule concentration.
+ * @param P The type of position.
+ */
+class ExporterContextImpl>(override val ctx: SimulationContext) : ExporterContext {
+ override var type: Exporter? = null
+
+ /**
+ * The list of data extractors.
+ */
+ var extractors: List> = emptyList()
+
+ override fun data(vararg extractors: Extractor<*>) {
+ this.extractors = extractors.toList()
+ }
+}
diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/GlobalProgramsContext.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/GlobalProgramsContext.kt
new file mode 100644
index 0000000000..472752a80b
--- /dev/null
+++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/GlobalProgramsContext.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010-2025, Danilo Pianini and contributors
+ * listed, for each module, in the respective subproject's build.gradle.kts file.
+ *
+ * This file is part of Alchemist, and is distributed under the terms of the
+ * GNU General Public License, with a linking exception,
+ * as described in the file LICENSE in the Alchemist distribution's top directory.
+ */
+
+package it.unibo.alchemist.boundary.dsl.model
+
+import it.unibo.alchemist.model.GlobalReaction
+import it.unibo.alchemist.model.Position
+
+/**
+ * Context for configuring global reactions in a simulation.
+ *
+ * @param T The type of molecule concentration.
+ * @param P The type of position.
+ */
+interface GlobalProgramsContext> {
+ /** The parent simulation context. */
+ val ctx: SimulationContext
+
+ /**
+ * Adds a global reaction to the simulation.
+ *
+ * @param this The global reaction to add.
+ */
+ operator fun GlobalReaction.unaryPlus()
+}
+
+/**
+ * Implementation of [GlobalProgramsContext].
+ *
+ * @param T The type of molecule concentration.
+ * @param P The type of position.
+ */
+class GlobalProgramsContextImpl>(override val ctx: SimulationContext) :
+ GlobalProgramsContext {
+ override fun GlobalReaction.unaryPlus() {
+ ctx.environment.addGlobalReaction(this)
+ }
+}
diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/LayerContext.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/LayerContext.kt
new file mode 100644
index 0000000000..67f9f4b116
--- /dev/null
+++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/LayerContext.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2010-2025, Danilo Pianini and contributors
+ * listed, for each module, in the respective subproject's build.gradle.kts file.
+ *
+ * This file is part of Alchemist, and is distributed under the terms of the
+ * GNU General Public License, with a linking exception,
+ * as described in the file LICENSE in the Alchemist distribution's top directory.
+ */
+
+package it.unibo.alchemist.boundary.dsl.model
+
+import it.unibo.alchemist.boundary.dsl.AlchemistDsl
+import it.unibo.alchemist.model.Layer
+import it.unibo.alchemist.model.Position
+
+/**
+ * Context interface for configuring spatial layers in a simulation.
+ *
+ * Layers define overlays of data that can be sensed everywhere in the environment.
+ * They can be used to model physical properties such as pollution, light, temperature, etc.
+ *
+ * ## Usage Example
+ *
+ * ```kotlin
+* layer {
+* molecule = "A"
+* layer = StepLayer(2.0, 2.0, 100.0, 0.0)
+* }
+ * ```
+ *
+ * @param T The type of molecule concentration.
+ * @param P The type of position, must extend [Position].
+ *
+ * @see [SimulationContext.layer] for adding layers to a simulation
+ * @see [Layer] for the layer interface
+ */
+@AlchemistDsl
+interface LayerContext> {
+ /**
+ * The molecule name associated with this layer.
+ *
+ */
+ var molecule: String?
+
+ /**
+ * The layer instance that provides spatial data.
+ *
+ * @see [Layer]
+ */
+ var layer: Layer?
+}
diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/LayerContextImpl.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/LayerContextImpl.kt
new file mode 100644
index 0000000000..89d546c573
--- /dev/null
+++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/LayerContextImpl.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2010-2025, Danilo Pianini and contributors
+ * listed, for each module, in the respective subproject's build.gradle.kts file.
+ *
+ * This file is part of Alchemist, and is distributed under the terms of the
+ * GNU General Public License, with a linking exception,
+ * as described in the file LICENSE in the Alchemist distribution's top directory.
+ */
+
+package it.unibo.alchemist.boundary.dsl.model
+
+import it.unibo.alchemist.model.Layer
+import it.unibo.alchemist.model.Position
+
+/**
+ * Context for configuring layers in a simulation.
+ *
+ * @param T The type of molecule concentration.
+ * @param P The type of position.
+ */
+class LayerContextImpl> : LayerContext {
+ override var molecule: String? = null
+
+ override var layer: Layer? = null
+}
diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/OutputMonitorsContext.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/OutputMonitorsContext.kt
new file mode 100644
index 0000000000..4d607c4aeb
--- /dev/null
+++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/OutputMonitorsContext.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010-2025, Danilo Pianini and contributors
+ * listed, for each module, in the respective subproject's build.gradle.kts file.
+ *
+ * This file is part of Alchemist, and is distributed under the terms of the
+ * GNU General Public License, with a linking exception,
+ * as described in the file LICENSE in the Alchemist distribution's top directory.
+ */
+
+package it.unibo.alchemist.boundary.dsl.model
+
+import it.unibo.alchemist.boundary.OutputMonitor
+import it.unibo.alchemist.model.Position
+
+/**
+ * Context for configuring output monitors in a simulation.
+ *
+ * @param T The type of molecule concentration.
+ * @param P The type of position.
+ */
+interface OutputMonitorsContext> {
+ /** The parent simulation context. */
+ val ctx: SimulationContext
+
+ /**
+ * Adds an output monitor to the simulation.
+ *
+ * @param this The output monitor to add.
+ */
+ operator fun OutputMonitor.unaryPlus()
+}
+
+/**
+ * Implementation of [OutputMonitorsContext].
+ *
+ * @param T The type of molecule concentration.
+ * @param P The type of position.
+ */
+class OutputMonitorsContextImpl>(override val ctx: SimulationContextImpl) :
+ OutputMonitorsContext {
+ override fun OutputMonitor.unaryPlus() {
+ ctx.monitors += this
+ }
+}
diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/ProgramsContext.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/ProgramsContext.kt
new file mode 100644
index 0000000000..ebd4dc072c
--- /dev/null
+++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/ProgramsContext.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2010-2025, Danilo Pianini and contributors
+ * listed, for each module, in the respective subproject's build.gradle.kts file.
+ *
+ * This file is part of Alchemist, and is distributed under the terms of the
+ * GNU General Public License, with a linking exception,
+ * as described in the file LICENSE in the Alchemist distribution's top directory.
+ */
+
+package it.unibo.alchemist.boundary.dsl.model
+
+import it.unibo.alchemist.boundary.dsl.AlchemistDsl
+import it.unibo.alchemist.model.Action
+import it.unibo.alchemist.model.Condition
+import it.unibo.alchemist.model.Node
+import it.unibo.alchemist.model.Position
+import it.unibo.alchemist.model.PositionBasedFilter
+import it.unibo.alchemist.model.Reaction
+import it.unibo.alchemist.model.TimeDistribution
+
+/**
+ * Context interface for configuring programs (reactions) in a deployment.
+ *
+ * Programs define the behavior of nodes through reactions that execute actions
+ * when conditions are met. Programs can be applied to all nodes or filtered by position.
+ *
+ * ## Usage Example
+ *
+ * ```kotlin
+ * deployments {
+ * deploy(deployment) {
+ * programs {
+ * all {
+ * timeDistribution("1")
+ * program = "{token} --> {firing}"
+ * }
+ * inside(RectangleFilter(-1.0, -1.0, 2.0, 2.0)) {
+ * program = "{firing} --> +{token}"
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param T The type of molecule concentration.
+ * @param P The type of position, must extend [Position].
+ *
+ * @see [DeploymentContext.programs] for configuring programs in a deployment
+ * @see [Reaction] for the reaction interface
+ * @see [TimeDistribution] for time distribution configuration
+ */
+@AlchemistDsl
+interface ProgramsContext> {
+ /**
+ * The deployment context this programs context belongs to.
+ */
+ val ctx: DeploymentContext
+
+ /**
+ * Configures a program for all nodes in the deployment.
+ *
+ * @param block The program configuration block.
+ */
+ fun all(block: ProgramContext.() -> Unit)
+
+ /**
+ * Configures a program for nodes inside a position filter.
+ *
+ * Only nodes whose positions match the filter will receive the configured program.
+ *
+ * @param filter The position filter to apply.
+ * @param block The program configuration block.
+ * @see [PositionBasedFilter]
+ */
+ fun inside(filter: PositionBasedFilter, block: ProgramContext.() -> Unit)
+}
+
+/**
+ * Context interface for configuring a single program (reaction) for a node.
+ *
+ * This context is used within [ProgramsContext] blocks to define reactions with
+ * their time distributions, conditions, and actions.
+ *
+ * @param T The type of molecule concentration.
+ * @param P The type of position, must extend [Position].
+ *
+ * @see [ProgramsContext] for the parent context
+ * @see [Reaction] for the reaction interface
+ * @see [TimeDistribution] for time distribution
+ * @see [Action] for reaction actions
+ * @see [Condition] for reaction conditions
+ */
+@AlchemistDsl
+interface ProgramContext> {
+ /**
+ * The programs context this program context belongs to.
+ */
+ val ctx: ProgramsContext
+
+ /**
+ * The node this program context is configuring.
+ */
+ val node: Node
+
+ /**
+ * The program specification as a string.
+ *
+ * The format depends on the incarnation being used.
+ */
+ var program: String?
+
+ /**
+ * The time distribution for the reaction.
+ *
+ * @see [TimeDistribution]
+ */
+ var timeDistribution: TimeDistribution?
+
+ /**
+ * An optional custom reaction instance.
+ *
+ * If provided, this reaction will be used instead of creating one from [program].
+ *
+ * @see [Reaction]
+ */
+ var reaction: Reaction?
+
+ /**
+ * Sets the time distribution using a string specification.
+ *
+ * The string is processed by the incarnation to create a [TimeDistribution].
+ *
+ * ```kotlin
+ * timeDistribution("1")
+ * ```
+ *
+ * @param td The time distribution specification string.
+ * @see [TimeDistribution]
+ * @see [it.unibo.alchemist.model.Incarnation.createTimeDistribution]
+ */
+ fun timeDistribution(td: String)
+
+ /**
+ * Adds an action to the program.
+ *
+ * Actions are executed when the reaction fires and all conditions are met.
+ *
+ * @param block A factory function that creates the action.
+ * @see [Action]
+ */
+ fun addAction(block: () -> Action)
+
+ /**
+ * Adds an action to the program using the incarnation createAction function.
+ *
+ * @param action the action
+ * @see [it.unibo.alchemist.model.Incarnation.createAction]
+ */
+ fun addAction(action: String)
+
+ /**
+ * Adds a condition to the program.
+ *
+ * Conditions must all be satisfied for the reaction to fire.
+ *
+ * @param block A factory function that creates the condition.
+ * @see [Condition]
+ */
+ fun addCondition(block: () -> Condition)
+
+ /**
+ * Adds a condition to the program, using the incarnation createCondition function.
+ *
+ * @param condition the condition
+ * @see [it.unibo.alchemist.model.Incarnation.createCondition]
+ */
+ fun addCondition(condition: String)
+
+ /**
+ * Unary plus operator for type-safe casting of [TimeDistribution].
+ *
+ * This operator allows casting a [TimeDistribution] with wildcard type parameter
+ * to a specific type parameter, enabling type-safe usage in generic contexts.
+ *
+ * @return The same [TimeDistribution] instance cast to the specified type parameter.
+ */
+ @Suppress("UNCHECKED_CAST")
+ operator fun TimeDistribution<*>.unaryPlus(): TimeDistribution = this as TimeDistribution
+}
diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/ProgramsContextImpl.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/ProgramsContextImpl.kt
new file mode 100644
index 0000000000..917467ddd4
--- /dev/null
+++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/ProgramsContextImpl.kt
@@ -0,0 +1,173 @@
+package it.unibo.alchemist.boundary.dsl.model
+
+import it.unibo.alchemist.boundary.dsl.util.LoadingSystemLogger.logger
+import it.unibo.alchemist.model.Action
+import it.unibo.alchemist.model.Actionable
+import it.unibo.alchemist.model.Condition
+import it.unibo.alchemist.model.Node
+import it.unibo.alchemist.model.Position
+import it.unibo.alchemist.model.PositionBasedFilter
+import it.unibo.alchemist.model.Reaction
+import it.unibo.alchemist.model.TimeDistribution
+
+/**
+ * Context for managing programs (reactions) in a simulation.
+ *
+ * @param T The type of molecule concentration.
+ * @param P The type of position.
+ * @param ctx The deployments context.
+ */
+class ProgramsContextImpl>(override val ctx: DeploymentContext) : ProgramsContext {
+ /**
+ * Entry representing a program with its filter.
+ *
+ * @param filter Optional position filter.
+ * @param program The program configuration block.
+ */
+ inner class ProgramEntry(
+ val filter: PositionBasedFilter?,
+ val program: ProgramsContextImpl.ProgramContextImpl.() -> Unit,
+ )
+
+ /**
+ * List of program entries.
+ */
+ val programs: MutableList = mutableListOf()
+
+ override fun all(block: ProgramContext.() -> Unit) {
+ logger.debug("Adding program for all nodes")
+ programs.add(ProgramEntry(null, block))
+ }
+
+ override fun inside(filter: PositionBasedFilter, block: ProgramContext.() -> Unit) {
+ logger.debug("Adding program for nodes inside filter: {}", filter)
+ programs.add(ProgramEntry(filter, block))
+ }
+
+ /**
+ * Applies a program to nodes at a specific position.
+ *
+ * @param node The node to apply the program to.
+ * @param position The position of the node.
+ * @param program The program configuration block.
+ * @param filter Optional position filter.
+ */
+ fun applyToNodes(
+ node: Node,
+ position: P,
+ program: ProgramContextImpl.() -> Unit,
+ filter: PositionBasedFilter?,
+ ): Pair?, Actionable> {
+ logger.debug("Applying program to node at position: {}", position)
+ val c = ProgramContextImpl(node).apply(program)
+ val context = ctx.ctx.ctx
+ logger.debug("Creating time distribution for program")
+ val timeDistribution = c.timeDistribution
+ ?: context.incarnation.createTimeDistribution(
+ context.simulationGenerator,
+ context.environment,
+ node,
+ null,
+ )
+ logger.debug("Creating reaction for program")
+ val r = c.reaction
+ ?: // Create a basic reaction with custom actions/conditions
+ context.incarnation.createReaction(
+ context.simulationGenerator,
+ context.environment,
+ node,
+ timeDistribution,
+ c.program,
+ )
+ logger.debug("Adding actions to reaction")
+ r.actions += c.actions.map { it() }
+ logger.debug("Adding conditions to reaction")
+ r.conditions += c.conditions.map { it() }
+ logger.debug("Adding reaction to node")
+ if (filter == null || filter.contains(position)) {
+ node.addReaction(r)
+ }
+ return filter to r
+ }
+
+ /**
+ * Context for configuring a single program (reaction).
+ *
+ * @param node The node this program is associated with.
+ * @param ctx The programs' context.
+ */
+ open inner class ProgramContextImpl(override val node: Node) : ProgramContext {
+
+ override val ctx: ProgramsContext = this@ProgramsContextImpl
+ private val context = ctx.ctx.ctx.ctx
+
+ /**
+ * The program name.
+ */
+ override var program: String? = null
+
+ /**
+ * Collection of action factories.
+ */
+ var actions: Collection<() -> Action> = emptyList()
+
+ /**
+ * Collection of condition factories.
+ */
+ var conditions: Collection<() -> Condition> = emptyList()
+
+ /**
+ * The time distribution for the reaction.
+ */
+ override var timeDistribution: TimeDistribution? = null
+
+ /**
+ * Optional custom reaction instance.
+ */
+ override var reaction: Reaction? = null
+
+ override fun timeDistribution(td: String) {
+ timeDistribution = context.incarnation.createTimeDistribution(
+ context.simulationGenerator,
+ context.environment,
+ node,
+ td,
+ )
+ }
+
+ override fun addAction(block: () -> Action) {
+ actions += block
+ }
+
+ override fun addAction(action: String) {
+ actions += {
+ context.incarnation
+ .createAction(
+ context.simulationGenerator,
+ context.environment,
+ node,
+ timeDistribution,
+ reaction,
+ action,
+ )
+ }
+ }
+
+ override fun addCondition(block: () -> Condition) {
+ conditions += block
+ }
+
+ override fun addCondition(condition: String) {
+ conditions += {
+ context.incarnation.createCondition(
+ context.simulationGenerator,
+ context.environment,
+ node,
+ timeDistribution,
+ reaction,
+ condition,
+ )
+ }
+ }
+ }
+}
diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/PropertiesContext.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/PropertiesContext.kt
new file mode 100644
index 0000000000..fd237670d6
--- /dev/null
+++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/PropertiesContext.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2010-2025, Danilo Pianini and contributors
+ * listed, for each module, in the respective subproject's build.gradle.kts file.
+ *
+ * This file is part of Alchemist, and is distributed under the terms of the
+ * GNU General Public License, with a linking exception,
+ * as described in the file LICENSE in the Alchemist distribution's top directory.
+ */
+
+package it.unibo.alchemist.boundary.dsl.model
+
+import it.unibo.alchemist.boundary.dsl.AlchemistDsl
+import it.unibo.alchemist.model.Node
+import it.unibo.alchemist.model.NodeProperty
+import it.unibo.alchemist.model.Position
+import it.unibo.alchemist.model.PositionBasedFilter
+
+/**
+ * Context interface for configuring node properties in a deployment.
+ *
+ * Properties can be assigned to nodes based on their position using filters,
+ * or applied to all nodes in the deployment.
+ *
+ * ## Usage Example
+ *
+ * ```kotlin
+ * deployments {
+ * deploy(deployment) {
+ * properties {
+ * inside(RectangleFilter(-3.0, -3.0, 2.0, 2.0)) {
+ * add(MyNodeProperty())
+ * }
+ * all {
+ * add(CommonProperty())
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param T The type of molecule concentration.
+ * @param P The type of position, must extend [Position].
+ *
+ * @see [DeploymentContext.properties] for configuring properties in a deployment
+ * @see [NodeProperty] for the property interface
+ * @see [PositionBasedFilter] for position filtering
+ */
+
+@AlchemistDsl
+interface PropertiesContext> {
+ /**
+ * The deployment context this properties context belongs to.
+ */
+ val ctx: DeploymentContext
+
+ /**
+ * Configures properties for nodes inside a position filter.
+ *
+ * Only nodes whose positions match the filter will receive the configured properties.
+ *
+ * @param filter The position filter to apply.
+ * @param block The property configuration block.
+ * @see [PositionBasedFilter]
+ */
+ fun inside(filter: PositionBasedFilter<*>, block: PropertyContext.() -> Unit)
+
+ /**
+ * Configures properties for all nodes in the deployment.
+ *
+ * @param block The property configuration block.
+ */
+ fun all(block: PropertyContext.() -> Unit)
+}
+
+/**
+ * Context interface for configuring properties for a specific node.
+ *
+ * This context is used within [PropertiesContext] blocks to add properties to nodes.
+ *
+ * @param T The type of molecule concentration.
+ * @param P The type of position, must extend [Position].
+ *
+ * @see [PropertiesContext] for the parent context
+ * @see [NodeProperty] for the property interface
+ */
+@AlchemistDsl
+interface PropertyContext> {
+ /**
+ * The properties context this property context belongs to.
+ */
+ val ctx: PropertiesContext
+
+ /**
+ * The optional position filter applied to this property context.
+ */
+ val filter: PositionBasedFilter?
+
+ /**
+ * The node this property context is configuring.
+ */
+ val node: Node
+
+ /**
+ * Adds a property to the node.
+ *
+ * @param property The property to add to the node.
+ * @see [NodeProperty]
+ */
+ operator fun NodeProperty.unaryPlus()
+}
diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/PropertiesContextImpl.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/PropertiesContextImpl.kt
new file mode 100644
index 0000000000..cbe494b978
--- /dev/null
+++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/PropertiesContextImpl.kt
@@ -0,0 +1,72 @@
+package it.unibo.alchemist.boundary.dsl.model
+
+import it.unibo.alchemist.boundary.dsl.util.LoadingSystemLogger.logger
+import it.unibo.alchemist.model.Node
+import it.unibo.alchemist.model.NodeProperty
+import it.unibo.alchemist.model.Position
+import it.unibo.alchemist.model.PositionBasedFilter
+
+/**
+ * Context for managing node properties in a simulation.
+ *
+ * @param T The type of molecule concentration.
+ * @param P The type of position.
+ */
+class PropertiesContextImpl>(override val ctx: DeploymentContext) : PropertiesContext {
+ /**
+ * List of property contexts with their associated filters.
+ */
+ val propertiesCtx: MutableList Unit, PositionBasedFilter?>> = mutableListOf()
+
+ override fun inside(filter: PositionBasedFilter<*>, block: PropertyContext.() -> Unit) {
+ @Suppress("UNCHECKED_CAST")
+ val typedFilter = filter as PositionBasedFilter
+ propertiesCtx.add(block to typedFilter)
+ logger.debug("Adding property for nodes inside filter: {}", typedFilter)
+ }
+
+ override fun all(block: PropertyContext.() -> Unit) {
+ propertiesCtx.add(block to null)
+ logger.debug("Adding property for all nodes")
+ }
+
+ /**
+ * Applies configured properties to a node at a specific position.
+ *
+ * @param node The node to apply properties to.
+ * @param position The position of the node.
+ */
+ fun applyToNode(node: Node, position: P) {
+ propertiesCtx.forEach { (propertyCtx, filter) ->
+ if (filter == null || filter.contains(position)) {
+ val properties = PropertyContextImpl(filter, node)
+ .apply(propertyCtx)
+ .properties
+ properties.forEach { property ->
+ logger.debug("Applying property: {} to node: {}", property, node)
+ node.addProperty(property)
+ }
+ }
+ }
+ }
+
+ /**
+ * Context for configuring properties for a specific node.
+ *
+ * @param filter Optional position filter.
+ * @param node The node to configure properties for.
+ */
+ inner class PropertyContextImpl(override val filter: PositionBasedFilter?, override val node: Node) :
+ PropertyContext {
+ override val ctx: PropertiesContext = this@PropertiesContextImpl
+
+ /**
+ * List of properties to add to the node.
+ */
+ val properties: MutableList> = mutableListOf()
+
+ override operator fun NodeProperty.unaryPlus() {
+ properties.add(this)
+ }
+ }
+}
diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/SimulationContext.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/SimulationContext.kt
new file mode 100644
index 0000000000..74c486dedf
--- /dev/null
+++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/SimulationContext.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2010-2025, Danilo Pianini and contributors
+ * listed, for each module, in the respective subproject's build.gradle.kts file.
+ *
+ * This file is part of Alchemist, and is distributed under the terms of the
+ * GNU General Public License, with a linking exception,
+ * as described in the file LICENSE in the Alchemist distribution's top directory.
+ */
+
+package it.unibo.alchemist.boundary.dsl.model
+
+import it.unibo.alchemist.boundary.Launcher
+import it.unibo.alchemist.boundary.OutputMonitor
+import it.unibo.alchemist.boundary.Variable
+import it.unibo.alchemist.boundary.dsl.AlchemistDsl
+import it.unibo.alchemist.boundary.dsl.Dsl.incarnation
+import it.unibo.alchemist.model.Environment
+import it.unibo.alchemist.model.GlobalReaction
+import it.unibo.alchemist.model.Incarnation
+import it.unibo.alchemist.model.LinkingRule
+import it.unibo.alchemist.model.Position
+import it.unibo.alchemist.model.TerminationPredicate
+import java.io.Serializable
+import org.apache.commons.math3.random.RandomGenerator
+
+/**
+ * Main context interface for building and configuring Alchemist simulations using the DSL.
+ *
+ * This interface provides a type-safe way to configure simulations programmatically.
+ * It serves as the entry point for DSL users to define
+ * all aspects of a simulation including deployments, programs, monitors, exporters, and more.
+ *
+ * ## Usage Example
+ *
+ * ```kotlin
+ * simulation(incarnation, environment) {
+ * networkModel = ConnectWithinDistance(0.5)
+ * deployments {
+ * deploy(Grid(-5, -5, 5, 5, 0.25, 0.25)) {
+ * all {
+ * molecule = "moleculeName"
+ * concentration = 1.0
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param T The type of molecule concentration used in the simulation
+ * @param P The type of position used in the environment, must extend [Position]
+ *
+ * @see [it.unibo.alchemist.boundary.dsl.Dsl] for creating simulation contexts
+ * @see [DeploymentsContextImpl] for deployment configuration
+ * @see [ProgramsContextImpl] for program configuration
+ * @see [ExporterContextImpl] for exporter configuration
+ * @see [LayerContextImpl] for layer configuration
+ */
+@Suppress("UndocumentedPublicFunction") // Detekt false positive with context parameters
+interface SimulationContext> {
+ /**
+ * The incarnation instance that defines how molecules, nodes, and reactions are created.
+ *
+ * ## Creating an Incarnation
+ *
+ * Incarnations are created from the [AvailableIncarnations] enum using the extension function:
+ * ```kotlin
+ *
+ * simulation(AvailableIncarnations.SAPERE.incarnation(), environment) {
+ * // simulation configuration
+ * }
+ * ```
+ *
+ *
+ * @see [AvailableIncarnations] for the DSL enum of available incarnations
+ * @see [Incarnation] for the incarnation interface
+ * @see [it.unibo.alchemist.boundary.dsl.Dsl.incarnation] for converting enum to instance
+ */
+ val incarnation: Incarnation
+
+ /**
+ * The environment where the simulation takes place.
+ *
+ * @see [Environment]
+ */
+ val environment: Environment
+
+ /**
+ * The launcher responsible for executing the simulation.
+ *
+ * Some implementations are available in [it.unibo.alchemist.boundary.launchers].
+ *
+ * @see [Launcher]
+ */
+ var launcher: Launcher
+
+ /**
+ * Random number generator controlling the evolution of the events of the simulation.
+ *
+ * @see [RandomGenerator]
+ */
+ var simulationGenerator: RandomGenerator
+
+ /**
+ * Random number generator controlling the position of random deployments.
+ *
+ * @see [RandomGenerator]
+ */
+ var scenarioGenerator: RandomGenerator
+
+ /**
+ * The network model (linking rule) that defines how nodes connect in the environment.
+ *
+ * @see [LinkingRule]
+ */
+ var networkModel: LinkingRule
+
+ /**
+ * Configures node deployments for the simulation.
+ *
+ * ## Usage Example
+ * ```kotlin
+ * deployments {
+ * deploy(point(0,0))
+ * ...
+ * }
+ * ```
+ *
+ * @see [DeploymentsContextImpl] to configure deployments
+ */
+ context(randomGenerator: RandomGenerator, environment: Environment)
+ fun deployments(block: DeploymentsContext.() -> Unit)
+
+ /**
+ * Adds a termination predicate to the simulation.
+ *
+ * @param terminator The termination predicate to add
+ * @see [TerminationPredicate]
+ */
+ fun terminators(block: TerminatorsContext.() -> Unit)
+
+ /**
+ * Adds an output monitor to the simulation.
+ *
+ * @param monitor The output monitor to add
+ * @see [OutputMonitor]
+ */
+ fun monitors(block: OutputMonitorsContext.() -> Unit)
+
+ /**
+ * Add an exporter to the simulation for data output.
+ *
+ * @param block The configuration block
+ * @see [ExporterContextImpl]
+ */
+ fun exporter(block: ExporterContext.() -> Unit)
+
+ /**
+ * Configures a global program.
+ *
+ * @param program the global reaction to add
+ * @see [GlobalReaction]
+ */
+ fun programs(block: GlobalProgramsContext.() -> Unit)
+
+ /**
+ * Schedules a block of code to execute later during the loading process.
+ *
+ * This is useful for debug purposes or for operations that need to be deferred
+ *
+ * Example:
+ * ```kotlin
+ * runLater {
+ * environment.nodes.forEach { node ->
+ * println("Node: ${node}")
+ * }
+ * }
+ * ```
+ *
+ * @param block The block of code to execute later
+ */
+ fun runLater(block: context(SimulationContext) () -> Unit)
+
+ /**
+ * Add a spatial layer for a molecule.
+ *
+ * It is possible to define overlays (layers) of data that can be sensed
+ * everywhere in the environment
+ *
+ * @param block The configuration block
+ * @see [LayerContextImpl]
+ */
+ fun layer(block: LayerContext.() -> Unit)
+
+ /**
+ * Registers a Linear Variable for batch simulations.
+ *
+ * Example usage with a range variable:
+ * ```kotlin
+ * var myParam by variable(RangeVariable(0.0, 10.0, 0.5))
+ * ```
+ *
+ * @param source The variable source that provides the range of values
+ * @see [Variable]
+ */
+ fun variable(source: Variable): VariablesContext.VariableProvider
+
+ /**
+ * Registers a dependent variable that is computed from other variables.
+ *
+ * Example usage::
+ * ```kotlin
+ * var param by variable(RangeVariable(0.0, 10.0, 0.5))
+ * var computedParam by variable { param * 2.0 }
+ * ```
+ *
+ * @param source A function that computes the variable value
+ */
+ fun variable(source: () -> A): VariablesContext.DependentVariableProvider
+}
diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/SimulationContextImpl.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/SimulationContextImpl.kt
new file mode 100644
index 0000000000..82b91a5823
--- /dev/null
+++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/SimulationContextImpl.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2010-2025, Danilo Pianini and contributors
+ * listed, for each module, in the respective subproject's build.gradle.kts file.
+ *
+ * This file is part of Alchemist, and is distributed under the terms of the
+ * GNU General Public License, with a linking exception,
+ * as described in the file LICENSE in the Alchemist distribution's top directory.
+ */
+
+@file:Suppress("UNCHECKED_CAST")
+
+package it.unibo.alchemist.boundary.dsl.model
+
+import it.unibo.alchemist.boundary.Launcher
+import it.unibo.alchemist.boundary.OutputMonitor
+import it.unibo.alchemist.boundary.Variable
+import it.unibo.alchemist.boundary.dsl.util.LoadingSystemLogger.logger
+import it.unibo.alchemist.boundary.launchers.DefaultLauncher
+import it.unibo.alchemist.model.Environment
+import it.unibo.alchemist.model.Incarnation
+import it.unibo.alchemist.model.Layer
+import it.unibo.alchemist.model.LinkingRule
+import it.unibo.alchemist.model.Position
+import java.io.Serializable
+import org.apache.commons.math3.random.MersenneTwister
+import org.apache.commons.math3.random.RandomGenerator
+
+/**
+ * Main context for building and configuring a simulation.
+ *
+ * @param T The type of molecule concentration.
+ * @param P The type of position.
+ */
+
+class SimulationContextImpl>(
+ override val incarnation: Incarnation,
+ private var envIstance: Environment? = null,
+) : SimulationContext {
+ /** The environment instance (internal use). */
+ override val environment: Environment
+ get() = requireNotNull(envIstance) { "Environment has not been initialized yet" }
+
+ /**
+ * List of build steps to execute.
+ */
+ val buildSteps: MutableList.() -> Unit> = mutableListOf()
+
+ /**
+ * List of output .
+ */
+ val monitors: MutableList> = mutableListOf()
+
+ /**
+ * List of exporters.
+ */
+ val exporters: MutableList> = mutableListOf()
+
+ override var launcher: Launcher = DefaultLauncher()
+
+ /**
+ * Map of variable references.
+ */
+ val references: MutableMap = mutableMapOf()
+
+ private var _scenarioGenerator: RandomGenerator? = null
+ private var _simulationGenerator: RandomGenerator? = null
+
+ override var scenarioGenerator: RandomGenerator
+ get() {
+ return _scenarioGenerator ?: MersenneTwister(0L).also { _scenarioGenerator = it }
+ }
+ set(value) {
+ buildSteps.add { this._scenarioGenerator = value }
+ }
+
+ override var simulationGenerator: RandomGenerator
+ get() {
+ return _simulationGenerator ?: MersenneTwister(0L).also { _simulationGenerator = it }
+ }
+ set(value) {
+ buildSteps.add { this._simulationGenerator = value }
+ }
+
+ private val layers: MutableMap> = HashMap()
+
+ override var networkModel: LinkingRule
+ get() = environment.linkingRule
+ set(value) {
+ buildSteps.add { this.environment.linkingRule = value }
+ }
+
+ /**
+ * The variables context for managing simulation variables.
+ */
+ val variablesContext = VariablesContext()
+
+ /**
+ * Build a fresh new simulation context instance, and applies
+ * all the build steps to it.
+ * To ensure that each instance has
+ * its own variables spaces: check the [VariablesContext] documentation for more details.
+ * @see [VariablesContext]
+ */
+ fun build(envInstance: Environment, values: Map): SimulationContextImpl {
+ val batchContext = SimulationContextImpl(incarnation)
+ batchContext.envIstance = envInstance
+ batchContext.variablesContext.variables += this.variablesContext.variables
+ batchContext.variablesContext.dependentVariables += this.variablesContext.dependentVariables
+ logger.debug("Binding variables to batchInstance: {}", values)
+ this.variablesContext.addReferences(values)
+ buildSteps.forEach { batchContext.apply(it) }
+ return batchContext
+ }
+
+ context(randomGenerator: RandomGenerator, environment: Environment)
+ override fun deployments(block: DeploymentsContext.() -> Unit) {
+ logger.debug("adding deployments block inside {}", this)
+ buildSteps.add {
+ logger.debug("Configuring deployments inside {}", this)
+ DeploymentsContextImpl(this).apply(block)
+ }
+ }
+
+ override fun terminators(block: TerminatorsContext.() -> Unit) {
+ @Suppress("UNCHECKED_CAST")
+ buildSteps.add { TerminatorsContextImpl(this).block() }
+ }
+
+ override fun monitors(block: OutputMonitorsContext.() -> Unit) {
+ buildSteps.add { OutputMonitorsContextImpl(this).block() }
+ }
+
+ override fun exporter(block: ExporterContext.() -> Unit) {
+ buildSteps.add { this.exporters.add(ExporterContextImpl(this).apply(block)) }
+ }
+
+ override fun programs(block: GlobalProgramsContext.() -> Unit) {
+ buildSteps.add { GlobalProgramsContextImpl(this).block() }
+ }
+
+ override fun runLater(block: context(SimulationContext)() -> Unit) {
+ buildSteps.add { block() }
+ }
+
+ override fun layer(block: LayerContext.() -> Unit) {
+ buildSteps.add {
+ val l = LayerContextImpl().apply(block)
+ val layer = requireNotNull(l.layer) { "Layer must be specified" }
+ val moleculeName = requireNotNull(l.molecule) { "Molecule must be specified" }
+ require(!this.layers.containsKey(moleculeName)) {
+ "Inconsistent layer definition for molecule $moleculeName. " +
+ "There must be a single layer per molecule"
+ }
+ val molecule = incarnation.createMolecule(moleculeName)
+ logger.debug("Adding layer for molecule {}: {}", moleculeName, layer)
+ this.layers[moleculeName] = layer
+ this.environment.addLayer(molecule, layer)
+ }
+ }
+
+ override fun variable(source: Variable): VariablesContext.VariableProvider =
+ variablesContext.register(source)
+
+ override fun variable(source: () -> A): VariablesContext.DependentVariableProvider =
+ variablesContext.dependent(source)
+}
diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/TerminatorsContext.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/TerminatorsContext.kt
new file mode 100644
index 0000000000..2a0bc66008
--- /dev/null
+++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/TerminatorsContext.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2010-2025, Danilo Pianini and contributors
+ * listed, for each module, in the respective subproject's build.gradle.kts file.
+ *
+ * This file is part of Alchemist, and is distributed under the terms of the
+ * GNU General Public License, with a linking exception,
+ * as described in the file LICENSE in the Alchemist distribution's top directory.
+ */
+
+package it.unibo.alchemist.boundary.dsl.model
+
+import it.unibo.alchemist.boundary.dsl.AlchemistDsl
+import it.unibo.alchemist.model.Position
+import it.unibo.alchemist.model.TerminationPredicate
+
+/**
+ * Context for configuring termination predicates in a simulation.
+ *
+ * @param T The type of molecule concentration.
+ * @param P The type of position.
+ */
+@AlchemistDsl
+interface TerminatorsContext> {
+ /** The parent simulation context. */
+ val ctx: SimulationContext
+
+ /**
+ * Adds a termination predicate to the simulation.
+ *
+ * @param this The termination predicate to add.
+ */
+ operator fun TerminationPredicate<*, *>.unaryPlus()
+}
+
+/**
+ * Implementation of [TerminatorsContext].
+ *
+ * @param T The type of molecule concentration.
+ * @param P The type of position.
+ */
+class TerminatorsContextImpl>(override val ctx: SimulationContext) :
+ TerminatorsContext {
+ @Suppress("UNCHECKED_CAST")
+ override fun TerminationPredicate<*, *>.unaryPlus() {
+ ctx.environment.addTerminator(this as TerminationPredicate)
+ }
+}
diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/VariablesContext.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/VariablesContext.kt
new file mode 100644
index 0000000000..f60078b0cc
--- /dev/null
+++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/dsl/model/VariablesContext.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2010-2025, Danilo Pianini and contributors
+ * listed, for each module, in the respective subproject's build.gradle.kts file.
+ *
+ * This file is part of Alchemist, and is distributed under the terms of the
+ * GNU General Public License, with a linking exception,
+ * as described in the file LICENSE in the Alchemist distribution's top directory.
+ */
+
+package it.unibo.alchemist.boundary.dsl.model
+
+import it.unibo.alchemist.boundary.Variable
+import it.unibo.alchemist.boundary.dsl.util.LoadingSystemLogger.logger
+import java.io.Serializable
+import kotlin.properties.ReadOnlyProperty
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
+
+/**
+ * Context for managing variables in a simulation.
+ */
+class VariablesContext {
+
+ /** Map of default variable values. */
+ val defaults: MutableMap = mutableMapOf()
+
+ /** Thread-local map of variable references. */
+ val references = object : ThreadLocal