Nautilus Trader - Releases, Versioning & Upgrade Discipline
Nautilus ships from a three-branch model -
develop(dev wheels to Cloudflare R2),nightly(alpha wheels + CLI binaries),master(PyPI + Docker + docs rebuild) - with two independent version numbers: the Python package (pyproject.toml, currently1.226.0as of 2026-04-29) and the workspace Rust crates (Cargo.toml, currently0.55.0-class). The Python version drives the release tag (v1.226.0) and the publish pipeline. Empirically, minor versions ship roughly monthly with breaking changes in nearly every release; the leading1.digit is structurally pinned - the1.22x.0cadence is “continuous beta,” not semver-stable. Breaking changes are surfaced in a structuredRELEASES.mdwith seven canonical sections (Enhancements, Breaking Changes, Security, Fixes, Internal Improvements, Documentation Updates, Deprecations); each “Removed” / “Renamed” / “Changed” entry includes brief migration guidance. Deprecations name the replacement and announce removal “in a future version.” Security items (memory safety, undefined behavior, data integrity, input validation, build hardening) are split out from plain-logic Fixes. The practical consequence for any 4-6 month build on top of Nautilus is that you will eat at least 4 minor bumps with breaking changes during the build window - pin hard at milestone boundaries; never auto-track latest in production.
Core claim
Nautilus’s release process is transparent (structured changelog, named
breaking changes, deprecation guidance) but fast (roughly monthly minors,
breaking changes nearly every release, no LTS lane). The transparency is
load-bearing: you can read each release’s “Breaking Changes” block and decide
to skip or migrate. The speed means you must pin and treat every upgrade as a
deliberate engineering task, not a pip install -U afterthought. There is
no “stable” channel that sits behind the bleeding edge - master is the
release branch.
Branch model
Three branches, each with its own publish pipeline.
| Branch | Wheels published to | Trigger | Use case |
|---|---|---|---|
develop | Cloudflare R2 (dev wheels) | Every push | Active development; bleeding edge |
nightly | Cloudflare R2 (alpha wheels + CLI binaries) | Pre-release testing | Pre-release validation, RC channel |
master | PyPI + Docker Hub + docs site | Merge from nightly | Stable releases; what pip install nautilus_trader pulls |
Pushing to master automatically: tags the version from pyproject.toml,
creates a GitHub release, publishes wheels and sdist to PyPI, builds Docker
images, triggers docs rebuild. Cortana production must always pull from PyPI
(master); never from the Cloudflare R2 dev/alpha wheels.
Versioning policy - two numbers, one tag
“These are bumped independently. The Python version drives the release tag (
v1.223.0).”
- Python package (
pyproject.toml):1.226.0as of 2026-04-29. The leading1.is structurally pinned - this is what they actually ship - and the middle digit is the only one that moves in normal releases. Despite the1.x.yshape, this is not semver. Breaking changes ship in minor bumps as a matter of course. - Rust crates (
Cargo.tomlworkspace):0.55.0-class. End-user-facing but only relevant to contributors compiling from source. Pinning the Python wheel pins both, since the wheel statically links the matched Rust artifacts.
The two numbers are decoupled to let core Rust crate semver mature separately from the Python distribution’s release cadence. For Cortana, the Python version is the only one that matters for pinning.
Pinning syntax for Cortana (lock to a specific Python release):
# pyproject.toml under MK3
[project]
dependencies = [
"nautilus_trader[ib]==1.226.0", # exact pin, no range
]Do not use ~=1.226.0 (PEP 440 compatible-release) or >=1.226,<2.0.
Both let minor bumps slip in, and minor bumps ship breaking changes.
Release cadence - empirical
The releases doc page does not specify a cadence. Empirical pull from the GitHub releases page (sampled 2026-05-07):
| Version | Release date | Gap from prior |
|---|---|---|
| 1.226.0 | 2026-04-29 | 23 days |
| 1.225.0 | 2026-04-06 | 34 days |
| 1.224.0 | 2026-03-03 | 10 days |
| 1.223.0 | 2026-02-21 | 51 days |
| 1.222.0 | 2026-01-01 | 67 days |
| 1.221.0 | 2025-10-26 | - |
Headline: ~6 minor releases over ~6 months → roughly one minor per month during steady-state; gaps of 1-2 months between releases when the team is in a deeper internal-improvement window (the Oct 2025 → Jan 2026 gap). Every release in this window contains a “Breaking Changes” section.
There is no observed major-version bump in this window - the leading 1.
has been pinned for the whole sample. There is also no observed LTS branch
or back-port lane: when a Fix lands, it lands on develop and ships in the
next minor. If you’re on N-3 and a security fix lands on N, you have to
upgrade to get it.
Changelog conventions - the seven sections
RELEASES.md (and the corresponding GitHub Release body) is structured into
seven sections, in this exact order:
- Enhancements - new features, improvements that don’t break callers.
- Breaking Changes -
Removed,Renamed,Changedentries, each with brief migration guidance. - Security - memory safety issues, undefined behavior, data integrity fixes, input validation, build hardening. Plain logic panics belong in Fixes “unless they threaten system stability or data corruption.”
- Fixes - regressions, bug fixes, edge cases.
- Internal Improvements - refactors, performance work that doesn’t change the API surface.
- Documentation Updates.
- Deprecations -
Deprecated <feature>; <action>. Will be removed in future version.
Sections with no items are omitted.
Style is enforced: sentence case (capitalize first word only), backticks for
code elements, focus on what changed (not implementation detail), include
issue/PR numbers, credit contributors with thanks @username.
Breaking-change signals
Every breaking change is marked with one of three explicit verbs:
- Removed - the feature is gone. Migration path required.
- Renamed - the symbol moved. Migration is mechanical (find/replace) but you have to do it.
- Changed - semantics shifted. Read carefully; this is the most dangerous category because code may keep compiling but produce different behavior.
Each entry includes “explanation of migration path briefly.” There is no machine-readable schema for breaking changes - you read the text.
Deprecation policy
Deprecations are announced with this shape:
Deprecated <feature>; <action>. Will be removed in future version.
The deprecation entry includes:
- The replacement API.
- A brief explanation of why.
- A “will be removed in future version” notice - but no specific version number. The lead time is at the maintainers’ discretion.
Practical implication: if you see a feature you depend on appear under “Deprecations”, you are on the clock - but the clock has no documented length. Plan to migrate within the next 1-2 minor bumps to be safe; do not let deprecated calls accumulate.
Security release process
Security items live in their own changelog section, separate from Fixes. The boundary, verbatim:
“Plain logic panics belong in Fixes unless they threaten system stability or data corruption.”
So if a panic could corrupt cache state, screw an account ledger, or take down the kernel, it goes in Security. If it just bombs the strategy callback, it goes in Fixes. There is no separate security advisory mailing list documented; security fixes ship in the normal minor releases on the regular cadence. There is no security back-port lane - if you need the fix, you take the whole minor.
For Cortana, this means: monitor the RELEASES.md Security section on
every release, and don’t sit on a known security fix waiting for a
“convenient” upgrade window.
Upgrade-path documentation
Per release, the upgrade path is the union of:
- The Breaking Changes section’s per-entry migration notes.
- The Deprecations section’s named replacements.
- The Documentation Updates section’s pointer to any reworked guides.
There is no separate MIGRATING.md or per-version migration guide. You
walk the changelog from your pinned version forward, applying changes
release by release.
For a 4-week-old pin upgrading to current, that’s roughly 1 release worth of migration. For a 4-month-old pin, that’s 4 releases - significantly more painful, and the “Changed” entries (silent semantic shifts) compound.
Cortana MK3 implications
This is where this page earns its keep. MK3 is a 4-6 month build (per
projects/cortana-mk3-roadmap.md - six milestones, M1 → M6 over Weeks 1-20+).
Across that window, Nautilus will ship roughly 4-6 minor releases, each
with breaking changes. We need an explicit pin policy.
Recommended pin policy
| Phase | Pin | Upgrade cadence | Justification |
|---|---|---|---|
| Spike (2026-05-09) | nautilus_trader[ib]==<latest stable at spike date> | None (1-day spike) | Latest features, latest fixes; no upgrade pain in a 1-day window. |
| M1 Foundation (Weeks 1-3) | Exact pin to spike version | None during M1 | First architecture pass needs a stable target. Don’t chase a moving API while we’re still learning Nautilus’s primitives. |
| M2 Strategy parity (Weeks 4-7) | Same exact pin as M1 | One controlled upgrade allowed at start of M2 if a needed feature landed | Strategy port is the bulk of work - minimize churn. Re-pin once at M2 start if there’s a compelling reason. |
| M3 Single-tenant prod (Weeks 8-9) | Re-pin to current stable | Once at start of M3 | Production cutover is the right moment to absorb 2-3 minor versions of debt and validate against MK2 in parallel. |
| M4 Multi-tenant scaffolding (Weeks 10-13) | Same M3 pin | None | Multi-tenant work is enough complexity on its own. |
| M5 Onboarding flow (Weeks 14-19) | Re-pin once at M5 start | Once at start of M5 | Customer-facing surface - pull in any security fixes before exposing to even one stranger. |
| M6 First external customer (Week 20+) | Stay on M5 pin until first quiet weekend | Conservative - only upgrade when the new release contains a fix or feature MK3 actually needs | Beta tester is live. Stability over freshness. |
Default cadence: re-pin at each milestone gate, not within a milestone. That gives ~4 controlled upgrades across the 4-6 month build, each at a moment when we’re already validating end-to-end and can tolerate breakage.
How to monitor for breaking changes
- GitHub releases RSS feed: subscribe to
https://github.com/nautechsystems/nautilus_trader/releases.atom. Wire into the brain’s update pipeline OR a personal feed reader. New release triggers a 5-minute “read the Breaking Changes section” task. - GitHub watch (custom): in the repo, Watch → Custom → Releases. Email on every release; treat each one as a triage event.
- Pre-upgrade ritual: before any pin bump, read every intervening
release’s
RELEASES.mdend-to-end. Note every “Removed”, “Renamed”, “Changed” that touches code Cortana actually uses. Estimate migration time before committing. - Post-upgrade ritual: after pin bump, run the full backtest replay
suite against
decisions.dbgolden output. Numerical drift on golden trades is the canary for “Changed” entries we missed.
The “Nautilus 2.0 ships during M3” scenario
Empirically, the leading 1. has been pinned across the entire 6-release
sample (Oct 2025 → Apr 2026). There is no public roadmap for a 2.0
visible in the releases doc. But the project explicitly carries a “beta”
designation in its release notes, and the changelog conventions allow
breaking changes in minor bumps without requiring a major bump - meaning
the team gets most of the benefit of a 2.0 without ever cutting one. So a
2.0 is unlikely on the M1-M5 timeline.
If a 2.0 does ship mid-build:
- Don’t upgrade during the milestone in progress. Finish the milestone on the pinned 1.x. The 2.0 changelog is going to be enormous.
- Audit the 2.0 release notes. If it’s a packaging/repo split with no code-level breakage, upgrade at the next milestone gate. If it’s a fundamental API rework (Strategy/Actor signatures changed, MessageBus topics restructured), treat it as a separate migration project - don’t fold it into a regular milestone gate.
- Hold the 1.x pin indefinitely if 2.0 deletes features Cortana depends on. This is the worst case; mitigation is to read every Deprecations section as it lands and migrate eagerly so that 2.0 doesn’t catch us using something already deprecated.
Risk accounting
The risk that Nautilus’s release cadence breaks Cortana mid-build is real but bounded. Empirical cadence + structured changelog + named breaking changes + the ability to pin precisely on PyPI = we control the exposure. The irreducible risk is silent “Changed” entries we miss in the release notes; the mitigation is the post-upgrade golden-replay test.
This risk is materially smaller than the risk we already carry on MK2 - which has zero release process, zero versioning, and our own hand-rolled runtime is the moving target. Adopting Nautilus’s cadence is adding discipline, not adopting chaos.
Pre-commit and contribution discipline
Several pre-commit hooks enforce release-relevant conventions:
check_anyhow_usage.sh- anyhow usage rules.check_logging_macro_usage.sh- fully-qualifiedlog::info!etc.check_error_conventions.sh- error context strings lowercase.check_tokio_usage.sh- no plaintokio::spawn(usenautilus_common::live::get_runtime().spawn(...)).check_copyright_year.sh- current year LGPL-3.0 header on every Rust file.
PRs that bypass these fail CI. For Cortana contributors filing PRs upstream, these are the conventions to internalize.
What to actually file in our own changelog
When we eventually ship MK3 to customers (M5+), our own CHANGELOG.md
should mirror Nautilus’s seven-section structure for our own release notes -
specifically calling out the upstream Nautilus version we’re pinned to, so
customers can read both changelogs to understand what changed in their
trading platform.
## [MK3 0.4.0] - 2026-09-15
**Upstream:** nautilus_trader 1.230.0
### Breaking Changes
- Removed legacy `cooldown_state.py` shim; migration: ...
### Security
- ...
### Fixes
- ...This is a habit to start in M1 and keep the entire project life.
Open threads / Saturday spike check
These should be confirmed during the 2026-05-09 spike:
- Confirm
pip install "nautilus_trader[ib]==<spike-pin>"resolves cleanly and produces a reproducible wheel (no transitive>=ranges leaking bleeding-edge sub-deps). - Confirm the
[ib]extra pins IBKR adapter dependencies tightly - the adapter is the most likely surface for breakage. - Read the most recent 3 release notes end-to-end before pinning. If any
contain a Breaking Change in
LiveNode,Strategy,RiskEngine, orMessageBus, lean toward the version before that change for M1 foundation work, then absorb at M2 start. - Find the deprecation half-life empirically: pick two recent “Deprecated” entries and count how many releases until they were “Removed”. Sets a rough lead-time expectation for our own migration planning.
See Also
- Nautilus Developer Guide (extension and contribution)
- Nautilus Architecture (kernel, ports & adapters, layering)
- Nautilus Concepts (full canon)
- Nautilus Getting Started (install, prerequisites)
- Nautilus Integrations (IBKR adapter detail - the most-likely-to-break adapter)
- 2026-05-09 Nautilus Spike Plan:
~/conductor/workspaces/cortanaroi-mk2/belo-horizonte/plans/2026-05-09-nautilus-spike.md - 2026-05-09 MK3 Roadmap:
~/conductor/workspaces/cortanaroi-mk2/belo-horizonte/plans/2026-05-09-mk3-roadmap.md
Timeline
- 2026-05-07 | Cody - Filed during pre-spike concept mastery sweep batch 7 (developer guide).