releases
solverforge 0.24.x: Explicit Configuration, Constraint Weights, and Problem Changes
The 0.24.x line reads solver.toml only from the model, rejects unknown configuration keys, adds runtime constraint weights and problem changes, and trims the public API to what applications use.
solverforge 0.24.x follows solverforge 0.23.0; the line was published on
crates.io on 2026-11-01. The first release is
0.24.0, with a Rust 1.95
floor. cargo add solverforge installs 0.24.0, and docs.rs serves the 0.24.0
API. Fresh solverforge-cli 3.8.x scaffolds target it; existing applications
keep their pins until they upgrade.
What Changed
solver.toml comes from the model — breaking change
A #[planning_solution] reads solver configuration only from the file its
solver_toml = "..." argument names, embedded at compile time, or from a
SolverConfig the application loads itself with SolverConfig::load or
SolverConfig::from_toml_file. Nothing reads solver.toml from the process
working directory. A model without solver_toml runs the default
configuration wherever it is launched. Add solver_toml = "../../solver.toml"
(the CLI scaffold’s value, relative to src/domain/plan.rs) to keep using a
project file.
An embedded file that does not parse fails the solve with
RuntimeBuildError::Configuration instead of panicking or silently running the
defaults.
Configuration is validated — breaking changes
- Unknown keys anywhere in solver configuration fail to load with
ConfigError::Toml; a misspelled key no longer disappears. - YAML configuration is removed:
SolverConfig::from_yaml_file,SolverConfig::from_yaml_str, andConfigError::Yamlare gone. Convert YAML files to TOML and load them withfrom_toml_fileorfrom_toml_str. - Acceptor and forager values are checked when the configuration compiles.
AcceptorBuilder::build,AcceptorBuilder::build_with_seed, andForagerBuilder::buildreturnResult<_, ConfigValueError>, and anaccepted_countforager withlimit = 0is rejected instead of running with limit 1. - A
best_score_limitthat does not parse fails the solve. - In Rust,
AcceptorConfig::HillClimbingand the unit-likeForagerConfigvariants are written with{}.
Runtime constraint weights
A #[constraint_weights] field of type ConstraintWeights on the planning
solution binds a multiplier to each named constraint. The weights travel with
the solution, so snapshots, best solutions, and evaluator replicas carry them,
and changing a weight is an ordinary problem change. Each constraint’s total,
incremental delta, and match scores are scaled by the same integer multiplier,
so incremental scoring, full recalculation, and explanations agree. Models
without the field compile the unweighted path unchanged.
Solutions gain validate_constraint_weights, and a solve fails at start on
weights that name an unknown constraint. Score analysis reports
ConstraintAnalysis::weight_multiplier (a u32) in place of the removed
weight field.
Breaking for hand-written scoring code: ConstraintSet implementations must
provide constraint_weight_refs and the *_weighted methods;
IncrementalConstraint::weight(), ConstraintWeightOverrides, and
WeightProvider are removed; a ScoreDirector built with new or
with_descriptor does not apply a solution’s weights, so use the weighted
constructors. The single-constraint marker is public as SingleConstraint;
hand-written constraints write impl SingleConstraint for MyConstraint {}.
Problem changes reach running jobs
ProblemChange, ClosureProblemChange, and ProblemChangeId are exported
next to SolverManager, so a facade-only application can change a running
job’s problem. Changes are queued, applied at runtime safe points in
submission order, and acknowledged in telemetry; a paused job applies its
queue after resume. Changes are consumed by ownership, SolverHandle is
removed, and SolverManager::solve returns a SolverEventReceiver instead of
Tokio’s UnboundedReceiver. Weights installed by a problem change are
validated like the initial ones.
Stream sources are the model’s collections — breaking change
Constraint streams accept only planning-model collections as sources: pass
the generated accessor, such as Plan::shifts(). Closures, fn items, and
vec(...) are no longer stream sources, and a hand-written
CollectionExtract must report a change_source. Fluent same-collection
self-joins can continue past two sources, and a five-source join keeps its
cross-stream filters when it continues.
Scores
Every Score implementation provides scale(i64). Score arithmetic that
overflows i64 panics in release builds instead of wrapping; multiply and
divide panic for non-finite factors and zero divisors, and the
overflowing_* methods report overflow without panicking.
HardSoftDecimalScore parses and prints with exact fixed-point arithmetic.
BendableScore serializes under the serde feature.
A smaller public API — breaking changes
solverforge::bridgeis removed andsolverforge::consoleexposes onlyinit(); depend onsolverforge-bridgeorsolverforge-consoledirectly for anything else.- The
decimalfeature is removed fromsolverforgeandsolverforge-core; drop it fromfeatures = [...].verbose-loggingenablesconsole. solverforge-solveritems are reachable only at the crate root (solverforge_solver::Name). Host-provider contracts such asRuntimeProviderRegistryare imported fromsolverforge-bridge.MoveArenaandMoveSelector::append_movesare removed; selectors yield candidates through their cursors.- Public types that nothing produced or consumed are deleted from
solverforge-coreandsolverforge-scoring(and from the facade’sdomainandanalysismodules), including the listener and supply modules, the value-range provider traits,ScoreExplanation, and the indictment types. Solvable::solveand the synchronous solve returnRuntimeBuildResult<()>instead of re-panicking;SearchContext::newand the descriptor move selector builder are fallible, andSearchContext::try_newis removed.SolverScope::rng()is private; userandom_beloworrandom_u64.VrpSolution::vehicle_data(&self, usize) -> Option<&ProblemData>replaces the raw-pointervehicle_data_ptr.ListVariableSlotdrops the unusedconstruction_list_removehook.SelectorTelemetrydrops six conflict-repair fields that were always zero.
Move selectors and construction reject values outside a variable’s declared domain.
Published Version Boundaries
| Surface | Version | Notes |
|---|---|---|
| Core runtime | solverforge 0.24.0 |
Rust 1.95; explicit configuration, constraint weights, problem changes, trimmed API |
| Previous core runtime | solverforge 0.23.0 |
Parallel evaluation and per-phase telemetry; see the 0.23.x release note |
| CLI | solverforge-cli 3.8.1 |
Fresh scaffolds target core 0.24.0; see the CLI 3.8.x release note |
| Previous CLI | solverforge-cli 3.7.1 |
Scaffolds core 0.23.0; existing projects retain their pins |
| Python | solverforge-py 0.7.2 / PyPI solverforge 0.7.2 |
Still compiled onto core 0.23.0; upgrading the Rust core does not retarget these wheels |
| Worked use cases | November 3 app releases | All eight target core 0.24.0 and UI 0.9.0; see the use-case 3.0 release note |
| UI | solverforge-ui 0.9.0 |
Unchanged |
| Maps | solverforge-maps 2.1.4 |
Unchanged |
The registry records the facade crate’s publication at 2026-11-01T14:24:08Z.
Its artifact SHA-256 is
588a097929d85401d12561bda10dd20cdf784aa78abc6b19120b12ab50d9a852;
.cargo_vcs_info.json binds it to
c114e2d2bfabaada78beb5ee77318b847b964489, the v0.24.0 tag commit. The
GitHub Release
was published at 2026-11-01T14:24:28Z.
On that commit the CI run passed on Linux, macOS, and Windows, plus the MSRV, docs, and package jobs. The Release workflow re-ran that CI matrix, verified the release, published to crates.io, and created the GitHub Release; every job succeeded.
Patch History
| Version | Date | Notes |
|---|---|---|
0.24.0 |
2026-11-01 | solver.toml only from solver_toml or an explicit SolverConfig; unknown keys and YAML rejected; runtime constraint weights with weight_multiplier; problem changes through the facade; accessor-only stream sources; overflow-checked scores; trimmed public API. |
Upgrade Checklist
- Add
solver_toml = "<path>"to every#[planning_solution]that relies on asolver.tomlfile, or load the file withSolverConfig::load; then check that a solve uses the settings you expect. - Remove unknown or misspelled keys from solver configuration, convert YAML
configuration to TOML, and replace
accepted_countlimit = 0. - Replace closure,
fn, andvec(...)stream sources with the generated collection accessors. - Read
weight_multiplierinstead ofConstraintAnalysis::weight; update hand-writtenConstraintSetand constraint implementations for the weighted methods andSingleConstraint. - Drop the
decimalfeature; import bridge, console, and solver internals from their own crates and crate roots. - Update
SolverManager::solvecallers forSolverEventReceiver, custom selectors that overrodeappend_moves, andVrpSolutionimplementations. - Run full-assert or fresh-evaluation comparisons after upgrading custom scoring code, and treat a new overflow panic as a weight or score-range bug.
Where to read next
Read the core manual and the solver configuration guide. The full compare covers all 107 commits, and the CHANGELOG lists every breaking change.