KSLApp Session
Unified façade over the app runner/orchestrator layer.
UI code should hold one session for the application lifetime, submit RunSpec instances through submit, observe the returned RunHandle, and call close during application shutdown.
Quick start (synchronous)
Callers that just want to "run a configuration and read the result" — typical test code or a non-coroutine fun main() — can use submitAndAwaitBlocking to skip coroutines entirely:
fun main() {
val session = KSLAppSession(provider)
val result = session.submitAndAwaitBlocking(RunSpec.Single(myConfig))
println(result)
}The asynchronous submit / RunHandle path remains the right choice for GUIs and any caller that wants to observe lifecycle events; see submitAndAwaitBlocking for the full set of caveats.
Dispatch
submit selects validator and execution path by RunSpec variant:
RunSpec.Single / RunSpec.Scenarios / RunSpec.Experiment → validated by RunConfigurationValidator and executed through SingleRunOrchestrator, ScenarioOrchestrator, or ExperimentOrchestrator respectively.
RunSpec.Optimization → validated by OptimizationConfigurationValidator; on success a
Solveris built by OptimizationSolverFactory from the spec's ksl.app.config.optimization.OptimizationRunConfiguration and handed to OptimizationOrchestrator. Programmatic users who already hold a built solver can callOptimizationOrchestrator().submit(solver, …)directly and bypass the session.
Validation errors return an immediately-failed RunHandle rather than throwing, so the same event/result protocol covers all outcomes.
Lifecycle
Every handle returned from submit is retained on the session so that close can cancel any in-flight runs and release the owned coroutine scope. close() is idempotent.