Publish a plugin crate¶
atune has no contrib directory and no plugin monorepo, and it is not going to grow one. A sampler, a scheduler or a storage backend is a crate, written against the same public traits the built-ins use, published under its own name, versioned on its own schedule. This page is about that packaging step: what your crate should depend on, what its manifest should declare, and what a user does to reach it.
Nothing is published yet
No version of atune has been released, so the version numbers below are the ones a manifest will carry; until the first release a plugin crate depends on a path or a git revision. Install says which routes work today.
The repository does test the package boundary before publication. Its
dispatch-only staging workflow copies the checked-in atune_plugin_example
source unchanged into an isolated workspace, resolves only an extracted
atune_core crate archive with default features off, and runs the plugin's build
and tests. The retained evidence identifies exactly what passed: commit plus
source-tree hash, the complete consumer lockfile, archive hashes, and the resolved
feature set. This proves the staged bytes; it is not a crates.io receipt.
Depend on the traits, not on the world¶
There are two crates you could point at, and the choice is not a matter of taste:
| Depend on | When | What it costs your users |
|---|---|---|
atune_core |
You need the traits and the model, which is the normal case for a sampler, a scheduler or a storage backend | Six direct dependencies, and a crate that compiles to wasm32 |
atune (the facade) |
You need something the facade owns — a feature-gated backend, or one of the heavier algorithms to build on | The facade's feature graph, resolved in your users' builds as well as yours |
The reason the first row is the default is the same reason cmaes, gp, carbs
and sqlite are facade features rather than core modules: atune_core is
wasm32-clean and a gate proves it in every feature combination. A plugin that
depends only on the core inherits that property; one that depends on the facade
hands the decision to whatever the facade's features pull in.
Two manifest details follow from it. Set default-features = false on an
atune_core dependency and opt back into what you need, so you do not force the
operating-system surface on a consumer who is building for the browser. And put
anything OS-shaped or dependency-heavy in your crate behind a cargo feature of your
own, off by default — a clock, a filesystem, a thread pool or a linear-algebra
library is exactly the kind of thing a user should be able to decline.
If your crate also wants #[derive(Space)] for a struct-shaped search space, that
lives in atune_derive.
What the manifest should say¶
Beyond the usual name, description and license:
rust-version. atune's workspace is 1.92 and the crates are edition 2024. A plugin cannot be older than what it compiles against, and saying so turns a confusing mid-build failure into a clear refusal.- Keywords and categories. The dependency graph is the only registry there is
(see below), so discovery is search. atune itself uses
hyperparameter,optimization,tuning,hpo,search. - docs.rs metadata, if you have feature-gated items. The published crates here
set
all-features = trueand pass--cfg docsrs, so every gated item appears on docs.rs carrying a badge that names the feature it needs. A plugin with an optional sampler behind a feature wants the same treatment; without it, docs.rs simply does not show the thing you gated. - A version bound you can defend. The traits are the contract, and the shape of that contract is that new capability arrives as a defaulted method — the sampler trait carries four of them and the scheduler trait six, and most built-ins inherit those defaults untouched. Adding a defaulted method does not break an implementer; adding a required one does. Versioning and stability is where the promise itself is stated.
What your documentation must say¶
Three claims a user cannot check from the outside, and every built-in states its own:
- Your determinism grade. Stateless, history-dependent, or stateful — the three rows in Write a sampler, and the difference between them is whether a user may parallelise your component and still expect the same study. Say it in the type's own documentation, not only in a README.
- Which cargo features change behaviour, and which of them are on by default.
- For a storage backend, that it passes the conformance suite — the same thirty-four checks the in-memory, journal, SQLite and TCP backends pass. That is the single most useful sentence such a crate can contain, and Write a storage backend is how you earn it.
How a user reaches your crate¶
cargo add your crate, then one line at the study builder — the same line a
built-in occupies, because the builder takes an Arc<dyn Sampler>,
Arc<dyn Scheduler> or Arc<dyn Storage> and does not care where the
implementation came from. There is no registry to enrol in, no plugin directory to
drop a file into, and no dynamic loading anywhere in the system.
That is a deliberate trade, and it is worth being explicit about both halves. Compiled registration means a plugin is type-checked, versioned and reproducible like any other dependency; the price is that plugins are a Rust-side extension mechanism, and today that price is visible in two places:
- Python takes handles, not implementations.
atune.samplersfactories return opaque objects wrapping anArc<dyn Sampler>built in Rust, so a Python user cannot select your sampler. - The CLI takes storage spec strings.
:memory:,*.atj,*.db/*.sqliteandatune://host:portare matched by a closed function, so a third-party backend is not nameable from--studyeither.
Know that before promising a Python or CLI audience. Everything a Rust user does
with Tpe, they can do with yours.
Listing your crate¶
No index of third-party crates is currently available. A published crate against the frozen traits already works, and a link in your README to Write a sampler tells your users everything they need about the seam you implemented.
Where to go next¶
| If you want to | Go to |
|---|---|
| Implement the sampler seam | Write a sampler |
| Implement the scheduler seam | Write a scheduler |
| Implement and validate a storage backend | Write a storage backend |
| Control how a trial is evaluated | Write an objective or an executor |
| Know which feature carries what | Feature reference |
| Know what atune promises about breakage | Versioning and stability |