Skip to content

feat: publish to PyPI and hand downstream bumps to Dependabot - #25

Merged
zhanghanduo merged 2 commits into
mainfrom
feat/pypi-releases-and-dependabot
Sep 3, 2026
Merged

zhanghanduo merged 2 commits into
mainfrom
feat/pypi-releases-and-dependabot

Conversation

@zhanghanduo

Copy link
Copy Markdown
Collaborator

Going open source removed the premise the previous release design rested on. Closes the need for #24 — see below.

Why this replaces the mechanism from #23

Every consumer needed a credential just to resolve a private Git dependency, and opening a bump PR across repositories needed a second one — an org-level GitHub App or a cross-repo PAT, neither available without organization administration.

A public PyPI package needs none of it:

Before Now
apodex-agent-core @ git+ssh://…@<sha> apodex-agent-core==0.3.0
Deploy key / AGENT_CORE_REPO_TOKEN / insteadOf rewriting nothing — public package
repository_dispatch → repin script → PR Dependabot
GitHub App or PAT (needs org admin) nothing — Trusted Publishing is configured on PyPI's side

.github/downstream/ is deleted entirely.

What publishing looks like

release job (unchanged gates, plus two new ones) → publish-pypi job with id-token: write scoped to that job alone, so no build-time action can request the identity PyPI trusts. No API token exists anywhere in the repo.

Two new gates, both because a PyPI version number can never be reclaimed — not by deleting the release, not by yanking:

  • twine check on both artifacts;
  • install the built wheel into a clean environment and import agent_core (the release contract in the README required this; it was not implemented).

Version 0.3.0, not 0.2.0

0.2.0 was never tagged or distributed — it exists only in main's history. Consumers go from Git revisions straight to the first published release. CHANGELOG marks 0.2.0 Never published rather than pretending it was a release.

Also fixes two defects that shipped in #23

  1. The version gate accepted a decreasing version. It compared for inequality, not an increase, so a PR could move 0.2.0 back to 0.1.9 and pass.
  2. skip-version-bump was inert. Default pull_request event types exclude labeled, so applying the label did not re-run the check — the PR stayed red forever.

The one real limitation, documented

A Dependabot PR runs CI as if it came from a fork: read-only GITHUB_TOKEN, and secrets.* resolves against Dependabot secrets, not Actions secrets. Plain test CI is unaffected; any step consuming a secret needs that secret duplicated under Settings → Secrets → Dependabot, or a guard. docs/downstream-bump.md spells this out. Worth the trade against a long-lived cross-repo credential in both products.

Packaging metadata

PEP 639 SPDX (license = "Apache-2.0" + license-files) instead of the legacy table form, which had embedded the entire Apache text into the distribution's License field. Plus classifiers and project URLs. Metadata-Version: 2.5, twine check PASSED.

Verification

  • ruff clean, pyright 0 errors, 1138 passed, uv sync --frozen consistent.
  • twine check PASSED on wheel and sdist; wheel confirmed to carry agent_core/py.typed and the license.
  • The wheel smoke script was run locally, verbatim as it appears in the workflow — and caught a real error first: I had asserted user_msg("hi").role, but Message is a TypedDict, so constructors return plain dicts.
  • Every run: script passes bash -n.
  • New tests guard the invariants that would silently degrade: no PyPI token may appear, id-token must stay scoped to the publish job, twine check must run, .github/downstream/ must not return, the SPDX license must not gain a redundant License :: classifier (PyPI rejects both — after the tag exists).

Not done here

Downstream PRs ApodexHarness#524 and FrontierAgentInternal#107 install the now-deleted workflow and should be closed; they need replacing with a .github/dependabot.yml plus the switch to apodex-agent-core==0.3.0. Neither product declares AgentCore on main yet, so that belongs with whichever migration branch merges first.

Before tagging v0.3.0: register the PyPI pending publisher and create the pypi environment (docs/downstream-bump.md).

zhanghanduo and others added 2 commits September 3, 2026 17:44
Going open source removed the premise the previous release design was built on.
Every consumer needed a credential just to resolve a private Git dependency, and
opening a bump pull request across repositories needed a second one — an
organization-level GitHub App or a cross-repository PAT, neither of which is
available without org administration.

A public PyPI package needs none of it:

- Releases publish to PyPI from the tagged tree using Trusted Publishing (OIDC).
  No API token is stored anywhere; `id-token: write` is scoped to a separate
  publishing job so no build-time action can request that identity.
- Consumers depend on `apodex-agent-core==0.3.0` instead of a git+ssh revision,
  and Dependabot opens the bump pull request. Its `uv` ecosystem updates both
  pyproject.toml and uv.lock, and the pull request runs the product's own CI.
- The repository_dispatch + repin-script mechanism is deleted with
  .github/downstream/, along with every credential it needed.

Because a PyPI version number can never be reclaimed — not by deleting the
release, not by yanking — the release now runs `twine check` and installs the
built wheel into a clean environment to import it, before publishing. A broken
artifact must fail the release rather than burn the number.

Version 0.3.0, not 0.2.0: 0.2.0 was never tagged or distributed, so consumers
move from Git revisions straight to the first published release.

Also fixes two defects in the version gate that shipped in #23:

- it compared versions for inequality rather than an increase, so a pull request
  could move 0.2.0 back to 0.1.9 and pass;
- the skip-version-bump label was inert, because the default pull_request event
  types exclude `labeled`, leaving a labelled pull request red forever.

Packaging metadata now uses PEP 639 SPDX (`license = "Apache-2.0"` with
license-files) rather than the legacy table form, which had embedded the entire
Apache text into the distribution's License field.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@zhanghanduo
zhanghanduo merged commit 1d0ad18 into main Sep 3, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant