Files
agentic-mobile-control/openspec/changes/downgrade-python-3-13-paddleocr/design.md
T
q792602257 25ebc10a8a
Tests / Test passed: 789
feat: downgrade Python baseline to 3.13 for PaddleOCR compatibility
paddlepaddle has no Python 3.14 (cp314) wheel on PyPI, so host-agent
deployments on 3.14 can never install it, causing OCR to fail at
runtime with RuntimeError. Pin the workspace to Python 3.13 across
all pyproject.toml files, the Docker base image, and the Jenkins CI
image; regenerate uv.lock against 3.13.

Also fixes a pre-existing Python-2-style `except X, Y:` syntax error
(invalid in all Python 3.x) in runtime/task.py and
packages/cloud-platform/cloud/{sql_repository,internal_api/api}.py,
introduced in 22d37ca9 and unrelated to this change's scope, which
blocked the full test suite from collecting on any interpreter
version.

openspec change: downgrade-python-3-13-paddleocr
2026-07-14 18:05:49 +08:00

8.4 KiB

Context

The workspace root and all three uv workspace members (apps/device-host-agent, apps/cloud-api, packages/cloud-platform) pin requires-python = ">=3.14". Dockerfile and Jenkinsfile both build on astral-sh/uv:python3.14-bookworm-slim. paddleocr>=3.0.0 is a hard dependency of the root device-agent-runtime project (pyproject.toml:13), and perception/ocr.py::PaddleOCREngine._load() requires the separate paddlepaddle package at runtime to actually run inference — but paddlepaddle has no cp314 wheel on PyPI (verified against the PyPI project page and PaddlePaddle/PaddleOCR#17370), so no Python-3.14 environment in this project can ever install it. This has already caused a production task failure on a real host-agent device. docs/MACOS_IPHONE_SETUP.md:156-158 already flagged this exact tension and required it be resolved as a deliberate, validated workspace-wide change rather than an ad hoc per-host downgrade — this design is that change.

Goals / Non-Goals

Goals:

  • Make paddlepaddle installable (and therefore OCR functional) on every environment that follows this project's documented setup, by moving the supported Python version to one PaddlePaddle actually ships wheels for.
  • Keep the four workspace pyproject.toml files, uv.lock, Dockerfile, and Jenkinsfile mutually consistent on Python 3.13.
  • Preserve all existing runtime behavior — this is a toolchain change, not a feature change. No application code logic should need to change to satisfy this proposal (only the interpreter/dependency-resolution target).

Non-Goals:

  • Not declaring paddlepaddle as a project dependency in pyproject.toml/uv.lock. Per the earlier decision in this incident, installing paddlepaddle itself remains a manual, per-host operational step (uv pip install paddlepaddle), independent of this change. This proposal only removes the upstream blocker that made that manual install impossible; it does not automate the install.
  • Not hardening perception/ocr.py::run_ocr() to catch RuntimeError from the OCR engine. That was raised and explicitly declined as a separate concern during incident triage — a task can still fail today if paddlepaddle is missing on a 3.13 host, just for a legitimate reason (nobody installed it) rather than an impossible one (no wheel exists).
  • Not adding a Python version support matrix / CI test matrix across multiple Python versions. This project has always targeted a single pinned version; this change moves that single pin from 3.14 to 3.13, it does not introduce multi-version support.
  • Not touching any application-level requirement/behavior — no openspec/specs/* capability changes accompany this change (see proposal.md Capabilities section).

Decisions

D1: Target >=3.13,<3.14 (not a bare >=3.13). An open-ended >=3.13 would silently re-admit 3.14 (and any future 3.15+) the moment a contributor's local uv picks a newer interpreter, reintroducing this exact bug with no warning. Pinning <3.14 forces uv sync/uv lock to fail loudly if someone's environment tries to resolve against an unsupported interpreter, and makes the reason (paddlepaddle wheel availability) discoverable by grepping for the upper bound. When PaddlePaddle eventually ships cp314 wheels, lifting this bound is a deliberate, reviewable one-line change rather than an implicit drift.

D2: Change all four pyproject.toml files together, in one change. uv workspaces require a single resolved environment shared across all members (one uv.lock, one .venv) — apps/cloud-api, apps/device-host-agent, and packages/cloud-platform cannot each pin a different Python floor without breaking uv sync --all-packages. Alternatives considered: downgrading only apps/device-host-agent (the only member that actually touches OCR) — rejected, because uv workspace resolution is root-driven and per-member requires-python values are intersected, not independently honored; leaving the others at >=3.14 while the root moves to <3.14 would make the workspace intersection empty and uv sync would fail outright.

D3: Re-run uv lock from scratch against Python 3.13 rather than hand-editing uv.lock. uv.lock embeds resolution metadata (wheel tags, hashes) tied to the target Python version; hand-editing it to swap version markers risks producing an internally inconsistent lockfile that uv sync --locked would reject or, worse, silently misresolve. A clean uv lock re-resolution is the only way to get a trustworthy lockfile, and lets the resolver naturally surface any dependency that (unlike paddleocr) turns out to have its own 3.13-vs-3.14 constraint conflicts.

D4: Treat the Python-3.14-only-syntax audit as a verification task, not a design-time judgment call. A preliminary grep for known 3.14-only constructs (PEP 750 t-strings, annotationlib, compression.zstd) found no usage — but the only reliable check is actually running the full non-integration test suite under a real 3.13 interpreter, since static grepping cannot catch every possible 3.14-only stdlib call. tasks.md makes this an explicit, checked step rather than assuming the grep was sufficient.

D5: No openspec/specs/* capability delta. This change alters the supported Python interpreter version and dependency resolution, not any capability's observable request/response behavior, protocol, or user-facing requirement. Every existing capability spec (host-agent-protocol, perception-provider, etc.) continues to describe the system correctly after this change. Consistent with treating this as a pure infrastructure/toolchain change (see proposal.md Capabilities section, which lists none).

Risks / Trade-offs

  • [Risk] Some other pinned dependency in uv.lock might not have resolved the same way (or at all) for cp313 as it did for cp314 → Mitigation: uv lock will surface any resolution failure explicitly at lock time (task 2), and the full non-integration test suite must pass against the regenerated lock before this change is considered complete (task 5).
  • [Risk] Any contributor or deployment target still holding a Python 3.14 interpreter/venv will have builds/CI break the moment this merges → Mitigation: this is the intended, accepted breaking change (called out as BREAKING in proposal.md); docs/MACOS_IPHONE_SETUP.md is updated in the same change to tell operators to install Python 3.13 instead, and the Docker/Jenkins base images are updated in lockstep so CI and containerized deployments do not need a separate manual step.
  • [Risk] Undiscovered 3.14-only syntax somewhere in the ~200+ Python files → Mitigation: D4 — full test suite run under 3.13 is a required, verified task, not an assumption.
  • [Trade-off] This still does not make OCR work out of the boxpaddlepaddle remains an unlisted, manual dependency per the prior decision (Non-Goals). Every host still needs a manual uv pip install paddlepaddle after this change, and that install will still be wiped by a future uv sync --locked (a previously accepted and documented risk, now finally actionable since a compatible wheel will exist). This trade-off was deliberately kept out of scope to keep this change focused on the single blocking issue (wheel availability), not dependency-declaration policy.

Migration Plan

  1. Update requires-python in all four pyproject.toml files to >=3.13,<3.14.
  2. Update Dockerfile and Jenkinsfile base images to astral-sh/uv:python3.13-bookworm-slim.
  3. uv python install 3.13, then uv lock to regenerate uv.lock against the new interpreter.
  4. uv sync --locked --all-packages to provision a 3.13 .venv from the new lock.
  5. Run the full non-integration test suite (uv run --all-packages pytest -m "not integration") and fix any failures caused by the interpreter change (expected: none, per D4).
  6. Update docs/MACOS_IPHONE_SETUP.md to reflect the new 3.13 baseline and remove the now-obsolete "do not downgrade" warning.
  7. No code rollback path is meaningfully separate from a git revert of this change — reverting restores the 3.14 pin and the pre-existing (already-broken) OCR situation, which is a safe rollback target since it is today's status quo.

Open Questions

  • None blocking. If PaddlePaddle later ships a cp314 wheel, lifting the <3.14 bound (D1) is a natural, separately-reviewable follow-up, not something this change needs to anticipate further than the explicit upper bound already makes discoverable.