- name
- agent-framework-py-release
- description
- Use when cutting a Python release for the microsoft/agent-framework monorepo. Triggers on "bump py versions", "cut a python release", "prepare release PR for python", "release py packages", "bump python to X.Y.Z", or similar requests to bump Python package versions and prepare a release PR. Handles all four lifecycle tiers (alpha, beta, rc, released) with CHANGELOG-driven selective bumps, floor bound checks, and post-bump validation.
# Agent Framework Python Release
Cuts a Python release PR for the `microsoft/agent-framework` monorepo.
## Core principle: CHANGELOG drives bumps
**No more universal lockstep.** A package only bumps if it has a CHANGELOG entry this cycle. Draft the CHANGELOG first, then derive the bump list from it.
Exceptions to selective bumping (these stay coupled regardless of per-package changes):
- **Root `agent-framework` is coupled to `agent-framework-core` via `agent-framework-core[all]==X.Y.Z`** (exact pin in `python/pyproject.toml`). If `core` bumps, root bumps. Root may also bump independently for its own changes (docs/metadata/extras).
- **Beta-tier cohort bumps are still allowed** when the user explicitly wants a fresh date stamp across all betas for cohort signaling — but this is a deliberate user decision per release, not a default.
If the user explicitly states which packages to bump (or hands over a PR list per package), treat that as authoritative.
## Lifecycle tiers and version rules
Use `python/.github/skills/python-package-management/SKILL.md` as the source of truth for lifecycle
version patterns, date-stamp cutoffs, classifier alignment, and internal dependency updates.
For release work, derive the live tier map at release time from `python/PACKAGE_STATUS.md` and the actual
`version =` lines in each `pyproject.toml`. Do not hardcode counts — packages move between tiers.
## Inputs to confirm before bumping
1. **The changeset**: explicit commits/PRs the release covers, OR derive from `git log ${LAST_RELEASED_TAG}..${RELEASE_BASE} -- python/`.
2. **Per-package CHANGELOG entries**: which packages will get a line in the new release section. This list IS the bump list.
3. **Per-released-package semver bump**: for each released-tier package that has a CHANGELOG entry, decide PATCH / MINOR / MAJOR.
4. **Date stamp** (only if any alpha/beta is being bumped): default from the `python-package-management`
date-stamp rule.
5. **Optional cohort bump?**: ask if betas should all get a new date stamp regardless of per-package changes. Default: no.
If the user states target versions or a date explicitly, use exactly what they said — do not "correct" based on historical timezones.
## Non-negotiable rules
- **Release workflow owns the CHANGELOG**: individual feature/fix PRs do not edit
`python/CHANGELOG.md`; this workflow creates the entries centrally from merged changes.
- **CHANGELOG-driven bumps**: only packages mentioned in the new CHANGELOG section get version bumps. Exceptions: root follows core (==pin); user-opted cohort bump on betas.
- **Follow `python-package-management` for package lifecycle and versioning rules** — do not duplicate those
rules in this release workflow.
- **No `Co-Authored-By` trailer** on any commit.
- **Use `uv run`** for all Python/poe commands (`uv run poe ...`, `uv run pytest ...`).
- **Never rename an existing CHANGELOG section header** during a new release cut. Only INSERT a new section above existing ones.
- **Footer reference links are part of the CHANGELOG edit**, not optional.
## Workflow
### 1. Orient and create branch
```bash
git fetch origin main --tags --quiet
git fetch upstream main --tags --quiet 2>/dev/null || true
git status
# Fork clones use upstream/main as the authoritative release base; direct clones use origin/main.
if git show-ref --verify --quiet refs/remotes/upstream/main; then
RELEASE_BASE=upstream/main
else
RELEASE_BASE=origin/main
fi
git log -1 --oneline "$RELEASE_BASE"
```
If the user already has a `bump-py-ver-release-*` branch checked out, use it. Otherwise:
```bash
git checkout -b bump-py-ver-release-YYMMDD "$RELEASE_BASE"
```
### 2. Build the live tier map
Read `python/PACKAGE_STATUS.md` to enumerate current packages and lifecycle stages, and `grep '^version' python/pyproject.toml python/packages/*/pyproject.toml` to confirm actual versions. Cross-reference — if `PACKAGE_STATUS.md` shows a package as `beta` but its version looks like `1.0.0aYYMMDD`, surface the inconsistency before proceeding.
Watch for: new packages added since the last release, lifecycle transitions (alpha→beta, beta→rc, rc→released), and stale packages that haven't been touched in many cycles.
### 3. Identify changeset
Use the LATEST released tag as the compare base:
```bash
LAST_RELEASED_TAG=$(
git tag -l 'python-[0-9]*.[0-9]*.[0-9]*' \
| uv run --directory python python -c 'import sys; tags=[t.strip() for t in sys.stdin if t.strip()]; print(max(tags, key=lambda t: tuple(map(int, t[7:].split(".")))))'
)
echo "Compare base: $LAST_RELEASED_TAG"
```
List commits and packages touched:
```bash
git log --oneline ${LAST_RELEASED_TAG}..${RELEASE_BASE} -- python/ ':!python/CHANGELOG.md'
# Per-commit package footprint
for sha in $(git log --format='%H' ${LAST_RELEASED_TAG}..${RELEASE_BASE} -- python/); do
echo "--- $(git show -s --format='%h %s' $sha) ---"
git show --name-only --format='' $sha | grep '^python/packages/' | \
sed 's|^python/packages/||' | awk -F/ '{print $1}' | sort -u
done
```
When both remotes exist, record whether the fork is behind the authoritative base:
```bash
git rev-list --left-right --count origin/main...upstream/main
```
If user provides an explicit commit/PR list, treat THAT as authoritative.
#### 3a. Build the authoritative touched-package set
Aggregate the per-commit footprint into a single union across the whole range. This is the **source of truth** for what must appear in the CHANGELOG. Save it before drafting entries.
```bash
# Union of all touched package directories across the range
git log --name-only --format='' ${LAST_RELEASED_TAG}..${RELEASE_BASE} -- python/packages/ \
| grep '^python/packages/' \
| sed 's|^python/packages/||' \
| awk -F/ '{print $1}' \
| sort -u
# Root-level files (drive a root agent-framework entry if substantive)
git log --name-only --format='' ${LAST_RELEASED_TAG}..${RELEASE_BASE} \
-- python/pyproject.toml python/agent_framework_meta/ python/README.md \
2>/dev/null | grep -v '^$' | sort -u
```
**What "touched" means for bump purposes** (apply judgment, document in commit message):
| Change scope under `python/packages/<pkg>/` | Drives a bump? |
| --- | --- |
| Source code under `<pkg>/agent_framework_*` or the package's library tree | **Yes** — ship-affecting |
| `pyproject.toml` (deps, classifiers, extras, version metadata) | **Yes** — publishes new metadata |
| `README.md` substantive changes (env vars, install instructions, usage) | **Yes** — user-facing |
| `README.md` typo/wording-only | Judgment — usually no, but record under `### Changed` if you do bump |
| Tests only (`tests/`) | Usually no — test changes don't ship to PyPI consumers. Bump only if the test change reflects a real behavior change in the package |
| Package-local samples (alpha packages only, before promotion) | **Yes** for alpha; samples ship with the alpha package |
| Repo infra touching the package dir (CI config, lint config) | No — record under `- **tests**:`, `- **samples**:`, or `- **docs**:` instead |
Root `agent-framework` is touched when `python/pyproject.toml`, `python/agent_framework_meta/`, or root `README.md` substantive content changed.
### 4. Draft CHANGELOG entries (THIS DRIVES THE BUMP LIST)
Individual feature/fix PRs intentionally do not add CHANGELOG entries. During release preparation, derive this section
centrally from the merged PRs since the last released tag.
Locate `## [Unreleased]` and the top existing release header. INSERT a new section between them.
**New section structure:**
```markdown
## [<release-label>] - YYYY-MM-DD
### Added
- **agent-framework-<pkg>**: <subject> ([#NNNN](https://github.com/microsoft/agent-framework/pull/NNNN))
### Changed
- **agent-framework-<pkg>**: ...
### Fixed
- **agent-framework-<pkg>**: ...
```
`<release-label>` is the released-tier target if any released package is bumping (e.g. `1.7.0`), or the date stamp (e.g. `1.0.0b260528`) if the release is prerelease-only. Reflect the user's framing.
**Categorization heuristics (read commit subject + PR title/body):**
- **Added**: new public APIs, new packages, new samples, `feat(...)`, new re-exports from `agent_framework`, new capabilities surfaced from underlying SDKs
- **Changed**: metadata/classifier corrections, dependency upgrades, test infra changes, signature adjustments on existing APIs, renames, doc updates, lifecycle transitions
- **Fixed**: `fix(...)`, bug/crash/regression repairs, noise reduction (e.g. stop emitting spurious warnings), protocol-compliance fixes
- **Removed**: deprecations/deletions (use `[BREAKING]` prefix if breaking)
**Entry format:**
- `- **agent-framework-<pkg>**: <subject, grammar-normalized> ([#NNNN](https://github.com/microsoft/agent-framework/pull/NNNN))`
- Multiple packages touched by one PR: comma-separated bold names at the head: `- **agent-framework-core**, **agent-framework-foundry**: ...`
- Root package changes: use `- **agent-framework**: ...`
- Test/sample/repo infra: `- **tests**: ...`, `- **samples**: ...`, `- **docs**: ...` (these do not drive package bumps)
**Once entries are drafted, the set of `**agent-framework-<pkg>**` and root `**agent-framework**` mentions IS the bump list.** Anything not mentioned does not bump.
#### 4a. Reconcile mentions against the touched-package set (DO NOT SKIP)
Before moving on, prove that every ship-affecting touched package has at least one CHANGELOG entry. A package whose code changed but is missing from CHANGELOG will NOT bump — and the fix will not ship to PyPI consumers.
```bash
# 1. Touched ship-affecting packages and root package files (from step 3a)
TOUCHED_PACKAGES=$(git log --name-only --format='' ${LAST_RELEASED_TAG}..${RELEASE_BASE} -- python/packages/ \
| grep '^python/packages/' \
| sed 's|^python/packages/||' \
| awk -F/ '{print $1}' \
| sort -u)
ROOT_TOUCHED=$(git log --name-only --format='' ${LAST_RELEASED_TAG}..${RELEASE_BASE} \
-- python/pyproject.toml python/agent_framework_meta/ python/README.md \
2>/dev/null | grep -v '^$' | sort -u)
TOUCHED=$(
{
if [ -n "$TOUCHED_PACKAGES" ]; then echo "$TOUCHED_PACKAGES"; fi
if [ -n "$ROOT_TOUCHED" ]; then echo "agent-framework"; fi
} | sort -u
)
# 2. Packages mentioned in the new CHANGELOG section
# (Extract from the section you just drafted — adjust the awk range to the new section's bounds)
MENTIONED=$(awk '
/^## \[<release-label>\]/ { in_section=1; next }
in_section && /^## \[/ { exit }
in_section { print }
' python/CHANGELOG.md \
| grep -oE '\*\*agent-framework(-[a-z0-9_-]+)?\*\*' \
| sed 's/\*\*//g;s/^agent-framework-//' \
| sort -u)
# 3. Diff — anything in TOUCHED but missing from MENTIONED is a gap
comm -23 <(echo "$TOUCHED") <(echo "$MENTIONED")
```
For every gap, decide explicitly with the user:
- **Add a CHANGELOG entry and bump** — the default. Even small package changes (a dep upgrade, a metadata fix) deserve an entry under `### Changed`.
- **Intentionally skip** — only when the touched files are demonstrably non-shipping (e.g. comments-only, test infra). Note the skip in the commit message body so reviewers see the reasoning.
Package-name aliasing to watch for: directory `foundry_local` → package `agent-framework-foundry-local`; `github_copilot` → `agent-framework-github-copilot`; `azure-ai-search` → `agent-framework-azure-ai-search`. The directory name and the published PyPI name are not always identical — confirm both when reconciling.
**Footer reference links** (REQUIRED every release):
```bash
grep -n "^\[.*\]:" python/CHANGELOG.md | head -5
```
Two edits when a released-tier bump is in play:
1. Advance `[Unreleased]` compare base from `python-${OLD_RELEASED}...HEAD` to `python-${NEW_RELEASED}...HEAD`.
2. INSERT a new `[${NEW_RELEASED}]` line ABOVE the previous version's link: `[${NEW_RELEASED}]: https://github.com/microsoft/agent-framework/compare/python-${OLD_RELEASED}...python-${NEW_RELEASED}`
For prerelease-only releases (no released-tier bump this cycle), footer links don't change.
### 5. Apply bumps per tier
For each package in the bump list, choose the rule for its current tier. Use anchored `sed` (`^version = "..."$`) so only the project's own version matches. Use `sed -i.bak` plus backup cleanup for portable in-place edits across macOS/BSD sed and GNU sed.
**Released tier (per-package semver):**
```bash
# Example: openai goes 1.6.0 -> 1.6.1 (PATCH); core stays
sed -i.bak 's/^version = "1.6.0"$/version = "1.6.1"/' python/packages/openai/pyproject.toml
rm python/packages/openai/pyproject.toml.bak
View on GitHub