Orchestrata Ultimate › Research › Python deployment best practices

Python deployment best practices, measured against the repo

Question How should a multi-package Python project be laid out, locked and deployed, and what does that make packages/?
Asked 2026-10-22, during the components cut-over planning
Method Perplexity research citing the PyPA packaging guide, the uv documentation, PEP 621, PEP 660, PEP 735, the deptry docs and GitLab's PyPI package registry docs. Then the repo state was measured in wt-COMPONENTS (branch components, d4b7e162a) and in orchestrata.codetools
Status Research done. One part acted on (the codetools cycle, a5c419f5). The packages/ dispute is OPEN: it was published on 2026-10-23 as ADR-0132 (Proposed), and Mohamed rules. Nothing else changed yet

What the standard says

The research came back with nine findings. Each is a practice the cited sources agree on.

  1. One edited source per package. Each package has exactly one place where people edit its code. The usual shape is a workspace member at packages/<name>/src/<pkg>.
  2. The wheel is the deployment artifact. You do not keep a second, hand-copied source tree for deployment. You build a wheel from the edited source and install that.
  3. Editable installs are for development only. PEP 660 defines editable installs for pyproject.toml builds, so that a developer's venv sees source edits live. Production venvs install non-editable.
  4. One uv workspace with one lockfile. All first-party packages are members of one workspace, which resolves them together into one uv.lock.
  5. Deploy offline from a wheelhouse, or from the lock. Two equivalent routes:
    • build every wheel into a wheelhouse, then pip install --no-index --no-deps the set;
    • or uv sync --package X --locked --no-editable in the target venv.
  6. Split out the runtime part of a toolchain. If production needs a slice of a dev toolchain, put that slice in a small runtime package, and give it no dependency back on the application.
  7. No machine paths in production metadata. Production must not depend on /opt/... path dependencies or editable dependencies.
  8. Check declared dependencies in CI with deptry. The rules:
    • DEP001: a module is imported but not declared;
    • DEP003: a module is imported but only arrives transitively;
    • DEP004: a dev-only dependency is imported by production code.
  9. Cut over by strangling, not by a big bang. Move consumers to the new packages one at a time while the old tree still works, then delete the old tree.

PEP 621 (project metadata in pyproject.toml) and PEP 735 (dependency groups for dev and test tools) are the metadata side of the same picture. They let one pyproject.toml per package carry both its runtime and its dev dependencies, so nothing needs a second copy.

What the repo looks like today

Practice Repo state Evidence
One workspace, one lock No workspace covers packages/. The root [tool.uv.workspace] has one member, scripts/_codemod_fast wt-COMPONENTS/pyproject.toml lines 539–542, read this run
One lockfile 9 lockfiles, one per package: contracts, gateway, governance, integration, kernel, knowledge_graph, session, tools, volks ls packages/*/uv.lock, this run
No /opt or editable deps packages/contracts has an editable /opt dependency. Its test extra lists codetools, and line 16 reads codetools = { path = "/opt/codetools/current", editable = true } packages/contracts/pyproject.toml, this run
Runtime slice in its own package About 1,550 generated lines import codetools.rtm. The packages' generated tests call into the dev toolchain for traceability Session summary, 2026-10-23. Not re-measured in this run
deptry DEP001 clean kernel imports codetools without declaring it Session summary. packages/kernel/pyproject.toml has no codetools entry (checked this run)
No dependency cycles codetools depended on orchestrata-ultimate, which makes a cycle with any orchestrata package that tests through codetools. Fixed by a5c419f5 (2026-10-23 00:01): orchestrata-ultimate is now only the optional openspec extra, "injected into read_requirements (Mohamed, 2026-10-20). Not needed to install or run" git log and pyproject.toml in orchestrata.codetools, this run

The deployed services already run from a separate install: /opt/orchestrata has its own .venv and a VERSION file naming the commit it was installed from (see runtime services). Whether that install is built from wheels was not measured. The gaps above are all in packages/, the tree that is meant to replace src/orchestrata_ultimate.

Diagram projects-orchestrata-ultimate-research-python-deployment-best-practices-1

The target the research describes. Today the repo has the left box (per-package src/), nine separate locks instead of one, and no deptry step.

The packages/ dispute (OPEN)

The research turned up a disagreement about what packages/ is for. It gates cut-over planning.

On 2026-10-23 it was recorded as ADR-0132 (Proposed), "which tree is edited at the cut-over", listed on open decisions and decisions. The ADR's context gives the scale: master holds src/orchestrata_ultimate/ (902 modules) and packages/*/src (4,227 modules).

Both positions are set out below as their holders state them.

Position A: packages/ is a deployment copy of src

Mohamed, 2026-10-22: packages/ is not for editing. It is a copy of the code from src, taken at a specific time and version, for deployment in that structure.

Supporting fact: packages/ was produced from src frozen at e57782ea4. It really was a copy of the source taken at one version.

What follows if A holds: src/orchestrata_ultimate stays the edited source. Three planned pieces of cut-over work come into doubt: re-pointing the tests at the packages, the map exporter, and "remove the old src tree". Two earlier commits that hand-edited packages/volks (b89593cb5, fe8dece4e) would be edits to a copy.

Position B: packages/<c>/src is the edited source, and the wheel is the copy

The records' position:

What follows if B holds: the cut-over work stands as planned (see the cut-over review). src/orchestrata_ultimate is deleted at the merge.

ADR-0132 words the two options as follows:

The ADR's recommendation is B, "the only option in which the cut-over completes". The ruling is Mohamed's.

Common ground already in the rulings

RUL-027 already requires production to run its own checkout, never the development tree. Neither position contradicts it. The dispute is about which tree people edit, and whether the deployed copy is a source tree or a wheel.

Recommendation from the research

These hold whichever way the dispute is ruled. None of them is a ruling.

  1. Put every first-party package in one uv workspace with one uv.lock, and delete the nine per-package locks.
  2. Replace the editable /opt/codetools/current dependency in packages/contracts with a normal versioned dependency (a wheel on the GitLab PyPI registry, or a workspace member).
  3. Move the runtime part that generated tests need (codetools.rtm's trace decorator) into a small runtime package with no dependency on orchestrata, and point the ~1,550 generated imports at it.
  4. Declare codetools in kernel's dependencies, or remove the import.
  5. Add deptry (DEP001/DEP003/DEP004) to CI so that undeclared and dev-only imports fail the build.
  6. Deploy from wheels (wheelhouse plus --no-index --no-deps, or uv sync --locked --no-editable).
  7. Cut over by strangling: one consumer at a time, then remove the old tree.

Already done: the codetools to orchestrata-ultimate cycle was removed by codetools a5c419f5, deployed by ct-install 75, with 20 of 20 tests passed.