Versioning and stability¶
What a version number means¶
Every crate in the workspace carries the same version, and the internal
dependencies declare it (atune_core = { path = …, version = "0.1.0" }). A release is therefore all of them at one number, not four
independently versioned crates. The Python wheel is built from the same tree and
carries the same version.
The numbers follow cargo's reading of semantic versioning, which before 1.0 is stricter than people expect:
| Change | Before 1.0 | After 1.0 |
|---|---|---|
| Breaking API change | a minor bump — 0.1 → 0.2 |
a major bump |
| New capability, nothing removed | a patch bump — 0.1.0 → 0.1.1 |
a minor bump |
| Bug fix | a patch bump | a patch bump |
So while atune is 0.x, treat every minor bump as breaking. A dependency
written as atune = "0.1" will not silently pick up 0.2.
What is stable today¶
The guarantees are narrower than a general API stability promise:
- The four plugin seams are frozen in shape, not in signature: a
atune::Storage,atune::Sampler,atune::Scheduleroratune::Objectiveimplemented against today's traits is the shape they will keep, and the storage conformance suite is what an out-of-tree backend is held to. See Extending. - The on-disk journal has a version field and a compatibility rule. This build writes format version 6; a reader refuses a journal whose header names a higher version rather than guessing at it, and still opens a lower one (a writable open migrates it once). The format is documented field by field on the journal reference and pinned by a committed golden file.
- Optuna's journal format is a stable import and export target, because Optuna itself guarantees it. See Optuna interop.
- Determinism is asserted, within a version. See the section below, which is the one stability question that bites people who record results.
The surfaces, and what each one commits to¶
| Surface | What it is | Pre-1.0 expectation |
|---|---|---|
atune (Rust facade) |
The crate to depend on | The main API surface; outside the pinned subset, anything may change with a minor bump |
atune_core |
The model layer and the four seams | Depend on it directly only when implementing a seam |
atune_derive |
#[derive(Space)] |
Generated code names the facade, so the two travel together |
atune_cli (binary atune) |
The command-line tool | Flags and subcommands may change; the CLI reference is generated from the code, so it cannot describe a flag that no longer exists |
atune on PyPI |
The Python package | Mirrors the facade's shape rather than its exact API |
| Cargo features | Part of the public API | Adding one is additive; removing or renaming one is breaking. The feature reference is generated from the manifests |
| The journal file | The on-disk study | Versioned, with the compatibility rule above |
The facade subset Oniro builds on¶
Oniro, the sibling reinforcement-learning framework, tunes through a small part of the Rust facade. That part is declared as contract XC-07, version 1: the paths below keep resolving under the named features, and the calls a downstream crate makes with them keep compiling, for as long as version 1 is supported.
The checked-in list crates/atune/tests/fixtures/xc07-facade-v1.txt names
every pinned path, one per line with the feature it needs. It covers the study
builder and handle, the objective and reporting context, the multi-seed
protocol, search spaces and the DSL parsers, the TPE, Random, and DEHB samplers
with the ASHA and PBT schedulers, storage (the journal under system, SQLite
under sqlite, and the opaque StateBlob), the subprocess executor and its
constants (system), the ask entry and trial attributes, and the PB2 scheduler
with the in-run population (pb2).
Within version 1 the contract only grows: a new pinned item, other than a variant of one of the exhaustive enums below, or a signature that accepts more than it did, is an ordinary change. Removing, renaming or moving a pinned item, or changing it so that a call that compiled before no longer does, is a breaking change to the contract. Version 1 is preserved as a historical fixture; it does not require the old API to keep resolving after a version 2 break.
XC-07 version 2 records D137's breaking API migration. D137 also removes the
first-party atune::prelude and atune_core::prelude modules; use explicit
imports from the facade root or the module that owns each item. The crate-root
and module re-exports remain available. Fallible storage and
reader constructors now use try_new names: JournalStorage::{open,
with_options, open_read_only, open_read_only_with,
open_read_only_with_options} map to try_new, try_new_with_options,
try_new_read_only, and try_new_read_only_with_options; SqliteStorage
replaces open, with_options, open_read_only, open_read_only_with,
in_memory, and in_memory_with with try_new, try_new_with_options,
try_new_read_only, try_new_read_only_with_options, try_new_in_memory,
and try_new_in_memory_with_options. RemoteStorage::connect becomes
try_new; connect_observer and its connect_read_only alias become the
single try_new_with_observer constructor; serve_embedded becomes
try_new_embedded. The role-selection helper remains private.
StudyReader::load is replaced by StudyReader::try_new.
RestartMode::shrink_perturb and MedianPruner::percentile are replaced by
RestartMode::try_new_with_shrink_perturb and
MedianPruner::try_new_with_percentile. GP fitting factories likewise changed:
Gp::fit, fit_mle, and fit_map are now try_new, try_new_with_mle, and
try_new_with_map; TvGp::fit and fit_noisy are now try_new and
try_new_with_noise. The affected search-space constructors also use fallible
try_new names. There are no compatibility shims for the old names.
Infallible static factories use new or descriptive new_with_* names:
Budget::unbounded and Budget::trials became Budget::new and
Budget::new_with_trials; RetryPolicy::none and RetryPolicy::times became
RetryPolicy::new and RetryPolicy::new_with_max_retries; Mabc::ucb_default
and Mabc::ucb became Mabc::new and Mabc::new_with_ucb; and
Qmc::halton became Qmc::new_with_halton. SpaceSchema::empty became
SpaceSchema::new. Open::{log, linear, around, around_int} became
Open::{new_with_log, new_with_linear, new_with_around, new_with_around_int};
TrialTemplate::fixed and TrialTemplate::fork became
TrialTemplate::new_with_fixed_params and
TrialTemplate::new_with_parent_and_mutation. MemStorage's with_clock
factory became new_with_clock. Perturb::factors became
Perturb::new_with_factors.
The remaining public named constructor changes are Point::unconstrained to
Point::new_with_unconstrained; AshaPruner::from_ladder,
MedianPruner::variance_aware, and ReplicateFan::from_tune to
new_with_ladder, new_with_variance_aware, and new_with_tune;
OutboxEffectIntent::{resume, replacement} and OutboxPageRequest::defaults
to new_with_resume, new_with_replacement, and new_with_defaults; and
TrialMeta::derive to TrialMeta::new_with_study_seed.
The redundant from_parts aliases on FinalizationToken,
FinalizationObservation, TrialReservation, TrialOwnerToken, and
LeaseObservation were removed; their existing new constructors remain.
OutboxKey now implements From<TrialOwnerToken> by forwarding to
OutboxKey::from_token, which remains available.
The DSL parser results are named records: parse_named returns
NamedDistribution { name, distribution }, parse_growing returns
GrowingDistribution { distribution, open }, and
parse_schema_growing plus parse_schema_lines_growing return
GrowingSchema { schema, policy }. These records are #[non_exhaustive], so
downstream patterns must allow future fields. Evolving Decision, Command,
SamplerChoice, diagnosis Rule, and IncumbentPoint are also
#[non_exhaustive]; error types carry type-level #[must_use]. Poisoned
mutex contents are never inspected or repaired: calls that need the protected
state return typed errors, while cleanup skips inaccessible state. These are
the owner's one-time D137 authorization for the breaking changes: Oniro must
migrate its source to the v2 paths before changing its pin to the v2 producer
revision. The next compatible minor release containing this break must be
0.2.0; this note does not change the current workspace version or authorize a
release.
The same v2 migration gives ParamWrite named name, distribution, and
value fields, while its serde representation stays the historical
three-element tuple. Storage::write_params remains tuple-based. The float and
integer options constructors accept Scale instead of log: bool; the
serialized Distribution variants still carry log: bool. FencedMutation
is #[non_exhaustive], so downstream matches need a wildcard arm. Any future
mutation that an adapter cannot represent must return Error::Unsupported
before applying or sending it. External conversions that can encounter a
future variant are fallible with TryFrom. Current mutation wire variants
remain unchanged.
Protocol records and vocabularies declared in storage::* intentionally support
literal construction and exhaustive matching by backend authors. A future field or
variant change requires a coordinated migration of every in-tree backend,
wire conversion, conformance suite and affected consumer contract, with an
explicit downstream migration handoff before a consumer changes its producer
pin. This policy covers the storage API, snapshot, lifecycle, import, outbox
and finalization records; it does not make their fields immutable or permit
unversioned serialized changes. Evolving algorithm choices and model/report
outputs use #[non_exhaustive] instead. In particular, shared trial model records
(FrozenTrial, TrialRecord, TrialTemplate, TrialDelta) use their public
constructors even when carried by a storage method. Diagnosis records provide
constructors for custom renderers and synthetic reports. Fixed scalar IDs, Boolean/geometry
vocabularies and statistical coefficient records retain their closed shapes.
The contract is narrower than it may look:
- It is still a pre-1.0 promise between two repositories with one owner, not the semantic-versioning guarantee 1.0 will make. Everything outside the list may change with a minor bump.
- Only the listed module paths are pinned. The crate-root aliases
(
atune::Study,atune::Tpe) are conveniences and may move; the facade and core preludes were removed as part of the XC-07 v2 migration. - The pinned enums without
#[non_exhaustive](Direction,TrialState,Distribution,ParamValue,Aggregate,Pairing,RestartMode,StudyLoadOutcomeandpb2::Explore) gain no variant and no#[non_exhaustive]within XC-07 v1, since Oniro may match them exhaustively. A variant arrives only with XC-07 v2; see D121 in the plan. - The numbers a study produces are not pinned across versions: determinism stays a within-version promise, and the journal keeps its own format version.
- The environment-variable names the executor sets (
ATUNE_PARAM_<name>,ATUNE_TRIAL_NUMBER,ATUNE_SEED, and the rest) and its default prune exit code, 42, are part of the contract. Its stdout protocol is the one described in tune any program.
The contract is checked, not just written down:
crates/atune/tests/facade_contract.rs resolves every pinned path at compile
time, requires the rendered list to equal the checked-in file byte for byte,
and drives each area end to end through those paths only, as a dependent crate
would.
Determinism is a within-version promise¶
Given the same version, seed, closed search space, and stateless sampler, a study reproduces exactly across thread counts and across the Rust and Python surfaces. History-dependent samplers, schedulers, and open search spaces have the narrower replayability contract described in Determinism; a fixed-seed single-worker run remains fully reproducible.
Across versions it is deliberately not a promise. Improving a sampler changes which configuration it suggests, so the byte-for-byte outputs change with it; the repository's parity and journal goldens are re-blessed on purpose when that happens, and the diff is the record of the change. If a result has to be reproducible later, pin the exact version that produced it — a caret requirement is not enough.
An open search space stores its growth policy in the study. A build that predates open search spaces does not read that policy: it samples the declared seed range while earlier trials recorded wider distributions. The compatibility check accepts that bound drift, so such a study degrades — growth stops — rather than corrupting.
Rust version¶
The workspace sets rust-version = 1.92 and edition 2024, and every published
crate inherits it. An older toolchain refuses the build outright rather than
failing halfway through it. An MSRV increase is recorded in the
changelog as a breaking change, because for a consumer pinned to
an older toolchain it is one. The two unpublished GUI crates, atune_gui and
atune_showcase, declare rust-version = 1.95 because egui and eframe 0.37
need it (D125); they are not part of this promise.
Python wheel support¶
Wheels use abi3-py310 for GIL-enabled CPython 3.10 and newer. Free-threaded
CPython builds, including cp314t, are not shipped; raising the supported
floor is a breaking change.
The publish order, and why it is not one command¶
The four published crates must go to crates.io bottom-up:
atune_core → atune_derive → atune → atune_cli
This is not a preference. The internal dependencies are path + version
entries, and a normal publish package resolves the version half against the
registry. Before the first release, an unpatched cargo package -p atune fails
with:
no matching package named atune_core found … location searched: crates.io index
The push gate runs a fast package include/exclude check. The dispatch-only release-staging rehearsal packages all four crates bottom-up with temporary same-workspace patches and verifies each archive; authorization and artifact handling are in Staging, authorization, and rollback.
atune_bench, atune_gui, atune_dev, atune_showcase and
atune_plugin_example are publish = false and are never released. They are
internal tooling, a demo and a compile-checked example; their Rust API reference
is published on this site rather than on docs.rs, because they will never
appear there.
Staging, authorization, and rollback¶
The repository's release automation stops at a manual, read-only staging rehearsal. A staging run must build the supported crate, wheel, sdist, and documentation artifacts from one commit; consume each build in an isolated environment; run the current dependency-policy gate; and retain a sorted manifest, SHA-256 sums, a standard SBOM, and available hosted provenance. A successful workflow run is review evidence, not permission to publish.
Publication requires a maintainer to review that evidence, select and protect the release identity or signing mechanism, and authorize the exact version and destinations. Registry credentials, signing keys, GitHub Pages mutation, crate yanks, and PyPI uploads are intentionally absent from the staging workflow. Nothing should be published from a dirty worktree or from artifacts rebuilt after the reviewed staging run.
If a problem is found before publication completes, stop and leave every remaining destination untouched. If an already published artifact is unsafe, the maintainer follows the registry's supported withdrawal mechanism (for example, a crates.io yank), publishes a corrected version rather than replacing immutable bytes, and moves documentation aliases only after the corrected artifacts pass staging. PyPI and crates.io receipts, provenance, signatures, and the incident decision are retained with the release evidence. The local rollback rehearsal exercises only disposable indexes and aliases; it never writes a public registry or production documentation target.
Documentation versions¶
The site is versioned from the start: /latest/ moves with each stable release, each
release also keeps an immutable /0.1.0/-style snapshot, and /dev/ is built
from the repository's default branch when the documentation workflow publishes
it. Retrofitting that later would rewrite every published URL, so the
mechanism is in place before there is anything to publish with it.
Where to go next¶
| If you want to | Go to |
|---|---|
| What changed, release by release | Changelog |
| To contribute, and the gates a change must pass | Contributing |
| What determinism actually guarantees | Determinism |
| Which feature carries which algorithm | Feature reference |
| To report a vulnerability | Security policy |