Python Package Management
When to invoke
- Adding or changing an Agent Framework Python package or connector.
- Updating dependencies, bounds, lazy exports, installation metadata, or lifecycle versions.
- Preparing package documentation and validation for a release.
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 for dependency management and
poethepoet for task automation.
# 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
# Release cuts: refresh uv.lock and probe changed packages at both bound extremes.
# The release probe has a shared five-minute deadline.
uv run poe validate-python-release --base-ref upstream/main
# Exhaustive test+typing matrix (slow; use for deliberate dependency-range work or CI)
uv run poe validate-dependency-bounds-test
# Defaults to --package "*"; scope locally whenever possible.
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 release-only version, lifecycle, pin, and internal-floor edits, use
validate-python-release. It refreshes
uv.lock, finds changed package metadata relative to the selected main ref, and runs the changed packages'
published runtime dependencies and non-development extras through lock-independent lowest-direct and highest
import probes on the minimum Python minor supported by each package's internal editable closure. The probes run
concurrently under one 300-second deadline; pass --python only when an explicit interpreter override is needed.
- For deliberate external dependency-range changes, use
validate-dependency-bounds-project --mode both for the target package/dependency to find and validate the actual
minimum and maximum constraints. Scope the exhaustive validate-dependency-bounds-test matrix to affected
packages during local iteration; reserve the workspace-wide form for CI or an intentional full audit. The same
project task can 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:
# 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
- Create directory under
packages/ (e.g., packages/my-connector/)
- Add the package to
tool.uv.sources in root pyproject.toml
- Set the package version to the alpha pattern:
1.0.0a<date>
- Set the package classifier to
Development Status :: 3 - Alpha
- Include samples inside the package (e.g.,
packages/my-connector/samples/)
- Do NOT add to
[all] extra in packages/core/pyproject.toml
- Do NOT create lazy loading in core yet
- 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:
- Add the dependency to the target package:
uv run poe add-dependency-to-project --package core --dependency "<dependency-spec>"
- Implement connector code and tests.
- Validate dependency bounds for that package/dependency:
uv run poe validate-dependency-bounds-project --mode both --package core --dependency "<dependency-name>"
- 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.
- Update the package version to the beta pattern:
1.0.0b<date>
- Update the classifier to
Development Status :: 4 - Beta
- Add the package to
[all] in packages/core/pyproject.toml
- Move samples to the root
samples/ tree and remove package-local samples
- Create or update the relevant lazy-loading namespace in core when the package belongs under one
- 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.
- Update the package version to the release-candidate pattern:
1.0.0rc<number>
- Keep the classifier at
Development Status :: 4 - Beta because PyPI does not have a separate
release-candidate classifier
- Keep the package in
core[all]
- Keep samples only in the root
samples/ tree
- 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.
- Update the package version to the stable pattern:
1.0.0
- Update the classifier to
Development Status :: 5 - Production/Stable
- Keep the package in
core[all]
- Keep samples only in the root
samples/ tree
- Update
python/PACKAGE_STATUS.md to show the package as released
- 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
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
Output template
## Package management result
- Package: `<name>`
- Lifecycle/version: `<stage and version>`
- Dependency changes: `<details>`
- Export and documentation changes: `<details>`
- Validation: `<commands and results>`
Quality gate
1---2name: python-package-management3description: Guide for managing packages in the Agent Framework Python monorepo, including creating new connector packages, versioning, and the lazy-loading pattern. Use when adding, modifying, or releasing packages.4---56<!-- Generated from harness/github-copilot/plugins/open-horizons-platform/skills/python-package-management/SKILL.md by harness/claude-code/scripts/convert_from_copilot.py. Edit the source, not this file. -->78# Python Package Management910## When to invoke1112- Adding or changing an Agent Framework Python package or connector.13- Updating dependencies, bounds, lazy exports, installation metadata, or lifecycle versions.14- Preparing package documentation and validation for a release.1516## Monorepo Structure1718```19python/20├── pyproject.toml # Root package (agent-framework)21├── packages/22│ ├── core/ # agent-framework-core (main package)23│ ├── foundry/ # agent-framework-foundry24│ ├── anthropic/ # agent-framework-anthropic25│ └── ... # Other connector packages26```2728- `agent-framework-core` contains core abstractions and OpenAI/Azure OpenAI built-in29- Provider packages extend core with specific integrations30- Root `agent-framework` depends on `agent-framework-core[all]`3132## Dependency Management3334Uses [uv](https://github.com/astral-sh/uv) for dependency management and35[poethepoet](https://github.com/nat-n/poethepoet) for task automation.3637```bash38# Full setup (venv + install + prek hooks)39uv run poe setup4041# Install dependencies from lockfile (frozen resolution with prerelease policy)42uv run poe install4344# Create venv with specific Python version45uv run poe venv --python 3.124647# Intentionally upgrade a specific dependency to reduce lockfile conflicts48uv lock --upgrade-package <dependency-name> && uv run poe install4950# Refresh exact development dependency-group pins, lockfile, and validation in one run51uv run poe upgrade-dev-dependencies5253# Release cuts: refresh uv.lock and probe changed packages at both bound extremes.54# The release probe has a shared five-minute deadline.55uv run poe validate-python-release --base-ref upstream/main5657# Exhaustive test+typing matrix (slow; use for deliberate dependency-range work or CI)58uv run poe validate-dependency-bounds-test59# Defaults to --package "*"; scope locally whenever possible.60uv run poe validate-dependency-bounds-test --package core6162# Then expand bounds for one dependency in the target package63uv run poe validate-dependency-bounds-project --mode both --package core --dependency "<dependency-name>"6465# Repo-wide automation can reuse the same task66uv run poe validate-dependency-bounds-project --mode upper --package "*"6768# Add a dependency to one project and run both validators for that project/dependency69uv run poe add-dependency-and-validate-bounds --package core --dependency "<dependency-spec>"70```7172### Dependency Bound Notes7374- Stable dependencies (`>=1.0`) should typically be bounded as `>=<known-good>,<next-major>`.75- Prerelease (`dev`/`a`/`b`/`rc`) and `<1.0` dependencies should use hard bounds with an explicit upper cap (avoid open-ended ranges).76- 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.77- Prefer supporting multiple majors when practical; if APIs diverge across supported majors, use version-conditional imports/paths.78- For release-only version, lifecycle, pin, and internal-floor edits, use `validate-python-release`. It refreshes79 `uv.lock`, finds changed package metadata relative to the selected main ref, and runs the changed packages'80 published runtime dependencies and non-development extras through lock-independent `lowest-direct` and `highest`81 import probes on the minimum Python minor supported by each package's internal editable closure. The probes run82 concurrently under one 300-second deadline; pass `--python` only when an explicit interpreter override is needed.83- For deliberate external dependency-range changes, use84 `validate-dependency-bounds-project --mode both` for the target package/dependency to find and validate the actual85 minimum and maximum constraints. Scope the exhaustive `validate-dependency-bounds-test` matrix to affected86 packages during local iteration; reserve the workspace-wide form for CI or an intentional full audit. The same87 project task can drive repo-wide upper-bound automation by using `--package "*"` and omitting `--dependency`.88- Prefer targeted lock updates with `uv lock --upgrade-package <dependency-name>` to reduce `uv.lock` merge conflicts.89- Use `add-dependency-and-validate-bounds` for package-scoped dependency additions plus bound validation in one command.90- Keep shared tooling and source/type-check support in the root or package `dev` group. Put package-specific test91 fixtures in a `test` group, and use a feature-named group for local-only executable dependencies that cannot be92 expressed in published runtime metadata.93- Use `upgrade-dev-dependencies` for repo-wide development dependency refreshes; it repins exact dependencies94 across development groups, refreshes `uv.lock`, and reruns `check`, `typing`, and `test`.9596## Lazy Loading Pattern9798### Root core API99100The root `agent_framework` package is a lazy public API surface:101102- Runtime exports live in `packages/core/agent_framework/__init__.py`.103- Typing/editor exports live in `packages/core/agent_framework/__init__.pyi`.104- Add or move root exports in `_LAZY_MODULE_EXPORTS`, keep the explicit runtime `__all__` in sync, and add the same105 symbol to the `.pyi` file.106- Keep deprecation behavior in the owning module (for example, a module-level `__getattr__` that warns and returns107 the deprecated alias). Do not add one-off deprecated-symbol branches to root `__getattr__`.108- Validate root API changes with `uv run poe syntax -P core`, `uv run poe pyright -P core`, and import smoke tests109 for both `from agent_framework import <symbol>` and `from agent_framework import *`.110111### Provider namespaces112113Provider folders in core use `__getattr__` to lazy load from connector packages:114115```python116# In agent_framework/foundry/__init__.py117_IMPORTS: dict[str, tuple[str, str]] = {118 "FoundryChatClient": ("agent_framework_foundry", "agent-framework-foundry"),119}120121def __getattr__(name: str) -> Any:122 if name in _IMPORTS:123 import_path, package_name = _IMPORTS[name]124 try:125 return getattr(importlib.import_module(import_path), name)126 except ModuleNotFoundError as exc:127 raise ModuleNotFoundError(128 f"The package {package_name} is required to use `{name}`. "129 f"Install it with: pip install {package_name}"130 ) from exc131```132133## Adding a New Connector Package134135**Important:** Do not create a new package unless approved by the core team.136137Every new package starts as `alpha`.138139### Alpha package checklist1401411. Create directory under `packages/` (e.g., `packages/my-connector/`)1422. Add the package to `tool.uv.sources` in root `pyproject.toml`1433. Set the package version to the alpha pattern: `1.0.0a<date>`1444. Set the package classifier to `Development Status :: 3 - Alpha`1455. Include samples inside the package (e.g., `packages/my-connector/samples/`)1466. Do **NOT** add to `[all]` extra in `packages/core/pyproject.toml`1477. Do **NOT** create lazy loading in core yet1488. Add the package to `python/PACKAGE_STATUS.md` and keep that file updated when packages are added,149 removed, renamed, or promoted. If the package exposes individually staged APIs, keep the feature list150 there current too.151152Recommended dependency workflow during connector implementation:1531541. Add the dependency to the target package:155 `uv run poe add-dependency-to-project --package core --dependency "<dependency-spec>"`1562. Implement connector code and tests.1573. Validate dependency bounds for that package/dependency:158 `uv run poe validate-dependency-bounds-project --mode both --package core --dependency "<dependency-name>"`1594. If the package has meaningful tests/checks that validate dependency compatibility, you can use the add + validation flow in one command:160 `uv run poe add-dependency-and-validate-bounds --package core --dependency "<dependency-spec>"`161 If compatibility checks are not in place yet, add the dependency first, then implement tests before running bound validation.162163### Promotion path164165Promotion work is not isolated to the package being promoted. If a promotion changes dependency166metadata for downstream packages, also update the dependent packages' own versions so they publish167new metadata alongside the promoted dependency bounds.168Apply the internal package dependency update rules from the versioning section below during169promotions as well as standalone version update work.170171#### Alpha -> Beta172173Move a package to `beta` when it is stable enough to be part of the main install surface.1741751. Update the package version to the beta pattern: `1.0.0b<date>`1762. Update the classifier to `Development Status :: 4 - Beta`1773. Add the package to `[all]` in `packages/core/pyproject.toml`1784. Move samples to the root `samples/` tree and remove package-local samples1795. Create or update the relevant lazy-loading namespace in core when the package belongs under one1806. Update `python/PACKAGE_STATUS.md`181182After `alpha`, there should be no samples left inside a package folder.183184#### Beta -> RC185186Move a package to `rc` when its API is close to the final released shape.1871881. Update the package version to the release-candidate pattern: `1.0.0rc<number>`1892. Keep the classifier at `Development Status :: 4 - Beta` because PyPI does not have a separate190 release-candidate classifier1913. Keep the package in `core[all]`1924. Keep samples only in the root `samples/` tree1935. Update `python/PACKAGE_STATUS.md` to show the package as `rc`194195#### RC -> Released196197Move a package to `released` when it no longer carries a prerelease qualifier.1981991. Update the package version to the stable pattern: `1.0.0`2002. Update the classifier to `Development Status :: 5 - Production/Stable`2013. Keep the package in `core[all]`2024. Keep samples only in the root `samples/` tree2035. Update `python/PACKAGE_STATUS.md` to show the package as `released`2046. Update all `README.md` files that install that package with205 `pip install agent-framework-... --pre` so they use `pip install agent-framework-...` without206 the `--pre` suffix207208## Versioning209210### Internal package dependency updates211212- If package A depends on package B within this repository, only update package A's dependency213 declaration when the work on package B actually affects package A.214- If package A does not need anything from the package B change, leave package A's dependency215 declaration unchanged.216- If package A does need something from the package B change, update package A's dependency217 declaration to the version or versioning scheme that matches what package A now requires.218- If package B is promoted to a different lifecycle stage, update package A's dependency219 declaration to the new versioning scheme for package B even when the only change is the stage220 transition itself.221- Use this guidance both for ordinary version updates and for package promotion work.222223- All non-core packages declare a lower bound on `agent-framework-core`224- When core version bumps with breaking changes, update the lower bound in all packages225- Non-core packages version independently; only raise core bound when using new core APIs226- If promoting a package changes a dependent package's published dependency metadata, bump the227 dependent package's own version in the correct lifecycle pattern for its current stage228- Lifecycle version patterns:229 - `alpha`: `1.0.0a<date>` where `<date>` is the current Pacific (US west coast) `YYMMDD`230 - `beta`: `1.0.0b<date>` where `<date>` is the current Pacific (US west coast) `YYMMDD`231 - `rc`: `1.0.0rc<number>` where `<number>` increments only when the package has changes232 - `released`: `X.Y.Z` using semver per package233- For alpha/beta date stamps, use the current Pacific date as the cutoff, not UTC and not the user's local234 timezone. Same-Pacific-day re-cuts use a `.postN` suffix. Honor an explicit user-provided date over this235 default.236- Keep the `Development Status` classifier in `pyproject.toml` aligned with the lifecycle stage:237 - `alpha` -> `Development Status :: 3 - Alpha`238 - `beta` -> `Development Status :: 4 - Beta`239 - `rc` -> `Development Status :: 4 - Beta`240 - `released` -> `Development Status :: 5 - Production/Stable`241- See the PyPI classifier list for the available classifier values:242 `https://pypi.org/classifiers/`243244## Installation Options245246```bash247pip install agent-framework-core # Core only248pip install agent-framework-core[all] # Core + all connectors249pip install agent-framework # Same as core[all]250pip install agent-framework-foundry # Specific connector (pulls in core)251```252253## Maintaining Documentation254255When changing a package, check if its `AGENTS.md` needs updates:256- Adding/removing/renaming public classes or functions257- Changing the package's purpose or architecture258- Modifying import paths or usage patterns259260Keep `python/PACKAGE_STATUS.md` updated when:261- A package is added, removed, renamed, or promoted between lifecycle stages262- A package starts or stops exposing individually staged experimental or release-candidate APIs263264When a package adds, removes, or renames environment variables, update the related documentation in the same265change:266- The package's `README.md` for package-level configuration/env var guidance267- `samples/README.md` if the package is included in `packages/core/pyproject.toml` `[all]` and the env var is268 part of the consolidated package env-var inventory269- Any affected sample/package-local `.env.example`, `.env.template`, or sample README files when sample setup270 changes alongside the package271272## Output template273274```markdown275## Package management result276277- Package: `<name>`278- Lifecycle/version: `<stage and version>`279- Dependency changes: `<details>`280- Export and documentation changes: `<details>`281- Validation: `<commands and results>`282```283284## Quality gate285286- [ ] Package metadata, lifecycle classifier, and version pattern agree.287- [ ] Dependency bounds are explicit and validated at relevant extremes.288- [ ] Lazy exports and package-level public APIs remain synchronized.289- [ ] Root extras, samples, status inventory, and documentation are updated when applicable.290- [ ] Lockfile and focused package checks passed.