Skip to content

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 = true and 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.samplers factories return opaque objects wrapping an Arc<dyn Sampler> built in Rust, so a Python user cannot select your sampler.
  • The CLI takes storage spec strings. :memory:, *.atj, *.db/*.sqlite and atune://host:port are matched by a closed function, so a third-party backend is not nameable from --study either.

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