Python version pin — ratify requires-python >=3.13 (matches existing .python-version + bc8ce4e commit)

Published

May 16, 2026

ADR-037: Python version pin — ratify requires-python >=3.13

Status

Accepted (2026-05-16). Ratifies the existing state established by prior commit bc8ce4e (chore: pin Python to >=3.13; add .python-version). Closes SPEC_GREENFIELD ledger row 310 (Python version pin). Companion to ADR-036 (library version pins — uv sync verification depends on Python 3.13 + library pins being mutually compatible).

Context

requires-python in pyproject.toml controls who can install the project. .python-version (consumed by pyenv / uv python pin) controls which Python version is used in the dev environment. The two should agree.

Existing state at Phase 0-08 entry (heavily pre-constraining): - .python-version = 3.13 (committed at bc8ce4e). - pyproject.toml line 7: requires-python = ">=3.13" (committed at bc8ce4e). - Prior commit bc8ce4e: chore: pin Python to >=3.13; add .python-version.

So Python >=3.13 is already in the repo. Row 310 is effectively a ratification with rationale, not an open choice. Four options were considered (per Phase 0-08 Q1 walk):

  1. Ratify >=3.13 — existing state; ADR documents the lock.
  2. Tighten to ==3.13.* — brittle.
  3. Loosen to >=3.12 — contradicts the existing pin.
  4. Loosen to >=3.11 — same as C; bigger gap.

User selection at Q1 walk: A.

Decision

Locked Python version pin

pyproject.toml line 7 (unchanged from bc8ce4e):

requires-python = ">=3.13"

.python-version (unchanged from bc8ce4e):

3.13

Rationale

  • Active support — Python 3.13 in active-support phase through October 2029 per PEP 602 annual release schedule. EOL is well past the submission’s audit lifetime.
  • uv handles installability transparently — any reviewer running make smoke (T1 per ADR-034) or make eval-from-hub (T0 per ADR-034) invokes uv sync which auto-installs Python 3.13 from the pinned .python-version regardless of the reviewer’s system Python. Wider-compatibility pins (option C/D) don’t materially help reviewer install friction.
  • Library compatibility — the three load-bearing libraries (eval-toolkit v0.31.0 + runpod-deploy v0.7.7 + research_toolkit v1.9.1 per ADR-036) are own-authored; per-library requires-python constraints are under Brandon’s control. Verified at Phase 0-08 close via uv sync — if any library blocks >=3.13, this ADR cannot lock until the conflict is resolved via either upstream patch or superseding pin.
  • uv.lock provides byte-level reproducibility on top — already committed per kit-level discipline; tag pin (per ADR-036) + Python pin (this ADR) + lockfile = canonical reproducible install.
  • Aligns with prior commit bc8ce4e — ratifies a deliberate prior choice rather than introducing new constraint.

Phase 0-08 close verification

uv sync runs at Phase 0-08 close (after both ADR-036 library pins are committed) to verify the full dependency tree resolves cleanly under requires-python >=3.13. The check is:

uv sync --extra dev

Success = all three load-bearing libraries + dev dependencies install. Failure = one of the libraries has a requires-python constraint below >=3.13 (would block install); fix-forward per the limitation section below.

Consequences

Positive

  • Zero churn — existing artefacts (.python-version, pyproject.toml, uv.lock) already encode this pin; ADR ratifies what’s already true.
  • Active-support window through Oct 2029 — pins to a version that won’t reach EOL during the submission’s audit lifetime.
  • Modern type-system features available — PEP 695 type parameter syntax + PEP 701 f-string improvements + free-threaded experimental build all available if needed.
  • uv-driven workflow installability — reviewer install friction is independent of the pin width; any reviewer running make smoke / make eval-from-hub gets Python 3.13 auto-installed.

Negative / cost

  • 3.13 is recent (Oct 2024 release) — some downstream wheels may not have 3.13 builds yet. Mitigation: uv defaults to building from source for missing wheels; slower first install but not blocking. Mitigation: the three load-bearing libraries are own-authored so version compatibility is under Brandon’s control.
  • requires-python >=3.13 blocks reviewers on system Python ≤3.12 from installing directly via pip install -e . — but uv is the documented install path (per Makefile install target = uv sync --extra dev); reviewers using pip directly fall outside the supported install path.

Neutral

  • ==3.13.* exact-minor pin not chosenuv.lock already provides byte-level reproducibility; exact-minor adds no new value.
  • Future Python releases (3.14, 3.15) automatically allowed by >=3.13 — bumps are not blocked; only floor is enforced.

Limitation

3.13 is recent — uv sync at Phase 0-08 close is the verification gate. If any of the three load-bearing libraries has a requires-python constraint below >=3.13 that breaks compatibility (rare since 3.13 is back-compatible with most 3.10+ code), fix-forward via:

  • (a) Bump the upstream library requires-python to >=3.13 in a same-day patch (Brandon is the upstream author for all three).
  • (b) Loosen this ADR’s pin to >=3.12 via superseding ADR; update .python-version to 3.12 + pyproject.toml to >=3.12.

Extension condition for revisit

  • 3.13-only feature dependency emerges — e.g., free-threaded build for a CPU-bound bootstrap loop, PEP 695 syntax that 3.12 doesn’t support — tighten pin to >=3.13 (already there) with a note in the writeup; no ADR supersession needed for this case.
  • 3.13 wheel-availability problem on RunPod base images — observed at Phase 1+ entry — loosen pin to >=3.12 via superseding ADR; update .python-version + pyproject.toml. Currently expected to not be an issue since RunPod base images target current Python versions.
  • Python 3.14 release (Oct 2025) becomes the new active-support floor years later — currently no action needed; >=3.13 accommodates 3.14 + 3.15 + future.

Alternatives Considered

  • (B) ==3.13.* exact-minor pin — brittle; refuses 3.14+; uv.lock already provides byte-level reproducibility. Rejected per Q1 walk.
  • (C) >=3.12 loosen — contradicts the existing pin at bc8ce4e; doesn’t materially expand reviewer base since uv handles installability transparently. Rejected per Q1 walk.
  • (D) >=3.11 loosen further — same as C; bigger gap; loses more 3.13 features. Rejected per Q1 walk.

References

  • Python release schedule (PEP 602) — https://devguide.python.org/versions/
  • uv python pinning docs — https://docs.astral.sh/uv/concepts/python-versions/#installing-python
  • PEP 602 annual release cadence — https://peps.python.org/pep-0602/
  • ADR-036 (library version pins — uv sync verification depends on this pin)

Transcript

See transcripts/2026-05-16__phase-0-08__process-tech-stack-acceptance.md for the conversation that led to this decision (Q1 walk + option A ratification).