This document describes the coordinated-release model used by mellea-contribs. A single root tag v<X.Y.Z> builds and publishes every subpackage plus the meta-package as assets of one GitHub Release. There is no PyPI publishing and no per-subpackage tag pipeline.
Note: the
release.ymlworkflow that drives this lands in Phase 3 of the restructure. This document is the contract that pipeline implements; some references below are forward-looking until P3 merges.
The receiver (.github/workflows/receive-mellea-release.yml) opens bump PRs using a token minted from the "mellea auto release" GitHub App, not the default GITHUB_TOKEN. This is because the generative-computing org locks GITHUB_TOKEN to read-only and forbids Actions from creating pull requests org-wide (and the per-repo override isn't available), so gh pr create under the default token fails and leaves orphaned sync-mellea-* branches.
For the receiver to work, all of the following must hold (they already do today):
- The "mellea auto release" App is installed on
generative-computing/mellea-contribswith Contents: write and Pull requests: write. - Its credentials are available to the repo's Actions as
CI_APP_ID(variable) andCI_PRIVATE_KEY(secret) — the same paircd.ymluses.
No repo or org Actions setting needs changing; the App token supplies the write access directly.
mellea-contribs releases track upstream mellea exactly. Every published mellea vX.Y.Z triggers a contribs release at the same vX.Y.Z. Off-cycle subpackage patches are not released independently — they ride the next coordinated vX.Y.(Z+1).
The release ships:
- One wheel + sdist per subpackage (
_integration_core,dspy,crewai,langchain,agent-utilities,reqlib). - One meta-package wheel + sdist whose
[project.optional-dependencies]extras pin each subpackage by direct-URL to assets of the same release. - A version-free copy of the meta-package wheel named
mellea_contribs-py3-none-any.whlso the GitHubreleases/latest/download/redirector resolves it without callers naming a version.
Versions are not chosen by contribs maintainers. Each contribs release adopts the upstream mellea version that triggered it; pre-releases on the mellea side propagate the same suffix here. The mellea>= floor inside each subpackage's own dependencies is owned by that subpackage and changes only when the owner bumps it deliberately.
Automatic (the normal path). A release event on the upstream mellea repo is dispatched into this repo. The receiver (receive-mellea-release.yml) opens bump PRs in two passes:
- A single PR for
_integration_core(every framework subpackage depends on it, so it must land first). - Once that PR is merged, one PR per remaining subpackage opens together:
dspy,crewai,langchain,agent-utilities,reqlib, plus the meta-package.
Each bump PR carries a sync-mellea-version:<X.Y.Z> label so the receiver can recover the target version on the second pass without re-parsing the branch name. Merging the final PRs runs release.yml, which builds and publishes the coordinated release.
Manual escape hatch. A maintainer pushes a v<X.Y.Z> tag from main. Use this only when the dispatch path is unavailable (e.g. recovering from a broken release).
The pass-2 PRs are triggered by the merge event of the pass-1 PR. GitHub has a safety rule that prevents events caused by the default GITHUB_TOKEN from triggering further workflow runs — so if the receiver ran under GITHUB_TOKEN, merging the pass-1 PR (by anyone) would not open pass 2, and the release would silently stop.
The receiver avoids this by minting a token from the "mellea auto release" GitHub App (.github/workflows/receive-mellea-release.yml, via CI_APP_ID / CI_PRIVATE_KEY). An App token is exempt from the safety rule, so merging the pass-1 _integration_core PR — by a human, a bot, or auto-merge — correctly re-triggers the receiver and opens pass 2. No special merge handling is required.
If you ever revert the receiver to the default
GITHUB_TOKEN, this breaks: pass 2 stops firing on merge and a human would have to click merge on the_integration_corePR to advance it. Keep the App token.
- Validate structure. Runs the same gate as PR CI; fails fast if any subpackage's
pyproject.tomlis malformed. - Build
_integration_corefirst. Framework subpackages depend on it, so it must be built and assembled before its dependents. - Lint, mypy, pytest per subpackage. Each subpackage runs its full CI suite at the tagged version. A failing subpackage is recorded as a straggler (see below) and excluded from the release.
- Build wheels with rewritten dependencies. Source
pyproject.tomlfiles declare[tool.uv.sources] mellea-contribs-integration-core = { path = "../_integration_core" }for local-dev convenience. The pipeline rewrites this dependency to the GitHub Release URL ofmellea-contribs-integration-coreat the samevX.Y.Zbefore invokinguv build. The[tool.uv.sources]table does not appear in the published wheel's metadata — that is a property of how hatch builds wheels (uv sources are uv-only). Contributors do not need to think about this. - Build the meta-package wheel. Each
[project.optional-dependencies]extra is rewritten at build time to point at the just-built subpackage's release-asset URL, pinned tovX.Y.Z. - Upload assets. Wheels and sdists are attached to the GitHub Release; the meta-package wheel is also uploaded under the version-free name
mellea_contribs-py3-none-any.whl. - Generate release notes. Notes call out any stragglers explicitly (see below).
On the Releases page, confirm the vX.Y.Z release contains:
- A wheel + sdist per non-straggler subpackage (
mellea_contribs_<name>-X.Y.Z-py3-none-any.whl,.tar.gz). - The versioned meta-package wheel + sdist.
- The version-free
mellea_contribs-py3-none-any.whlcopy. - Release notes listing the included subpackages and any stragglers excluded from this release.
The recommended install uses the latest redirector:
pip install "mellea-contribs[<extra>] @ https://github.com/generative-computing/mellea-contribs/releases/latest/download/mellea_contribs-py3-none-any.whl"The meta-package wheel is downloaded "latest", but its extras pin concrete subpackage versions internally — the install is reproducible.
For a fully explicit pin, use the versioned URL:
pip install "mellea-contribs[<extra>] @ https://github.com/generative-computing/mellea-contribs/releases/download/vX.Y.Z/mellea_contribs-X.Y.Z-py3-none-any.whl"Available extras: dspy, crewai, langchain, agent-utilities, reqlib, plus all. _integration_core is always installed transitively.
If a subpackage fails to build, lint, or test in the release pipeline, it is excluded from [all] extras and from the GitHub Release asset set for that version. The CHANGELOG entry for the release names the affected subpackages explicitly.
The straggler ships in the next coordinated release once its owner lands the fix. Distro-style: the train leaves on schedule; missed packages catch the next one. Users who need the missing subpackage can either pin to the prior release or install the subpackage from source until the next cut.
Each release entry in CHANGELOG.md records:
- The
melleaversion this release tracks. - A short summary of changes per subpackage that shipped.
- An explicit "Skipped" subsection naming any straggler subpackages excluded from this release and linking to their tracking issue.
- Any breaking changes called out under a
### Breakingheading per subpackage.
The CHANGELOG is updated in the sync PR before merge — not after the tag is pushed — so the diff is reviewable.
A twice-weekly smoke job (.github/workflows/smoke-against-mellea-main.yml) runs each opted-in subpackage's tests against mellea @ main at 07:17 UTC every Monday and Wednesday. The matrix is read from .github/smoke-matrix.json; while that list is empty the smoke job is skipped. Each subpackage opts in by appending its path to the subpackages array as part of its migration PR.
When a subpackage's smoke job goes red, the auto-issue bot (.github/scripts/auto_issue_bot.py) tracks consecutive failures and opens a tracking issue once the second consecutive red lands. Issues are labelled contribs-broken and assigned to the package's OWNERS.
A second twice-weekly workflow (.github/workflows/auto-issue-archival.yml) runs at 08:00 UTC every Monday and Wednesday and applies the archival timeline based on how long the contribs-broken label has continuously been present on each tracking issue:
- Day 7 — a reminder comment is posted to escalate the failure.
- Day 14 — the bot posts a notice that the subpackage will be called out as broken in the next contribs release notes. When you cut a release while a tracking issue is at or past day 14, mention the affected subpackages explicitly in the release notes.
- Day 21 — the bot applies the
archivedlabel and posts a final comment. Contribs maintainers move the subpackage to the archived layout in a follow-up PR.
Recovery (a green smoke run) clears the contribs-broken label, posts a "smoke green again on <date>, fixed in <sha>" comment, resets the archival clock, and leaves the issue open for a human to close. Removing the contribs-broken label by hand has the same effect — the timeline is driven by the label, not the issue's open state.
The bot's persistent state lives at .github/bot-state/<package>.json (one JSON file per subpackage) on a bot-managed branch and is updated by each invocation. Per-file state eliminates the write race that a single shared file would create when smoke legs run concurrently.
To run the bot locally against the in-memory fake (no GitHub API calls):
uv run python .github/scripts/auto_issue_bot.py \
--action record-failure \
--package dspy \
--run-url https://example/run/1 \
--fakeMaintainers can build the full asset set locally without publishing:
# Build _integration_core first
cd _integration_core && uv build && cd ..
# Then each framework subpackage
for pkg in dspy crewai langchain agent-utilities reqlib; do
(cd "$pkg" && uv build)
done
# Build the meta-package last
uv buildThe wheels land in each subpackage's dist/. To verify that the published wheel's metadata does not leak [tool.uv.sources], inspect a built wheel:
unzip -p dspy/dist/mellea_contribs_dspy-*.whl '*.dist-info/METADATA' | grep -i 'uv\.sources\|integration-core'Only the rewritten direct-URL dependency on mellea-contribs-integration-core should appear.