The control plane publishes to PyPI as agentplane-control-plane
(CLI: control-plane, import: control_plane) using GitHub Actions with
Trusted Publishing (OIDC) — no API tokens are stored anywhere. Same
pattern as Chronicle (agent-chronicle) and TokenOps (agent-tokenops).
One workflow run is atomic: verify → build → git tag → GitHub Release → PyPI.
- Sign in at pypi.org (account that will own the project).
- Open Publishing (Your account → Publishing).
- Under Add a new pending publisher, choose GitHub and fill in:
- PyPI project name:
agentplane-control-plane - Owner:
theagentplane - Repository:
control-plane - Workflow filename:
release.yml - Environment name:
pypi
- PyPI project name:
- Save. The project does not exist yet; the first successful CI upload creates it and binds that publisher permanently.
Optional: do the same on TestPyPI
for a dry run (same project name agentplane-control-plane).
In the theagentplane/control-plane repo:
- Settings → Environments → New environment
- Name it exactly
pypi(must matchenvironment: pypiinrelease.ymland the PyPI publisher form). - Required reviewers are configured on this environment, so the PyPI upload pauses until a human approves it in Actions → the run → Review deployments. PyPI versions are immutable, so this is the last point at which a bad build can be stopped.
No secrets needed — OIDC uses permissions: id-token: write in release.yml.
- Move the
CHANGELOG.md[Unreleased]items under a new version heading with today's date; start a fresh empty[Unreleased]. - Bump
versioninpyproject.tomland__version__incontrol_plane/__init__.pyfollowing SemVer. Commit and merge tomain. - In GitHub: Actions → Release → Run workflow
- Branch:
main(the commit that has the version bump) - Tag (optional): e.g.
v0.1.0— leave empty to usev{version}frompyproject.toml
- Branch:
- The workflow will, in one run:
- Require the tag to match
pyproject.tomland__version__ - Build sdist/wheel and
twine check - Create and push the git tag
- Create the GitHub Release (attaches dist artifacts)
- Upload to PyPI — waits on
pypienvironment approval (see above)
- Require the tag to match
- Verify:
pip install agentplane-control-plane==X.Y.Z python -c "import control_plane; print(control_plane.__version__)" control-plane --help
PyPI versions are immutable and come from metadata inside the built wheel —
that metadata is read from pyproject.toml at build time. The git tag
(v0.1.0) is only a label; it does not set the package version.
If you passed tag v0.1.1 but pyproject.toml still says 0.1.0, the workflow
refuses to continue. Empty tag input defaults to v + the pyproject version.
Also keep control_plane.__version__ in sync so runtime version matches pip.
- Stay in
0.xwhile the HTTP contract may still change. - Install name:
agentplane-control-plane. Import name:control_plane. CLI:control-plane. - Do not create tags or GitHub Releases by hand for normal cuts — use Run workflow so tag, Release, and PyPI stay in lockstep.