chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
---
|
||||
applyTo: 'python/**'
|
||||
---
|
||||
|
||||
See [AGENTS.md](../../AGENTS.md) for project structure and package documentation.
|
||||
Detailed conventions are in the agent skills under `.github/skills/`.
|
||||
|
||||
When changing the public root API surface (`agent_framework/__init__.py`), keep the lazy runtime export
|
||||
registry, explicit runtime `__all__`, and `agent_framework/__init__.pyi` synchronized. Runtime deprecation
|
||||
behavior for a public alias should live in the owning module, not as a special case in the root package.
|
||||
@@ -0,0 +1,361 @@
|
||||
---
|
||||
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}..origin/main -- 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
|
||||
|
||||
- **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
|
||||
```
|
||||
|
||||
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 origin/main
|
||||
```
|
||||
|
||||
### 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}..origin/main -- python/ ':!python/CHANGELOG.md'
|
||||
|
||||
# Per-commit package footprint
|
||||
for sha in $(git log --format='%H' ${LAST_RELEASED_TAG}..origin/main -- 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
|
||||
```
|
||||
|
||||
If the release ultimately tags from `upstream/main` but `origin/main` is behind, also run:
|
||||
|
||||
```bash
|
||||
git log --oneline ${LAST_RELEASED_TAG}..upstream/main -- python/ ':!python/CHANGELOG.md'
|
||||
```
|
||||
|
||||
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}..origin/main -- 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}..origin/main \
|
||||
-- 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)
|
||||
|
||||
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}..origin/main -- 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}..origin/main \
|
||||
-- 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
|
||||
```
|
||||
|
||||
**Root `agent-framework` (only when `core` bumps, OR for its own changes):**
|
||||
|
||||
```bash
|
||||
sed -i.bak "s/^version = \"${OLD_ROOT}\"$/version = \"${NEW_ROOT}\"/" python/pyproject.toml
|
||||
# AND keep the exact pin in sync with core
|
||||
sed -i.bak "s/agent-framework-core\\[all\\]==${OLD_CORE}/agent-framework-core[all]==${NEW_CORE}/" python/pyproject.toml
|
||||
rm python/pyproject.toml.bak
|
||||
```
|
||||
|
||||
**RC tier (counter increment):**
|
||||
|
||||
```bash
|
||||
# ag-ui goes 1.0.0rc3 -> 1.0.0rc4 only because it has a CHANGELOG entry
|
||||
sed -i.bak 's/^version = "1.0.0rc3"$/version = "1.0.0rc4"/' python/packages/ag-ui/pyproject.toml
|
||||
rm python/packages/ag-ui/pyproject.toml.bak
|
||||
```
|
||||
|
||||
**Alpha/Beta tier (new date stamp):**
|
||||
|
||||
```bash
|
||||
OLD_DATE=260521
|
||||
NEW_DATE=260528
|
||||
|
||||
# Only the packages with CHANGELOG entries get the new stamp.
|
||||
# Example: anthropic and bedrock had entries, others did not.
|
||||
for pkg in anthropic bedrock; do
|
||||
file="python/packages/$pkg/pyproject.toml"
|
||||
sed -i.bak "s/1\\.0\\.0b${OLD_DATE}/1.0.0b${NEW_DATE}/g" "$file"
|
||||
rm "${file}.bak"
|
||||
done
|
||||
```
|
||||
|
||||
If the user opts into a cohort-wide beta bump regardless of per-package changes, enumerate every beta package from `PACKAGE_STATUS.md` and stamp all of them. State that this is a cohort bump in the commit message.
|
||||
|
||||
Spot-check with `grep '^version' python/pyproject.toml python/packages/*/pyproject.toml | sort` before moving on.
|
||||
|
||||
### 6. Floor bound updates on `agent-framework-core`
|
||||
|
||||
Only relevant when `core` itself bumped this cycle. Two policies, pick one explicitly with the user:
|
||||
|
||||
- **Conservative (default)**: raise `agent-framework-core>=X.Y.Z` to the new core version on every non-core package that is ALSO bumping this cycle. Leaves packages-not-bumped at their existing floor.
|
||||
- **Strict per-upstream-doc**: only raise the floor on packages that actually consume a new core API introduced in the bump. This requires per-package code inspection. Use only when the user is comfortable letting `validate-dependency-bounds-test` (lower-resolution pass) catch any mistakes.
|
||||
|
||||
When raising a core floor, replace only the `>=OLD` half of the bound you intend to change:
|
||||
|
||||
```bash
|
||||
file="python/packages/<pkg>/pyproject.toml"
|
||||
sed -i.bak "s/agent-framework-core>=${OLD_CORE}/agent-framework-core>=${NEW_CORE}/" "$file"
|
||||
rm "${file}.bak"
|
||||
```
|
||||
|
||||
If `core` did not bump this cycle, do not touch floors.
|
||||
|
||||
### 7. Validate
|
||||
|
||||
```bash
|
||||
cd python && uv run poe validate-dependency-bounds-test
|
||||
```
|
||||
|
||||
Must exit 0. This is the safety net for selective bumping: the lower-resolution pass catches floors set too low for code that depends on new APIs, and the upper pass catches caps that exclude installable versions. If it fails, the output names the offending bound — fix and re-run before committing. This step also regenerates `uv.lock` to match new bounds.
|
||||
|
||||
If only prereleases changed (no `core` bump, no floor changes), this validation is still required — `uv.lock` regeneration alone justifies the run.
|
||||
|
||||
### 8. Commit (expect hook retry)
|
||||
|
||||
The project's pre-commit hook runs `poe check` / `poe typing` / `uv lock` and may re-modify `uv.lock` on first commit attempt. Expected pattern — run twice:
|
||||
|
||||
```bash
|
||||
git add -u
|
||||
git commit -m "<message>" # may fail with "files were modified by this hook"
|
||||
git add -u # re-stage hook-generated changes
|
||||
git commit -m "<message>" # succeeds
|
||||
```
|
||||
|
||||
**Commit message format** — HEREDOC, no `Co-Authored-By`:
|
||||
|
||||
```bash
|
||||
git commit -m "$(cat <<'EOF'
|
||||
Bump Python package versions for <release-label> release
|
||||
|
||||
<One paragraph explaining:
|
||||
- which packages bumped and why (CHANGELOG-driven)
|
||||
- semver rationale for any released-tier bumps
|
||||
- whether a beta cohort bump was applied
|
||||
- whether core floors were raised, and under which policy>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
### 9. Push and report
|
||||
|
||||
```bash
|
||||
git push origin bump-py-ver-release-YYMMDD
|
||||
```
|
||||
|
||||
The push output includes a `Create a pull request for '<branch>' on GitHub by visiting: <URL>` line — surface that to the user.
|
||||
|
||||
## Pitfalls from past cycles — avoid repeating
|
||||
|
||||
- **Scoping from the wrong tag.** A release may have been tagged between bump cycles. Always compute `LAST_RELEASED_TAG` fresh — do NOT reuse the previous compare base. Symptom: dragging already-shipped PRs into the new CHANGELOG section.
|
||||
- **Bumping packages that had no changes.** This was the old lockstep default. Now: a package without a CHANGELOG entry does not bump. The two exceptions (root follows core, optional beta cohort bump) are explicit choices, not defaults.
|
||||
- **Touched package missing from CHANGELOG.** The inverse failure mode of the previous pitfall, and the dangerous one. If a PR landed code into `python/packages/foo/` but the new CHANGELOG section has no `**agent-framework-foo**:` line, then `foo` will NOT bump, and the fix sits in `main` but never reaches PyPI. Always run the touched-vs-mentioned reconciliation in step 4a before applying bumps.
|
||||
- **Directory name vs PyPI name confusion.** Some package dirs use underscores or split names that differ from the published PyPI name (e.g. `foundry_local` → `agent-framework-foundry-local`). When reconciling touched dirs against CHANGELOG mentions, normalize on the PyPI form.
|
||||
- **Forgetting root when core bumps.** Root `agent-framework` has `agent-framework-core[all]==EXACT_VERSION`. If `core` bumps and root does not, the pin is broken. Always bump them together and update the pin string.
|
||||
- **Renaming an existing section header.** When drafting a new release, do NOT rewrite an existing `## [X.Y.Z]` header to a new label — that wipes the historical section. Always INSERT a new section above.
|
||||
- **Forgetting footer reference links.** When a released-tier bump is in play, the `[Unreleased]` line and the new `[X.Y.Z]` link at the bottom MUST be updated. Heading links don't resolve without them.
|
||||
- **Wrong footer compare base.** The new `[X.Y.Z]` line compares from the PREVIOUS released tag, not from two releases ago.
|
||||
- **Timezone drift.** For alpha/beta date stamps, follow the `python-package-management` date-stamp rule;
|
||||
do not infer a local timezone from the user's current shell.
|
||||
- **`Co-Authored-By` trailer.** Never add it. Rewrite/amend if it slipped in.
|
||||
- **Stale inventory in this skill.** Always read `python/PACKAGE_STATUS.md` for the live tier map. Do not trust a hardcoded list.
|
||||
- **Divergent origin vs upstream.** If the release tags from `upstream/main` but `origin/main` is behind, check both — warn if they differ and offer to sync.
|
||||
- **`--pre` README cleanup on promotion.** When a package is promoted to `released` in this cycle, grep for `pip install agent-framework-<pkg> --pre` in READMEs and drop the `--pre` flag.
|
||||
- **RC counter inflation.** Do not increment `1.0.0rcN` without a CHANGELOG entry for that package. The counter tracks iterations, not calendar.
|
||||
|
||||
## References
|
||||
|
||||
- Package lifecycle and versioning source of truth: `python/.github/skills/python-package-management/SKILL.md`
|
||||
- Lifecycle source of truth: `python/PACKAGE_STATUS.md`
|
||||
- Validator: `python/scripts/dependencies/validate_dependency_bounds.py` (runs `lowest-direct` and `highest` resolution smoke tests; catches floors/caps that don't match the code)
|
||||
- Poe task definitions: `python/pyproject.toml` `[tool.poe.tasks]`
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
---
|
||||
name: pull-requests
|
||||
description: >
|
||||
Guidance for creating pull requests and handling PR review comments in the
|
||||
Agent Framework repository. Use this when writing a PR description (filling out
|
||||
the PR template) or when responding to and resolving review comments on an
|
||||
existing PR.
|
||||
---
|
||||
|
||||
# Pull Request Workflow
|
||||
|
||||
This skill covers two tasks: (1) writing a high-quality PR description, and
|
||||
(2) handling review comments on an existing PR.
|
||||
|
||||
## 1. Writing the PR description
|
||||
|
||||
Always follow the repository PR template at
|
||||
[`.github/pull_request_template.md`](../../../../.github/pull_request_template.md). Keep its
|
||||
exact structure and headings. Fill every section:
|
||||
|
||||
### `### Motivation & Context`
|
||||
Explain *why* the change is needed: the problem it solves and the scenario it
|
||||
contributes to. Describe the net change relative to `main` — this is implied, so
|
||||
do **not** spell out "vs main" explicitly.
|
||||
|
||||
### `### Description & Review Guide`
|
||||
Describe the changes, the overall approach, and the design. Answer the three
|
||||
prompts:
|
||||
- **What are the major changes?**
|
||||
- **What is the impact of these changes?**
|
||||
- **What do you want reviewers to focus on?** — This item is for **human
|
||||
reviewers only**. Automated/AI reviewers must ignore it and review the entire
|
||||
change rather than narrowing scope to it.
|
||||
|
||||
### `### Related Issue`
|
||||
Link the issue the PR fixes using a GitHub closing keyword (`Fixes #123` /
|
||||
`Closes #123`) so it closes automatically on merge. A PR with no linked issue may
|
||||
be closed regardless of how valid the change is. Before opening, confirm there is
|
||||
no other open PR for the same issue; if there is, explain how this PR differs.
|
||||
|
||||
### `### Contribution Checklist`
|
||||
Check every item that applies. For the breaking-change item:
|
||||
- Leave **"This is not a breaking change."** checked for the common case.
|
||||
- If the change **is** breaking, add the `breaking change` label **or** put
|
||||
`[BREAKING]` in the title prefix, before or after a language prefix such as
|
||||
`Python:` or `.NET:` — workflows keep the label and the title prefix in sync
|
||||
automatically (see `.github/workflows/label-title-prefix.yml` and
|
||||
`.github/workflows/label-pr.yml`).
|
||||
|
||||
### Do not
|
||||
- Do **not** add ad-hoc sections such as "Validation" or "Tests run"; CI/CD and
|
||||
the checklist already cover validation status.
|
||||
- Do **not** remove or reorder the template's headings.
|
||||
|
||||
### Creating the PR
|
||||
Open new PRs as **drafts** until they are ready for review. Example:
|
||||
|
||||
```bash
|
||||
gh pr create --repo microsoft/agent-framework --base main \
|
||||
--head <your-fork-owner>:<branch> --draft \
|
||||
--title "<concise title>" --body "<body following the template>"
|
||||
```
|
||||
|
||||
## 2. Handling review comments
|
||||
|
||||
When a PR receives review comments, follow this sequence — **do not start editing
|
||||
code before the user has reviewed the plan**:
|
||||
|
||||
1. **Review the comments.** Read every review comment and thread on the PR,
|
||||
including inline code comments and general review summaries.
|
||||
2. **Make a plan.** Produce a concrete plan describing how each comment will be
|
||||
addressed (or why it should not be, with reasoning).
|
||||
3. **Let the user review the plan.** Present the plan and wait for the user's
|
||||
approval or adjustments before implementing anything.
|
||||
4. **Implement.** Make the agreed changes.
|
||||
5. **Reply to every comment.** Add a reply to **all** comments explaining how it
|
||||
was addressed (or the agreed outcome) — leave none unanswered.
|
||||
6. **Resolve resolved threads.** Mark a review thread as resolved only when the
|
||||
comment has actually been addressed.
|
||||
|
||||
### Useful commands
|
||||
|
||||
List review comments and threads:
|
||||
|
||||
```bash
|
||||
# Inline review comments
|
||||
gh api repos/{owner}/{repo}/pulls/{pr}/comments
|
||||
|
||||
# Review threads with resolution state (GraphQL)
|
||||
gh api graphql -f query='
|
||||
query($owner:String!,$repo:String!,$pr:Int!){
|
||||
repository(owner:$owner,name:$repo){
|
||||
pullRequest(number:$pr){
|
||||
reviewThreads(first:100){
|
||||
nodes{ id isResolved comments(first:50){ nodes{ id body author{login} } } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}' -F owner={owner} -F repo={repo} -F pr={pr}
|
||||
```
|
||||
|
||||
Reply to an inline review comment:
|
||||
|
||||
```bash
|
||||
gh api repos/{owner}/{repo}/pulls/{pr}/comments/{comment_id}/replies \
|
||||
-f body="Addressed in <commit>: <explanation>"
|
||||
```
|
||||
|
||||
Resolve a review thread (needs the thread node id from the GraphQL query above):
|
||||
|
||||
```bash
|
||||
gh api graphql -f query='
|
||||
mutation($threadId:ID!){
|
||||
resolveReviewThread(input:{threadId:$threadId}){ thread{ isResolved } }
|
||||
}' -F threadId={thread_id}
|
||||
```
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
---
|
||||
name: python-code-quality
|
||||
description: >
|
||||
Code quality checks, linting, formatting, and type checking commands for the
|
||||
Agent Framework Python codebase. Use this when running checks, fixing lint
|
||||
errors, or troubleshooting CI failures.
|
||||
---
|
||||
|
||||
# Python Code Quality
|
||||
|
||||
## Quick Commands
|
||||
|
||||
All commands run from the `python/` directory:
|
||||
|
||||
```bash
|
||||
# Syntax formatting + checks (parallel across packages by default)
|
||||
uv run poe syntax
|
||||
uv run poe syntax -P core
|
||||
uv run poe syntax -F # Format only
|
||||
uv run poe syntax -C # Check only
|
||||
uv run poe syntax -S # Samples only
|
||||
|
||||
# Type checking
|
||||
#
|
||||
# Division of labor (see "Type checking architecture" below):
|
||||
# - Pyright (strict) is the source-code type checker.
|
||||
# - Pyright (relaxed `basic`), mypy, pyrefly, ty, zuban all check the TESTS;
|
||||
# pyright/pyrefly/ty also check the SAMPLES (mypy/zuban skip script-style samples).
|
||||
uv run poe pyright # Pyright (strict) over SOURCE, fan-out across packages
|
||||
uv run poe pyright -P core
|
||||
uv run poe pyright -A
|
||||
uv run poe test-typing # mypy + pyrefly + ty + zuban + pyright over each package's TESTS
|
||||
uv run poe test-typing -P core
|
||||
uv run poe test-typing -S # samples (pyrefly + ty + pyright)
|
||||
uv run poe test-typing -P core --checker mypy # narrow to one checker (repeatable)
|
||||
uv run poe test-typing -P core --checker pyright # relaxed pyright over the tests
|
||||
uv run poe mypy # alias: MyPy over the tests only
|
||||
uv run poe mypy -P core
|
||||
uv run poe typing # Pyright (source) + the tests checkers
|
||||
uv run poe typing -P core
|
||||
uv run poe typing -A
|
||||
|
||||
# All package-level checks in parallel (syntax + pyright)
|
||||
uv run poe check-packages
|
||||
|
||||
# Full check (packages + samples + tests + markdown)
|
||||
uv run poe check
|
||||
uv run poe check -P core
|
||||
|
||||
# Samples only
|
||||
uv run poe check -S
|
||||
uv run poe pyright -S
|
||||
|
||||
# Markdown code blocks
|
||||
uv run poe markdown-code-lint
|
||||
```
|
||||
|
||||
## Pre-commit Hooks (prek)
|
||||
|
||||
Prek hooks run automatically on commit. They stay lightweight and only check
|
||||
changed files.
|
||||
|
||||
```bash
|
||||
# Install hooks
|
||||
uv run poe prek-install
|
||||
|
||||
# Run all hooks manually
|
||||
uv run prek run -a
|
||||
|
||||
# Run on last commit
|
||||
uv run prek run --last-commit
|
||||
```
|
||||
|
||||
They run changed-package syntax formatting/checking, markdown code lint only
|
||||
when markdown files change, and sample syntax lint/pyright only when files
|
||||
under `samples/` change.
|
||||
They intentionally do not run workspace `pyright` or `mypy` by default.
|
||||
|
||||
## Type checking architecture
|
||||
|
||||
Following the "too many type checkers" approach, type checkers are split by target:
|
||||
|
||||
| Target | Checker(s) | Mode | Config |
|
||||
|--------|-----------|------|--------|
|
||||
| Source (`agent_framework*`) | **pyright** | strict | `[tool.pyright]` in `pyproject.toml` |
|
||||
| Tests | pyright, mypy, pyrefly, ty, zuban | relaxed/basic | `pyrightconfig.tests.json`, `[tool.mypy]`, `pyrefly.toml`, `ty` rules |
|
||||
| Samples | pyright, pyrefly, ty | basic | `pyrightconfig.samples.json`, `pyrefly.samples.toml`, `ty.samples.toml` |
|
||||
|
||||
- **Pyright is the only *strict* source-code checker**, and it ALSO runs in a relaxed
|
||||
`basic` profile over the tests and samples (so the surfaces customers copy from are
|
||||
validated by every checker, including pyright). MyPy was removed from source; its
|
||||
`[tool.mypy]` block is now a *relaxed* profile used only for tests/samples.
|
||||
- The extra checkers run over tests/samples because those exercise the public API the way
|
||||
users do. The profile is intentionally relaxed (private access allowed, untyped test
|
||||
bodies allowed) so authors aren't forced into ugly over-annotation.
|
||||
- **Gating checkers** are `pyright`, `mypy`, `pyrefly`, `ty`, and `zuban` — all five run by
|
||||
default and gate CI. `zuban` is the strictest of the mypy-compatible pair, so the same
|
||||
`[tool.mypy]` config yields more findings; suppress zuban-only friction with shared
|
||||
`# type: ignore[code]`. Suppress relaxed-pyright friction with `# pyright: ignore[rule]`.
|
||||
- **Samples** add `pyright` to `pyrefly` + `ty` — mypy/zuban can't resolve script-style
|
||||
sample layouts (numeric-prefixed dirs, duplicate `main.py`), but pyright handles them.
|
||||
- The strict source-pyright (`[tool.pyright]`) enforces `reportUnnecessaryTypeIgnoreComment`
|
||||
and excludes tests/samples; the relaxed test/sample pyright configs do not flag unnecessary
|
||||
ignores.
|
||||
|
||||
## Ruff Configuration
|
||||
|
||||
- Line length: 120
|
||||
- Target: Python 3.10+
|
||||
- Auto-fix enabled
|
||||
- Rules: ASYNC, B, CPY, D, E, ERA, F, FIX, I, INP, ISC, Q, RET, RSE, RUF, SIM, T20, TD, W, T100, S
|
||||
- Scripts directory is excluded from checks
|
||||
|
||||
## Pyright Configuration
|
||||
|
||||
- **Source**: strict mode (`[tool.pyright]`), `reportUnnecessaryTypeIgnoreComment = "error"`,
|
||||
excludes tests, samples, .venv, packages/devui/frontend.
|
||||
- **Tests**: relaxed `basic` profile (`pyrightconfig.tests.json`) — private import/usage and
|
||||
not-required TypedDict access allowed; runs as the `pyright` checker in `test-typing`.
|
||||
- **Samples**: relaxed `basic` profile (`pyrightconfig.samples.json`, with a py310 variant) —
|
||||
runs as the `pyright` checker in `test-typing -S`.
|
||||
|
||||
## Parallel Execution
|
||||
|
||||
The task runner (`scripts/task_runner.py`) executes the cross-product of
|
||||
(package × task) in parallel using ThreadPoolExecutor. Single items run
|
||||
in-process with streaming output.
|
||||
|
||||
## CI Workflow
|
||||
|
||||
CI splits into 4 parallel jobs:
|
||||
1. **Pre-commit hooks** — lightweight hooks (SKIP=poe-check)
|
||||
2. **Package checks** — syntax/pyright (source) via check-packages
|
||||
3. **Samples & markdown** — `check -S` plus `markdown-code-lint`
|
||||
4. **Test Typing** — change-detected mypy/pyrefly/ty over tests (`ci-test-typing`)
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
---
|
||||
name: python-development
|
||||
description: >
|
||||
Coding standards, conventions, and patterns for developing Python code in the
|
||||
Agent Framework repository. Use this when writing or modifying Python source
|
||||
files in the python/ directory.
|
||||
---
|
||||
|
||||
# Python Development Standards
|
||||
|
||||
## File Header
|
||||
|
||||
Every `.py` file must start with:
|
||||
|
||||
```python
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
```
|
||||
|
||||
## Type Annotations
|
||||
|
||||
- Always specify return types and parameter types
|
||||
- Use `Type | None` instead of `Optional[Type]`
|
||||
- Use `from __future__ import annotations` to enable postponed evaluation
|
||||
- Use suffix `T` for TypeVar names: `ChatResponseT = TypeVar("ChatResponseT", bound=ChatResponse)`
|
||||
- Use `Mapping` instead of `MutableMapping` for read-only input parameters
|
||||
- Prefer `# type: ignore[...]` over unnecessary casts, or `isinstance` checks, when these are internally called and executed methods
|
||||
But make sure the ignore is specific for both mypy and pyright so that we don't miss other mistakes
|
||||
- Internal private helpers may be used across `agent_framework*` modules when intentional; use a targeted
|
||||
`# pyright: ignore[reportPrivateUsage]` instead of making the helper public just to satisfy pyright.
|
||||
- Do not add trivial pass-through or one-line helper functions solely to appease typing. Prefer targeted ignores,
|
||||
casts, or clearer annotations over adding runtime overhead without a design benefit.
|
||||
|
||||
## Function Parameters
|
||||
|
||||
- Positional parameters: up to 3 fully expected parameters
|
||||
- Use keyword-only arguments (after `*`) for optional parameters
|
||||
- Provide string-based overrides to avoid requiring extra imports:
|
||||
|
||||
```python
|
||||
def create_agent(name: str, tool_mode: Literal['auto', 'required', 'none'] | ChatToolMode) -> Agent:
|
||||
if isinstance(tool_mode, str):
|
||||
tool_mode = ChatToolMode(tool_mode)
|
||||
```
|
||||
|
||||
- Avoid shadowing built-ins (use `next_handler` instead of `next`)
|
||||
- Avoid `**kwargs` unless needed for subclass extensibility; prefer named parameters
|
||||
|
||||
## Docstrings
|
||||
|
||||
Use Google-style docstrings for all public APIs:
|
||||
|
||||
```python
|
||||
def equal(arg1: str, arg2: str) -> bool:
|
||||
"""Compares two strings and returns True if they are the same.
|
||||
|
||||
Args:
|
||||
arg1: The first string to compare.
|
||||
arg2: The second string to compare.
|
||||
|
||||
Returns:
|
||||
True if the strings are the same, False otherwise.
|
||||
|
||||
Raises:
|
||||
ValueError: If one of the strings is empty.
|
||||
"""
|
||||
```
|
||||
|
||||
- Always document Agent Framework specific exceptions
|
||||
- Explicitly use `Keyword Args` when applicable
|
||||
- Only document standard Python exceptions when the condition is non-obvious
|
||||
|
||||
## Import Structure
|
||||
|
||||
```python
|
||||
# Core
|
||||
from agent_framework import Agent, Message, tool
|
||||
|
||||
# Components
|
||||
from agent_framework.observability import enable_sensitive_telemetry
|
||||
|
||||
# Connectors (lazy-loaded)
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
```
|
||||
|
||||
## Public API and Exports
|
||||
|
||||
In `__init__.py` files that define package-level public APIs, use direct re-export imports plus an explicit
|
||||
`__all__`. Avoid identity aliases like `from ._agents import Agent as Agent`, and avoid
|
||||
`from module import *`.
|
||||
|
||||
Do not define `__all__` in internal non-`__init__.py` modules. Exception: modules intentionally exposed as a
|
||||
public import surface (for example, `agent_framework.observability`) should define `__all__`.
|
||||
|
||||
```python
|
||||
__all__ = ["Agent", "Message", "ChatResponse"]
|
||||
|
||||
from ._agents import Agent
|
||||
from ._types import Message, ChatResponse
|
||||
```
|
||||
|
||||
Special case: the root `agent_framework/__init__.py` uses lazy runtime exports. For root public API changes:
|
||||
- Add the symbol to `_LAZY_MODULE_EXPORTS` and keep `_LAZY_EXPORTS` derived from it.
|
||||
- Keep the explicit runtime `__all__` synchronized; it is still required for `from agent_framework import *`.
|
||||
- Add the same public symbol to `agent_framework/__init__.pyi` so pyright, mypy, and editors see the typed surface.
|
||||
- Put runtime deprecation behavior in the owning module via that module's `__getattr__`; avoid root-level
|
||||
special-case branches for individual deprecated exports.
|
||||
- Identity aliases are appropriate in `.pyi` stubs because they mark re-exported names for type checkers; avoid them
|
||||
in runtime `.py` modules unless there is a specific compatibility reason.
|
||||
|
||||
## Performance Guidelines
|
||||
|
||||
- Cache expensive computations (e.g., JSON schema generation)
|
||||
- Prefer `match/case` on `.type` attribute over `isinstance()` in hot paths
|
||||
- Avoid redundant serialization — compute once, reuse
|
||||
|
||||
## Style
|
||||
|
||||
- Line length: 120 characters
|
||||
- Format only files you changed, not the entire codebase
|
||||
- Prefer attributes over inheritance when parameters are mostly the same
|
||||
- Async by default — assume everything is asynchronous
|
||||
|
||||
## Naming Conventions for Connectors
|
||||
|
||||
- `_prepare_<object>_for_<purpose>` for methods that prepare data for external services
|
||||
- `_parse_<object>_from_<source>` for methods that process data from external services
|
||||
@@ -0,0 +1,236 @@
|
||||
---
|
||||
name: python-feature-lifecycle
|
||||
description: >
|
||||
Guidance for package and feature lifecycle in the Agent Framework Python
|
||||
codebase, including stage meanings, feature-stage decorators, feature enums,
|
||||
and how to move APIs from one stage to the next.
|
||||
---
|
||||
|
||||
# Python Feature Lifecycle
|
||||
|
||||
## Two lifecycle levels
|
||||
|
||||
Agent Framework uses lifecycle at two different levels:
|
||||
|
||||
1. **Package lifecycle** — the maturity of the package as a whole
|
||||
2. **Feature lifecycle** — the maturity of a specific API or feature inside that package
|
||||
|
||||
These are related, but they are **not the same thing**.
|
||||
|
||||
- The **package stage is the default** for everything in the package.
|
||||
- **Feature-stage decorators are only for exceptions** when a feature is behind the package's default stage.
|
||||
- Do **not** decorate every class or function just because the package is experimental or release candidate.
|
||||
|
||||
### Important default
|
||||
|
||||
If a package is still in **beta / experimental preview**, all public APIs in that package are experimental by default.
|
||||
|
||||
- Do **not** add `@experimental(...)` everywhere in that package.
|
||||
- The package stage already communicates that default.
|
||||
|
||||
Once a package moves forward, you can keep individual features behind:
|
||||
|
||||
- If a package moves to **release candidate**, a feature may remain **experimental**
|
||||
- If a package moves to **released / GA**, a feature may remain **experimental** or **release candidate**
|
||||
|
||||
That is the main use case for feature-stage decorators.
|
||||
|
||||
## The four stages
|
||||
|
||||
### 1. Experimental
|
||||
|
||||
Use for features that are still unstable and may change or be removed without notice.
|
||||
|
||||
Feature-level code pattern:
|
||||
|
||||
```python
|
||||
from ._feature_stage import ExperimentalFeature, experimental
|
||||
|
||||
|
||||
@experimental(feature_id=ExperimentalFeature.MY_FEATURE)
|
||||
class MyFeature:
|
||||
...
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
- Adds an experimental warning block to the docstring
|
||||
- Records feature metadata on the decorated object
|
||||
- Emits a runtime warning the first time the feature is used (once per feature by default)
|
||||
|
||||
Enum setup:
|
||||
|
||||
- Add an all-caps member to `ExperimentalFeature`
|
||||
- Reuse the same feature ID across all APIs that belong to the same conceptual feature
|
||||
|
||||
### 2. Release candidate
|
||||
|
||||
Use for features that are nearly stable but may still receive small refinements before GA.
|
||||
|
||||
Feature-level code pattern:
|
||||
|
||||
```python
|
||||
from ._feature_stage import ReleaseCandidateFeature, release_candidate
|
||||
|
||||
|
||||
@release_candidate(feature_id=ReleaseCandidateFeature.MY_FEATURE)
|
||||
class MyFeature:
|
||||
...
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
- Adds a release-candidate note to the docstring
|
||||
- Records feature metadata on the decorated object
|
||||
- Does **not** emit the experimental warning
|
||||
|
||||
Enum setup:
|
||||
|
||||
- Add an all-caps member to `ReleaseCandidateFeature`
|
||||
|
||||
### 3. Released
|
||||
|
||||
Use for stable GA APIs.
|
||||
|
||||
Code pattern:
|
||||
|
||||
- **No feature-stage decorator**
|
||||
- **No entry** in `ExperimentalFeature`
|
||||
- **No entry** in `ReleaseCandidateFeature`
|
||||
|
||||
If a feature is fully released, remove any stage-specific feature annotation.
|
||||
|
||||
### 4. Deprecated
|
||||
|
||||
Use for APIs that still exist but should not be used for new code.
|
||||
|
||||
Code pattern:
|
||||
|
||||
```python
|
||||
import sys
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
from warnings import deprecated # type: ignore # pragma: no cover
|
||||
else:
|
||||
from typing_extensions import deprecated # type: ignore # pragma: no cover
|
||||
|
||||
|
||||
@deprecated("MyOldFeature is deprecated. Use MyNewFeature instead.")
|
||||
class MyOldFeature:
|
||||
...
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
- Uses the repository's version-conditional deprecation import pattern
|
||||
- Should describe what to use instead
|
||||
|
||||
Deprecated APIs should not also carry feature-stage decorators.
|
||||
|
||||
## Expected decorators by stage
|
||||
|
||||
| Feature stage | Expected annotation |
|
||||
| --- | --- |
|
||||
| Experimental | `@experimental(feature_id=ExperimentalFeature.X)` |
|
||||
| Release candidate | `@release_candidate(feature_id=ReleaseCandidateFeature.X)` |
|
||||
| Released | No feature-stage decorator |
|
||||
| Deprecated | `@deprecated("...")` |
|
||||
|
||||
## Feature enums
|
||||
|
||||
The feature enums are the inventory of currently staged features:
|
||||
|
||||
- `ExperimentalFeature`
|
||||
- `ReleaseCandidateFeature`
|
||||
|
||||
Guidance:
|
||||
|
||||
- Use one enum member per conceptual feature, not per class
|
||||
- Ideally, an ADR already defines the overall feature boundary and therefore the feature ID that staged APIs for that feature should reuse
|
||||
- Keep feature IDs all caps
|
||||
- Reuse the same member across related APIs for the same feature
|
||||
- Remove enum members when the feature no longer belongs to that stage
|
||||
- Treat these enums as **current-stage inventories**, not as a stable consumer introspection API
|
||||
|
||||
Minimal consumer guidance:
|
||||
|
||||
- Treat `__feature_stage__` and `__feature_id__` as optional staged metadata, not as stable contracts
|
||||
- Use `getattr(obj, "__feature_stage__", None)` and `getattr(obj, "__feature_id__", None)` rather than direct attribute access
|
||||
- Treat missing metadata as "no explicit feature-stage annotation"
|
||||
- For warning filters while a feature is staged, match the literal feature ID string
|
||||
- Do **not** rely on `ExperimentalFeature.X`, `ReleaseCandidateFeature.X`, or the continued presence of `__feature_id__` after a feature moves stages or is released
|
||||
|
||||
For consumers, the enums are also re-exported from `agent_framework`.
|
||||
|
||||
For internal implementation code inside `agent_framework`, continue to import the enums and decorators from `._feature_stage`.
|
||||
|
||||
## Package stage vs feature stage
|
||||
|
||||
Use the following rules:
|
||||
|
||||
### Package is experimental / beta
|
||||
|
||||
- All public APIs are experimental by default
|
||||
- Do **not** add feature-stage decorators just to restate that
|
||||
- Only introduce feature-level annotations later if the package advances first
|
||||
|
||||
### Package is release candidate
|
||||
|
||||
- All public APIs are RC by default
|
||||
- Do **not** decorate everything
|
||||
- Add `@experimental(...)` only for features that are intentionally still behind the package
|
||||
|
||||
### Package is released / GA
|
||||
|
||||
- All public APIs are released by default
|
||||
- Add `@experimental(...)` or `@release_candidate(...)` only for features still being held back
|
||||
|
||||
## Moving a feature from one stage to the next
|
||||
|
||||
### Experimental -> Release candidate
|
||||
|
||||
1. Move the feature ID from `ExperimentalFeature` to `ReleaseCandidateFeature`
|
||||
2. Replace `@experimental(...)` with `@release_candidate(...)`
|
||||
3. Update any tests or docs that mention the old stage
|
||||
|
||||
### Experimental -> Released
|
||||
|
||||
1. Remove `@experimental(...)`
|
||||
2. Remove the feature from `ExperimentalFeature`
|
||||
3. Do not add a replacement feature-stage decorator
|
||||
|
||||
### Release candidate -> Released
|
||||
|
||||
1. Remove `@release_candidate(...)`
|
||||
2. Remove the feature from `ReleaseCandidateFeature`
|
||||
3. Leave the API undecorated
|
||||
|
||||
### Any stage -> Deprecated
|
||||
|
||||
1. Remove any feature-stage decorator
|
||||
2. Remove the feature from the stage enum
|
||||
3. Add `@deprecated("...")`
|
||||
4. Update docs/tests to reflect the replacement path
|
||||
|
||||
## Promotion guidance
|
||||
|
||||
Features do **not** have to pass through every stage.
|
||||
|
||||
- It is usually a good idea to move features in order when that reflects reality
|
||||
- But it is completely acceptable to go **experimental -> released**
|
||||
- Do **not** force a feature through release candidate if there is no real RC period
|
||||
|
||||
Likewise, when a package advances, do not automatically move every feature with it.
|
||||
|
||||
- Promote features based on actual readiness
|
||||
- Keep lagging features explicitly marked only when they are behind the package default
|
||||
|
||||
## Practical rules of thumb
|
||||
|
||||
- **Package default first, feature exceptions second**
|
||||
- **Do not decorate everything in preview packages**
|
||||
- **Do not double-annotate members of an already-staged class**
|
||||
- **Use enums only for currently staged features**
|
||||
- **Do not treat stage enums as a compatibility contract**
|
||||
- **Treat `__feature_stage__` and `__feature_id__` as optional metadata; use `getattr`**
|
||||
- **Remove stage annotations once a feature is released or deprecated**
|
||||
@@ -0,0 +1,252 @@
|
||||
---
|
||||
name: python-package-management
|
||||
description: >
|
||||
Guide for managing packages in the Agent Framework Python monorepo, including
|
||||
creating new connector packages, versioning, and the lazy-loading pattern.
|
||||
Use this when adding, modifying, or releasing packages.
|
||||
---
|
||||
|
||||
# Python Package Management
|
||||
|
||||
## Monorepo Structure
|
||||
|
||||
```
|
||||
python/
|
||||
├── pyproject.toml # Root package (agent-framework)
|
||||
├── packages/
|
||||
│ ├── core/ # agent-framework-core (main package)
|
||||
│ ├── foundry/ # agent-framework-foundry
|
||||
│ ├── anthropic/ # agent-framework-anthropic
|
||||
│ └── ... # Other connector packages
|
||||
```
|
||||
|
||||
- `agent-framework-core` contains core abstractions and OpenAI/Azure OpenAI built-in
|
||||
- Provider packages extend core with specific integrations
|
||||
- Root `agent-framework` depends on `agent-framework-core[all]`
|
||||
|
||||
## Dependency Management
|
||||
|
||||
Uses [uv](https://github.com/astral-sh/uv) for dependency management and
|
||||
[poethepoet](https://github.com/nat-n/poethepoet) for task automation.
|
||||
|
||||
```bash
|
||||
# Full setup (venv + install + prek hooks)
|
||||
uv run poe setup
|
||||
|
||||
# Install dependencies from lockfile (frozen resolution with prerelease policy)
|
||||
uv run poe install
|
||||
|
||||
# Create venv with specific Python version
|
||||
uv run poe venv --python 3.12
|
||||
|
||||
# Intentionally upgrade a specific dependency to reduce lockfile conflicts
|
||||
uv lock --upgrade-package <dependency-name> && uv run poe install
|
||||
|
||||
# Refresh exact development dependency-group pins, lockfile, and validation in one run
|
||||
uv run poe upgrade-dev-dependencies
|
||||
|
||||
# First, run workspace-wide lower/upper compatibility gates
|
||||
uv run poe validate-dependency-bounds-test
|
||||
# Defaults to --package "*"; pass a package to scope test mode
|
||||
uv run poe validate-dependency-bounds-test --package core
|
||||
|
||||
# Then expand bounds for one dependency in the target package
|
||||
uv run poe validate-dependency-bounds-project --mode both --package core --dependency "<dependency-name>"
|
||||
|
||||
# Repo-wide automation can reuse the same task
|
||||
uv run poe validate-dependency-bounds-project --mode upper --package "*"
|
||||
|
||||
# Add a dependency to one project and run both validators for that project/dependency
|
||||
uv run poe add-dependency-and-validate-bounds --package core --dependency "<dependency-spec>"
|
||||
```
|
||||
|
||||
### Dependency Bound Notes
|
||||
|
||||
- Stable dependencies (`>=1.0`) should typically be bounded as `>=<known-good>,<next-major>`.
|
||||
- Prerelease (`dev`/`a`/`b`/`rc`) and `<1.0` dependencies should use hard bounds with an explicit upper cap (avoid open-ended ranges).
|
||||
- For `<1.0` dependencies, prefer the broadest validated range the package can really support. That may be a patch line, a minor line, or multiple minor lines when checks/tests show the broader lane is compatible.
|
||||
- Prefer supporting multiple majors when practical; if APIs diverge across supported majors, use version-conditional imports/paths.
|
||||
- For dependency changes, run workspace-wide bound gates first, then `validate-dependency-bounds-project --mode both` for the target package/dependency to keep minimum and maximum constraints current. The same task can also drive repo-wide upper-bound automation by using `--package "*"` and omitting `--dependency`.
|
||||
- Prefer targeted lock updates with `uv lock --upgrade-package <dependency-name>` to reduce `uv.lock` merge conflicts.
|
||||
- Use `add-dependency-and-validate-bounds` for package-scoped dependency additions plus bound validation in one command.
|
||||
- Keep shared tooling and source/type-check support in the root or package `dev` group. Put package-specific test
|
||||
fixtures in a `test` group, and use a feature-named group for local-only executable dependencies that cannot be
|
||||
expressed in published runtime metadata.
|
||||
- Use `upgrade-dev-dependencies` for repo-wide development dependency refreshes; it repins exact dependencies
|
||||
across development groups, refreshes `uv.lock`, and reruns `check`, `typing`, and `test`.
|
||||
|
||||
## Lazy Loading Pattern
|
||||
|
||||
### Root core API
|
||||
|
||||
The root `agent_framework` package is a lazy public API surface:
|
||||
|
||||
- Runtime exports live in `packages/core/agent_framework/__init__.py`.
|
||||
- Typing/editor exports live in `packages/core/agent_framework/__init__.pyi`.
|
||||
- Add or move root exports in `_LAZY_MODULE_EXPORTS`, keep the explicit runtime `__all__` in sync, and add the same
|
||||
symbol to the `.pyi` file.
|
||||
- Keep deprecation behavior in the owning module (for example, a module-level `__getattr__` that warns and returns
|
||||
the deprecated alias). Do not add one-off deprecated-symbol branches to root `__getattr__`.
|
||||
- Validate root API changes with `uv run poe syntax -P core`, `uv run poe pyright -P core`, and import smoke tests
|
||||
for both `from agent_framework import <symbol>` and `from agent_framework import *`.
|
||||
|
||||
### Provider namespaces
|
||||
|
||||
Provider folders in core use `__getattr__` to lazy load from connector packages:
|
||||
|
||||
```python
|
||||
# In agent_framework/foundry/__init__.py
|
||||
_IMPORTS: dict[str, tuple[str, str]] = {
|
||||
"FoundryChatClient": ("agent_framework_foundry", "agent-framework-foundry"),
|
||||
}
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
if name in _IMPORTS:
|
||||
import_path, package_name = _IMPORTS[name]
|
||||
try:
|
||||
return getattr(importlib.import_module(import_path), name)
|
||||
except ModuleNotFoundError as exc:
|
||||
raise ModuleNotFoundError(
|
||||
f"The package {package_name} is required to use `{name}`. "
|
||||
f"Install it with: pip install {package_name}"
|
||||
) from exc
|
||||
```
|
||||
|
||||
## Adding a New Connector Package
|
||||
|
||||
**Important:** Do not create a new package unless approved by the core team.
|
||||
|
||||
Every new package starts as `alpha`.
|
||||
|
||||
### Alpha package checklist
|
||||
|
||||
1. Create directory under `packages/` (e.g., `packages/my-connector/`)
|
||||
2. Add the package to `tool.uv.sources` in root `pyproject.toml`
|
||||
3. Set the package version to the alpha pattern: `1.0.0a<date>`
|
||||
4. Set the package classifier to `Development Status :: 3 - Alpha`
|
||||
5. Include samples inside the package (e.g., `packages/my-connector/samples/`)
|
||||
6. Do **NOT** add to `[all]` extra in `packages/core/pyproject.toml`
|
||||
7. Do **NOT** create lazy loading in core yet
|
||||
8. Add the package to `python/PACKAGE_STATUS.md` and keep that file updated when packages are added,
|
||||
removed, renamed, or promoted. If the package exposes individually staged APIs, keep the feature list
|
||||
there current too.
|
||||
|
||||
Recommended dependency workflow during connector implementation:
|
||||
|
||||
1. Add the dependency to the target package:
|
||||
`uv run poe add-dependency-to-project --package core --dependency "<dependency-spec>"`
|
||||
2. Implement connector code and tests.
|
||||
3. Validate dependency bounds for that package/dependency:
|
||||
`uv run poe validate-dependency-bounds-project --mode both --package core --dependency "<dependency-name>"`
|
||||
4. If the package has meaningful tests/checks that validate dependency compatibility, you can use the add + validation flow in one command:
|
||||
`uv run poe add-dependency-and-validate-bounds --package core --dependency "<dependency-spec>"`
|
||||
If compatibility checks are not in place yet, add the dependency first, then implement tests before running bound validation.
|
||||
|
||||
### Promotion path
|
||||
|
||||
Promotion work is not isolated to the package being promoted. If a promotion changes dependency
|
||||
metadata for downstream packages, also update the dependent packages' own versions so they publish
|
||||
new metadata alongside the promoted dependency bounds.
|
||||
Apply the internal package dependency update rules from the versioning section below during
|
||||
promotions as well as standalone version update work.
|
||||
|
||||
#### Alpha -> Beta
|
||||
|
||||
Move a package to `beta` when it is stable enough to be part of the main install surface.
|
||||
|
||||
1. Update the package version to the beta pattern: `1.0.0b<date>`
|
||||
2. Update the classifier to `Development Status :: 4 - Beta`
|
||||
3. Add the package to `[all]` in `packages/core/pyproject.toml`
|
||||
4. Move samples to the root `samples/` tree and remove package-local samples
|
||||
5. Create or update the relevant lazy-loading namespace in core when the package belongs under one
|
||||
6. Update `python/PACKAGE_STATUS.md`
|
||||
|
||||
After `alpha`, there should be no samples left inside a package folder.
|
||||
|
||||
#### Beta -> RC
|
||||
|
||||
Move a package to `rc` when its API is close to the final released shape.
|
||||
|
||||
1. Update the package version to the release-candidate pattern: `1.0.0rc<number>`
|
||||
2. Keep the classifier at `Development Status :: 4 - Beta` because PyPI does not have a separate
|
||||
release-candidate classifier
|
||||
3. Keep the package in `core[all]`
|
||||
4. Keep samples only in the root `samples/` tree
|
||||
5. Update `python/PACKAGE_STATUS.md` to show the package as `rc`
|
||||
|
||||
#### RC -> Released
|
||||
|
||||
Move a package to `released` when it no longer carries a prerelease qualifier.
|
||||
|
||||
1. Update the package version to the stable pattern: `1.0.0`
|
||||
2. Update the classifier to `Development Status :: 5 - Production/Stable`
|
||||
3. Keep the package in `core[all]`
|
||||
4. Keep samples only in the root `samples/` tree
|
||||
5. Update `python/PACKAGE_STATUS.md` to show the package as `released`
|
||||
6. Update all `README.md` files that install that package with
|
||||
`pip install agent-framework-... --pre` so they use `pip install agent-framework-...` without
|
||||
the `--pre` suffix
|
||||
|
||||
## Versioning
|
||||
|
||||
### Internal package dependency updates
|
||||
|
||||
- If package A depends on package B within this repository, only update package A's dependency
|
||||
declaration when the work on package B actually affects package A.
|
||||
- If package A does not need anything from the package B change, leave package A's dependency
|
||||
declaration unchanged.
|
||||
- If package A does need something from the package B change, update package A's dependency
|
||||
declaration to the version or versioning scheme that matches what package A now requires.
|
||||
- If package B is promoted to a different lifecycle stage, update package A's dependency
|
||||
declaration to the new versioning scheme for package B even when the only change is the stage
|
||||
transition itself.
|
||||
- Use this guidance both for ordinary version updates and for package promotion work.
|
||||
|
||||
- All non-core packages declare a lower bound on `agent-framework-core`
|
||||
- When core version bumps with breaking changes, update the lower bound in all packages
|
||||
- Non-core packages version independently; only raise core bound when using new core APIs
|
||||
- If promoting a package changes a dependent package's published dependency metadata, bump the
|
||||
dependent package's own version in the correct lifecycle pattern for its current stage
|
||||
- Lifecycle version patterns:
|
||||
- `alpha`: `1.0.0a<date>` where `<date>` is the current Pacific (US west coast) `YYMMDD`
|
||||
- `beta`: `1.0.0b<date>` where `<date>` is the current Pacific (US west coast) `YYMMDD`
|
||||
- `rc`: `1.0.0rc<number>` where `<number>` increments only when the package has changes
|
||||
- `released`: `X.Y.Z` using semver per package
|
||||
- For alpha/beta date stamps, use the current Pacific date as the cutoff, not UTC and not the user's local
|
||||
timezone. Same-Pacific-day re-cuts use a `.postN` suffix. Honor an explicit user-provided date over this
|
||||
default.
|
||||
- Keep the `Development Status` classifier in `pyproject.toml` aligned with the lifecycle stage:
|
||||
- `alpha` -> `Development Status :: 3 - Alpha`
|
||||
- `beta` -> `Development Status :: 4 - Beta`
|
||||
- `rc` -> `Development Status :: 4 - Beta`
|
||||
- `released` -> `Development Status :: 5 - Production/Stable`
|
||||
- See the PyPI classifier list for the available classifier values:
|
||||
`https://pypi.org/classifiers/`
|
||||
|
||||
## Installation Options
|
||||
|
||||
```bash
|
||||
pip install agent-framework-core # Core only
|
||||
pip install agent-framework-core[all] # Core + all connectors
|
||||
pip install agent-framework # Same as core[all]
|
||||
pip install agent-framework-foundry # Specific connector (pulls in core)
|
||||
```
|
||||
|
||||
## Maintaining Documentation
|
||||
|
||||
When changing a package, check if its `AGENTS.md` needs updates:
|
||||
- Adding/removing/renaming public classes or functions
|
||||
- Changing the package's purpose or architecture
|
||||
- Modifying import paths or usage patterns
|
||||
|
||||
Keep `python/PACKAGE_STATUS.md` updated when:
|
||||
- A package is added, removed, renamed, or promoted between lifecycle stages
|
||||
- A package starts or stops exposing individually staged experimental or release-candidate APIs
|
||||
|
||||
When a package adds, removes, or renames environment variables, update the related documentation in the same
|
||||
change:
|
||||
- The package's `README.md` for package-level configuration/env var guidance
|
||||
- `samples/README.md` if the package is included in `packages/core/pyproject.toml` `[all]` and the env var is
|
||||
part of the consolidated package env-var inventory
|
||||
- Any affected sample/package-local `.env.example`, `.env.template`, or sample README files when sample setup
|
||||
changes alongside the package
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
---
|
||||
name: python-testing
|
||||
description: >
|
||||
Guidelines for writing and running tests in the Agent Framework Python
|
||||
codebase. Use this when creating, modifying, or running tests.
|
||||
---
|
||||
|
||||
# Python Testing
|
||||
|
||||
We strive for at least 85% test coverage across the codebase, with a focus on core packages and critical paths. Tests should be fast, reliable, and maintainable.
|
||||
When adding new code, check that the relevant sections of the codebase are covered by tests, and add new tests as needed. When modifying existing code, update or add tests to cover the changes.
|
||||
We run tests in two stages, for a PR each commit is tested with unit tests only (using `-m "not integration"`), and the full suite including integration tests is run when merging.
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Run tests for all packages in parallel
|
||||
uv run poe test
|
||||
|
||||
# Run tests for a specific workspace package
|
||||
uv run poe test -P core
|
||||
|
||||
# Run all selected tests in a single pytest invocation
|
||||
uv run poe test -A
|
||||
|
||||
# With coverage
|
||||
uv run poe test -A -C
|
||||
uv run poe test -P core -C
|
||||
|
||||
# Run only unit tests (exclude integration tests)
|
||||
uv run poe test -A -m "not integration"
|
||||
|
||||
# Run only integration tests
|
||||
uv run poe test -A -m integration
|
||||
```
|
||||
|
||||
Direct package execution still works when you need it:
|
||||
|
||||
```bash
|
||||
uv run --directory packages/core poe test
|
||||
```
|
||||
|
||||
## Test Configuration
|
||||
|
||||
- **Async mode**: `asyncio_mode = "auto"` is enabled — do NOT use `@pytest.mark.asyncio`, but do mark tests with `async def` and use `await` for async calls
|
||||
- **Timeout**: Default 60 seconds per test
|
||||
- **Import mode**: `importlib` for cross-package isolation
|
||||
- **Parallelization**: Large packages (core, ag-ui, orchestrations, anthropic) use `pytest-xdist` (`-n auto --dist worksteal`) in their `poe test` task. The aggregate `uv run poe test -A` sweep also uses xdist across the selected packages.
|
||||
|
||||
## Test Directory Structure
|
||||
|
||||
Test directories must NOT contain `__init__.py` files.
|
||||
|
||||
Non-core packages must place tests in a uniquely-named subdirectory:
|
||||
|
||||
```
|
||||
packages/anthropic/
|
||||
├── tests/
|
||||
│ └── anthropic/ # Unique subdirectory matching package name
|
||||
│ ├── conftest.py
|
||||
│ └── test_client.py
|
||||
```
|
||||
|
||||
Core package can use `tests/` directly with topic subdirectories:
|
||||
|
||||
```
|
||||
packages/core/
|
||||
├── tests/
|
||||
│ ├── conftest.py
|
||||
│ ├── core/
|
||||
│ │ └── test_agents.py
|
||||
│ └── openai/
|
||||
│ └── test_client.py
|
||||
```
|
||||
|
||||
## Fixture Guidelines
|
||||
|
||||
- Use `conftest.py` for shared fixtures within a test directory
|
||||
- Before adding new fixtures, check if existing ones can be reused or extended
|
||||
- Use descriptive names: `mapper`, `test_request`, `mock_client`
|
||||
|
||||
## File Naming
|
||||
|
||||
- Files starting with `test_` are test files — do not use this prefix for helpers
|
||||
- Use `conftest.py` for shared utilities
|
||||
|
||||
## Integration Tests
|
||||
|
||||
Integration tests require external services (OpenAI, Azure, etc.) and are controlled by three markers:
|
||||
|
||||
1. **`@pytest.mark.flaky`** — marks the test as potentially flaky since it depends on external services
|
||||
2. **`@pytest.mark.integration`** — used for test selection, so integration tests can be included/excluded with `-m integration` / `-m "not integration"`
|
||||
3. **`@skip_if_..._integration_tests_disabled`** decorator — skips the test when the required API keys or service endpoints are missing
|
||||
|
||||
### Adding New Integration Tests
|
||||
|
||||
All three markers must be applied to every new integration test:
|
||||
|
||||
```python
|
||||
@pytest.mark.flaky
|
||||
@pytest.mark.integration
|
||||
@skip_if_openai_integration_tests_disabled
|
||||
async def test_openai_chat_completion() -> None:
|
||||
...
|
||||
```
|
||||
|
||||
For test files where all tests are integration tests (e.g., Azure Functions, Durable Task), use the module-level `pytestmark` list:
|
||||
|
||||
```python
|
||||
pytestmark = [
|
||||
pytest.mark.flaky,
|
||||
pytest.mark.integration,
|
||||
pytest.mark.sample("01_single_agent"),
|
||||
pytest.mark.usefixtures("function_app_for_test"),
|
||||
]
|
||||
```
|
||||
|
||||
### CI Workflow
|
||||
|
||||
The merge CI workflow (`python-merge-tests.yml`) splits integration tests into parallel jobs by provider with change-based detection:
|
||||
|
||||
- **Unit tests** — always run all non-integration tests
|
||||
- **OpenAI integration** — runs when `packages/core/agent_framework/openai/` or core infrastructure changes
|
||||
- **Azure OpenAI integration** — runs when `packages/core/agent_framework/azure/` or core changes
|
||||
- **Misc integration** — Anthropic, Ollama, MCP tests; runs when their packages or core change
|
||||
- **Functions integration** — Azure Functions + Durable Task; runs when their packages or core change
|
||||
- **Foundry integration** — runs when `packages/foundry/` or core changes
|
||||
|
||||
Core infrastructure changes (e.g., `_agents.py`, `_types.py`) trigger all integration test jobs. Scheduled and manual runs always execute all jobs.
|
||||
|
||||
### Keeping CI Workflows in Sync
|
||||
|
||||
Two workflow files define the same set of parallel test jobs:
|
||||
|
||||
- **`python-merge-tests.yml`** — runs on PRs, merge queue, schedule, and manual dispatch. Uses path-based change detection to skip unaffected integration jobs.
|
||||
- **`python-integration-tests.yml`** — called from the manual integration test orchestrator (`integration-tests-manual.yml`). Always runs all jobs (no path filtering).
|
||||
|
||||
These workflows must be kept in sync. When you add, remove, or modify a test job, update **both** files. The job structure, pytest commands, and xdist flags should match between them. The only difference is that `python-merge-tests.yml` has path filters and conditional job execution, while `python-integration-tests.yml` does not.
|
||||
|
||||
### Updating the CI When Adding Integration Tests for a New Provider
|
||||
|
||||
When adding integration tests for a new provider package, you must update **both** `python-merge-tests.yml` and `python-integration-tests.yml`:
|
||||
|
||||
1. **Add a path filter** for the new provider in the `paths-filter` job in `python-merge-tests.yml` so the CI knows which file changes should trigger those tests.
|
||||
2. **Add the test job to both workflow files** — either add them to the existing `python-tests-misc-integration` job, or create a dedicated job if the provider:
|
||||
- Has a large number of integration tests
|
||||
- Requires special infrastructure setup (emulators, Docker containers, etc.)
|
||||
- Has long-running tests that would slow down the misc job
|
||||
|
||||
The `python-tests-misc-integration` job is intended for small integration test suites that don't need dedicated infrastructure. When a provider's integration tests grow large or gain special requirements, split them out into their own job (like `python-tests-functions` was split out for Azure Functions + Durable Task).
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Run only related tests, not the entire suite
|
||||
- Review existing tests to understand coding style before creating new ones
|
||||
- Use print statements for debugging, then remove them when done
|
||||
- Resolve all errors and warnings before committing
|
||||
Reference in New Issue
Block a user