Stability and versioning¶
Status: Phase 7. This page states what you may rely on, what may change, and how a change is announced. The decision behind it is ADR 0013.
Xyberos is at 2.0.0, the first release of this line, and the leading digit records lineage rather
than maturity (ADR 0017). The honest summary is: the
stable tier is declared and frozen, and from 2.0.0 a change to a documented stable name requires a
major release.
The three tiers¶
| Tier | What it means | How it is marked | Can it change in 2.x? |
|---|---|---|---|
| Stable | The contract this project intends to keep. Changing it requires an ADR, a changelog entry, and a deprecation where one is possible | Nothing extra — a name in the documented import path and in __all__ is stable |
Only in a major release |
| Preview | Shipped, documented, and tested, but the shape may still move as real use reveals the right one | The guide says "preview API" in its status line | Yes, in a minor release, with upgrade notes |
| Experimental | Explicitly narrow, with stated non-guarantees that are part of the contract | ExperimentalWarning at the entry point, plus a limits section in the guide |
Yes, at any time; the non-guarantees are the contract |
The tiers are about promises, not about quality. Every tier is tested; the difference is what happens when a test is not enough and the shape turns out to be wrong.
What is in each tier¶
| Surface | Import path | Tier |
|---|---|---|
| Application runtime: composition, providers, scopes, lifecycle, route records, context and logging | xyberos |
stable |
| Configuration: TOML, environment mapping, typed sections | xyberos |
stable |
| Workflow definitions and the in-process executor | xyberos / xyberos.execution |
stable |
| Plugin protocol and registry | xyberos.plugins |
stable |
| Error taxonomy | xyberos.errors |
stable |
| Deprecation mechanics | xyberos.deprecation |
stable |
| HTTP adapter (Starlette) | xyberos.http |
preview |
| SQLite provider and migrations | xyberos.database |
preview |
| AI facade, contracts, tools, schema subset, fake provider | xyberos.ai |
preview |
| Ollama adapter | xyberos.ai.ollama |
preview |
Test doubles (ManualClock, FakeExternalService, FakeModelProvider, temporary_database, create_test_client) |
the *.testing modules |
preview |
Command-line interface: check, new, run, migrate |
the xyberos command |
preview |
| Durable executor | xyberos.durable |
experimental |
A tier applies to a whole surface, not to a version of it: xyberos.ai is preview even where it is
built only on stable pieces, because its contracts are newer and less exercised.
The command-line surface is the command and its documented options, not the modules that implement
them: nothing under xyberos.cli is promised as an import, so a change there is not a breaking
change even though a changed option is.
Minimal support range for the stable tier¶
Concretely, the stable tier covers: an App composed from typed providers, configured from TOML and
the environment, started and stopped through lifespan(), with providers resolved by declared
annotation and released in reverse dependency order; a Workflow definition validated before it
runs and executed with bounded retries, deadlines, cancellation, and structured events; a plugin
implementing the documented class attributes and register; and the error types those raise.
Import paths, and what counts as public¶
- A name is public if a guide names it as an import, or it is in the module's
__all__. Everything else is internal and may move in any release, including a patch. __all__is frozen by a test (tests/test_public_surface.py), so a name leaving the surface is a deliberate edit to that file rather than a side effect of a refactor. That test is also what caught five modules whose surface was accidentally "whatever their imports left behind".- Import from the module that defines a name. A name reachable through a package only because
something imported it there is not public, which is why
xyberos.httpno longer advertises its handler-compilation internals. - Submodules bound on a package by importing them (
xyberos.app,xyberos.errors) are an implementation detail of the import system, not a promise.
Versioning rules¶
The package uses PEP 440 spelling for SemVer concepts. The version base for this line is 2.x and its
first release is 2.0.0; the leading digit records lineage, not maturity, because the xyberos
name is held on the package index by the already-published 1.x line, which installs the same import
root and cannot be co-installed (ADR 0017). Maturity is
stated by the Development Status classifier and by the tier table above.
2.0.0is the release that first promises the stable tier. Until it exists, that tier is declared intent plus tested pinning, and a minor release may break a stable name with upgrade notes. From2.0.0, a breaking change to a stable name requires a major release.- A minor release may break a preview name with upgrade notes. A patch release fixes defects and breaks no documented name.
1.0.0is not one of this line's versions. It belongs to the previously published line, which stays installable and is not compatible with this one.- Versioned independently of the package: the plugin API (
PLUGIN_API_VERSION), the configuration schema (schema_version), Workflow definition versions (theversionyou declare), and the durable journal schema (JOURNAL_SCHEMA_VERSION). Each is a number you will see in an error message rather than in the package version. - Python support is
3.10through3.13, tested in CI. Adding a newer Python is a minor release; dropping the oldest is a breaking change, done as a minor release with upgrade notes until2.0.0exists and as a major release from2.0.0. - Third-party ranges are floored, and capped below a major where the dependency is not yet at one.
starlette>=0.40has no ceiling because Starlette's surface is stable;sqlalchemy>=2.0,<2.1andhttpx>=0.27,<1do, because the next major is where a breaking change lands. A cap is not a promise about the release inside it: the floor is what moves when a defect is fixed, and a dependency's new major forces a decision rather than a surprise.
The Ollama release floor¶
One supported external range is a service version rather than a package: Ollama 0.5.0 or newer, enforced by the adapter and explained in the AI guide. It is listed here because it is a compatibility promise in the same sense: the floor is set by the newest feature the adapter uses, and the adapter refuses to run below it rather than degrading.
Deprecating something¶
Deprecation is the mechanism that makes a promise survivable, so it is a documented process rather than an improvised warning:
- Announce it in the changelog under "Deprecated", naming the replacement.
- Warn at runtime from the deprecated path:
from xyberos.deprecation import warn_deprecated, deprecated
warn_deprecated(
"App.provide(instance=...)",
since="0.2",
removed_in="1.0",
alternative="App.provide(factory=...)",
)
@deprecated("old_helper", since="0.2", alternative="new_helper")
def old_helper() -> None: ...
- Keep it working for at least one minor release after the announcement. A deprecation removed in the same release that announced it is not a deprecation.
- Remove it in a minor release, and say so in the changelog under "Removed".
warn_deprecated composes its message from the subject, the release it started warning in, the
replacement, and the release that will remove it, so all four facts are stated once and appear
everywhere. The warning class derives from DeprecationWarning, which means it is silent by default,
visible under -W default, and an error under -W error — the behaviour a user expects from a
deprecation, and quiet enough that it does not become noise in a long-lived application.
Deprecation and experimental warnings are deliberately different categories: a deprecation says
"this will stop working, and here is the replacement"; ExperimentalWarning says "this may change".
Users filter them differently, so they are different classes.
What is not promised¶
- No stability for internals, including anything not in
__all__or not named by a guide. - No stable serialization format except the ones with an explicit version: definition metadata
(JSON, with a declared
version) and the journal (a versioned schema that refuses a newer file). Do not parserender()output; it is for humans and tests. - No compatibility promise for the experimental tier, whose non-guarantees are part of its contract: see the durable guide.
- No deprecation for a security fix. A vulnerability may be fixed in a way that changes behaviour, and that will be said plainly in the release notes.