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.
- 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>. - 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.
- Editable installs are for development only. PEP 660 defines editable installs for
pyproject.tomlbuilds, so that a developer's venv sees source edits live. Production venvs install non-editable. - One uv workspace with one lockfile. All first-party packages are members of one workspace, which resolves them together into one
uv.lock. - Deploy offline from a wheelhouse, or from the lock. Two equivalent routes:
- build every wheel into a wheelhouse, then
pip install --no-index --no-depsthe set; - or
uv sync --package X --locked --no-editablein the target venv.
- build every wheel into a wheelhouse, then
- 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.
- No machine paths in production metadata. Production must not depend on
/opt/...path dependencies or editable dependencies. - 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.
- 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.
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:
- ADR-0103. It was Accepted on probation by Mohamed on 2026-10-15, until a POC passes. It says
packages/<c>/src/"is the deliverable". The byte-faithful copy taken from the frozen tree is an oracle that is "never committed". Measured 2026-10-22: the ADR file exists only on brancharchitecture/domain-model(b3ea29a31), not oncomponentsormaster. - ADR-0113 and LEVEL0. They define the cut-over as: old
src/orchestrata_ultimate/*removed, one merge, and/opt/orchestrataships from that tree. - PyPA and uv (findings 1 and 2 above): one edited source per package, with the wheel as the deployment copy.
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:
- A:
src/stays the edited source, andpackages/is generated from it at a pinned commit. - B: the same until the cut-over; then
packages/<c>/srcis the only edited source, and each package's built wheel at a pinned version is what deploys.
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.
- Put every first-party package in one uv workspace with one
uv.lock, and delete the nine per-package locks. - Replace the editable
/opt/codetools/currentdependency inpackages/contractswith a normal versioned dependency (a wheel on the GitLab PyPI registry, or a workspace member). - 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. - Declare codetools in kernel's dependencies, or remove the import.
- Add deptry (DEP001/DEP003/DEP004) to CI so that undeclared and dev-only imports fail the build.
- Deploy from wheels (wheelhouse plus
--no-index --no-deps, oruv sync --locked --no-editable). - 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.
Related
- Open decisions, where the
packages/dispute is listed - codetools versus the components branch
- Cut-over review and test re-point measurement
- Deployment
- Decisions
- codetools project
- Research index