releases
SolverForge 0.18.x: One Compiled Search Runtime
SolverForge 0.18.x compiles native and dynamic models into one immutable search graph, adds resolved selector policy and qualified candidate traces, and hardens retained lifecycle control.
SolverForge 0.18.x started with
v0.18.0
on 2026-07-13. The current core patch is
v0.18.0. GitHub CI, the GitHub
Release workflow, and the coordinated crates.io publish completed successfully.
The 0.18 line keeps the Rust 1.95 floor and publishes the workspace crates on
one version line.
This release makes one architectural promise concrete: native Rust models, dynamic host-language models, explicit phase configs, and omitted-config defaults all enter the same validated compiler and executor. There is no second descriptor phase builder or binding-only fallback that can drift from the main runtime.
The runtime release remains separate from scaffold and binding publication.
The published solverforge-cli 2.2.2 package still scaffolds
solverforge 0.15.2; the published solverforge-py 0.6.1 package compiles
Python models into this solverforge 0.18.0 runtime and embeds
solverforge-ui 0.7.0. See the
SolverForge Python 0.6.x release post.
The worked applications moved with this runtime line. The tagged
solverforge-usecases bundle ships solverforge-hospital@2.0.4,
solverforge-lessons@2.0.4, solverforge-deliveries@2.0.4, and
solverforge-fsr@2.0.5; all four target SolverForge 0.18.0, retain the
solverforge-ui 0.6.5 web-shell line, and passed the bundle CI and Space sync
workflows. Their long-form guides record the app-specific metadata, datasets,
solver policies, and runtime behavior rather than treating the core version
bump as the whole upgrade.
What Changed
One immutable runtime graph
SolverForge now resolves a value-owned RuntimeModel into one immutable graph
before solving. The compiler validates and freezes:
- construction stages and obligations
- recursive local-search and VND selector trees
- scalar, list, grouped-scalar, and dynamic slot bindings
- native and host compound-provider handles
- stable list element-source identities
- candidate metrics, selector order, union weighting, and score tie policy
- configured and model-aware default phases
Macro-generated Solvable implementations and the dynamic bridge both use this
path. A failure is reported as a declaration, compilation, phase preparation,
or phase execution error with a specific path; private graph types and an
alternate fallback runner are not exposed.
Cursor-owned candidates and stable source identity
Shared scalar and list kernels now expose stable candidate IDs. Foragers can stop without draining the rest of a neighborhood, losing candidates are released promptly, and the selected move transfers by value exactly once. Generated telemetry therefore counts candidates actually yielded to the engine, not a logical tail the engine never requested.
Specialized list construction also binds elements by an explicit stable source
key. ListConstructionPhaseBuilder, ListCheapestInsertionPhase,
ListRegretInsertionPhase, and ListClarkeWrightPhase lower-level callers must
map declared elements, current assignments, and precedence successors to the
same unique usize identity. Generated usize list models supply that key.
Duplicate, missing, or inconsistent identities fail before candidate work;
SolverForge does not recover identity through payload equality or hashing.
Selector policy is explicit data
Every non-composite leaf can select original, seeded random, shuffled,
sorted, or probabilistic order. Sorted and probabilistic order require a
registered named candidate metric. Local search also owns a seeded score
tie-break policy, while union selectors now separate scheduling order from
child weighting:
[[phases]]
type = "local_search"
score_tie_break = "random"
[phases.move_selector]
type = "union_move_selector"
selection_order = "stratified_random"
weighting = "fixed"
weights = [3, 1]
[[phases.move_selector.selectors]]
type = "change_move_selector"
selection_order = "random"
[[phases.move_selector.selectors]]
type = "swap_move_selector"
selection_order = "random"
Union order can be sequential, round-robin, rotating round-robin, random, or stratified random. Weighting can be equal, fixed, or derived from declared candidate counts. Omitted stock local search uses seeded random leaves, a stratified-random union for multiple families, and sequential union order for a single family.
The omitted selector profile is capability-matched. List slots receive precedence/permutation, nearby or plain change/swap, sublist, reverse, k-opt, and ruin families only where their declared operations allow them. Nearby scalar leaves precede plain scalar fallbacks. Assignment-owned scalar slots remain on their grouped path, and registered repair providers add compound conflict-repair leaves.
Candidate traces carry execution provenance
Candidate tracing is opt-in and bounded:
[candidate_trace]
max_entries = 4096
The trace header owns canonical config, execution policy, resolved phase plan, and optional externally attested input and qualification provenance. Each retained pull records source, phase/step coordinates, optional selector and construction target, logical operation identity, and ordered dispositions such as interrupted, evaluated, rejected, selected, and applied. Truncation, unencoded identity, and incomplete provenance stay explicit.
Large trace detail is not cloned into routine lifecycle events, status, or
snapshots. SolverManager::get_telemetry_detail(...) returns candidate detail
atomically with the aggregate status, scores, and snapshot revision from the
same publication instant. Benchmark and bridge callers can provide externally
validated provenance through
solve_with_qualified_candidate_trace_provenance(...); that changes only the
diagnostic header, not the runner.
Dynamic models declare their capabilities
The bridge surface now includes explicit contracts for dynamic scalar assignment metadata, list mutation capabilities, and immutable list metadata bundles. A dynamic list declares whether it supports set, whole-row replace, reverse, and sublist operations, plus any owner, construction-order, precedence, distance, route, or savings metadata it actually implements.
Missing capabilities are validation boundaries, not permission for the runtime to synthesize an operation from weaker mutations. Dynamic nearby scalar sources are lazy and receive a consumption limit; scalar legality and descriptor resolution are checked before the compiled cursor uses the slot. Host compound providers and candidate metrics are frozen in solve-owned registries instead of being discovered from mutable schema, global maps, or thread-local state during candidate execution.
Lifecycle control settles at every boundary
Pause and cancel requests are polled inside long-running candidate work and again around phase and terminal hooks. A pending command cannot be overwritten by ordinary default completion. Paused jobs preserve resumable runtime state, paused time is excluded from active phase elapsed telemetry, and long phases emit metadata-only pulses with phase-local counters.
Retained telemetry now includes the active PhaseTelemetry snapshot and richer
move-level improvement counters in addition to selector, construction,
conflict-repair, and applied-move data.
Release qualification has a controlled performance gate
Portable pre-release checks remain portable. Hot-path regression qualification now has a separate pinned-CPU Linux gate that alternates independently linked baseline/candidate trials and checks enumeration order, work, wall time, allocations, peak memory, and available hardware counters. An unavailable or noisy measurement is not silently treated as proof of zero regression.
Install And Version Boundaries
For a direct Cargo project:
[dependencies]
solverforge = { version = "0.18.0", features = ["serde", "console"] }
The companion workspace crates are published on the same line:
solverforge-core = "0.18.0"
solverforge-scoring = "0.18.0"
solverforge-solver = "0.18.0"
solverforge-bridge = "0.18.0"
solverforge-cvrp = "0.18.0"
solverforge-console = "0.18.0"
solverforge-config = "0.18.0"
solverforge-macros = "0.18.0"
For a generated app, inspect the installed CLI before changing its manifest:
solverforge --version
The current published CLI reports 2.2.2 and scaffolds
solverforge 0.15.2, solverforge-ui 0.6.5, and
solverforge-maps 2.1.4. Upgrade a generated app to 0.18.0 deliberately and
run its model, config, and lifecycle tests; do not infer the runtime target from
the CLI package version.
Upgrade Checklist
- Normal facade-based models should bump the Cargo dependency, rebuild, and run their solve and snapshot tests against the compiled runtime.
- Lower-level specialized list-construction callers must provide the stable
element_source_keyrequired by the 0.18.0 constructors. - Direct Rust literals for leaf selector configs must account for
selection_orderandselection_metric; union literals must account for weighting and weights; local-search literals must account forscore_tie_break. - Dynamic binding layers should declare scalar/list capabilities and propagate
RuntimeBuildResulterrors instead of installing a binding-specific phase builder. - Enable
[candidate_trace]only when the diagnostic prefix is needed, and fetch it throughget_telemetry_detail(...)rather than expecting it in ordinary events.
Patch History
| Version | Date | Notes |
|---|---|---|
0.18.0 |
2026-07-13 | Establishes the immutable compiled runtime graph, cursor-owned candidates, resolved selector policy, qualified candidate traces, explicit dynamic capabilities, and hardened lifecycle control. |
Documentation Changes
- SolverForge runtime docs now describe the published 0.18.0 line and the single compiled lifecycle.
- Architecture maps the immutable runtime graph and its intentional dynamic-dispatch seams.
- Configuration covers leaf order, candidate metrics, union weighting, score ties, and candidate tracing.
- Construction documents live placer cursors and stable list-source identity.
- Local Search and Composite Selectors describe cursor ownership and the resolved default policy.
- SolverManager documents phase telemetry, trace detail retrieval, and phase-boundary lifecycle settlement.
- The Hospital, Lessons, Deliveries, and FSR guides match the tagged 2.0.4/2.0.5 applications on SolverForge 0.18.0.
- Status & Roadmap keeps the core, CLI scaffold, Python, UI, maps, and worked-example release lines separate.