…md#1454)
## Why this matters
`gaia agent publish` already dual-uploads each agent wheel to **R2**
(the Hub UI source) and **PyPI** (the `pip install` source) at author
time — but nothing published those wheels on a release. So `pip install
gaia-agent-<id>` and `amd-gaia[agents]` had no automation behind them.
This adds the missing CI half: on a `v*` tag, every production
`gaia-agent-*` wheel is built and published to PyPI via
`pypa/gh-action-pypi-publish` with `skip-existing: true`, so an
unchanged agent version is a no-op (PyPI-native immutability — no custom
overwrite logic). Closes the last open item of amd#1179.
The publish matrix is derived from `setup.py`'s `[agents]` extra through
a new `util/list_agent_packages.py` helper, so the meta-package, the
install docs, and the publish workflow all read one source of truth —
adding an agent to the extra is enough to start publishing it (no second
list to drift).
Scope note: the dual-publish `publisher.py`, the `gaia agent
publish/login` CLI, `tests/unit/test_hub_publisher.py`, the
`[agents]`/`[agent-*]` extras, and the `amd-gaia>=` wheel deps all
landed earlier (amd#1415, amd#1102 wave). This PR adds only what was still
missing for amd#1179: the release-tag PyPI workflow, the drift-proof matrix
helper, guard tests, and the install/CI docs.
## Test plan
- [x] `python util/lint.py --black --isort` — clean
- [x] `pylint -E util/list_agent_packages.py
tests/unit/test_agent_pypi_publish.py` — clean
- [x] `pytest tests/unit/ -k "agent and (publish or install or hub)"` —
38 passed
- [x] `pytest tests/unit/test_agent_pypi_publish.py` — 10 passed (helper
drift, wheel `amd-gaia` dep, `gaia-agent-<id>` naming, workflow uses
`gh-action-pypi-publish` + `PYPI_API_TOKEN` + `skip-existing`, publish
gated on `v*`)
- [x] `python -m build hub/agents/python/summarize` — wheel builds;
`twine check` PASSED; metadata declares `Requires-Dist:
amd-gaia>=0.20.0`
- [x] `python util/list_agent_packages.py --format matrix` — resolves
the 11 migrated agents to their `hub/agents/python/<id>` dirs
- [x] Workflow YAML validates; tag triggers carry no `paths:` filter (so
a release isn't skipped when the tagged commit didn't touch an agent)
- [ ] Requires a `PYPI_API_TOKEN` repository/org secret with upload
rights to the `gaia-agent-*` projects before the first tagged release
> Note: no live PyPI upload was performed — builds and uploads were
dry-run/mocked only.
Co-authored-by: Ovtcharov <[email protected]>
Why this matters
Hub agents could be authored, versioned, and validated (
gaia agent init/version/test) but there was no way to ship one — no command to turn a package into a wheel, and no path onto the Agent Hub or PyPI. Authors were stuck with a valid package and nowhere to send it.This closes the loop.
gaia agent packbuilds a distributable wheel from any hub agent package, andgaia agent publishdual-publishes it to R2 (canonical for the Hub UI/website) and PyPI (canonical forpip install gaia-agent-<id>) in one command. Both targets enforce version immutability, and every failure (missing token, build error, network, already-published version) surfaces as an actionable error rather than a silent no-op.Implements #1093 (Phase 2 — wheel packaging) and #1179 (Phase 3F — dual distribution) from
docs/spec/agent-hub-restructure.mdx.What's here
gaia agent pack—python -m build --wheel→dist/gaia_agent_<id>-<version>-py3-none-any.whl+ SHA-256. Python agents only (C++ ships a CMake binary).gaia agent publish— packs, then POSTs the wheel +gaia-agent.yamlto the Worker/publish(Bearer auth) andtwine uploads to PyPI.--skip-r2/--skip-pypipublish to one target.gaia agent login— stores Hub/PyPI tokens in the OS keyring;GAIA_HUB_TOKEN/PYPI_TOKENenv vars take precedence (CI-friendly).setup.py— newpublishextra (build,twine) plusagentsmeta-extras.Test plan
PYTHONPATH=src python -m pytest tests/unit/test_hub_packager.py tests/unit/test_hub_publisher.py tests/unit/test_cli_agent.py→ 66 pass (subprocess + HTTP mocked; covers wheel-path/checksum, dual-publish auth, missing-token, version-immutability, network errors)gaia agent pack hub/agents/python/summarize/buildsgaia_agent_summarize-0.1.0-py3-none-any.whl(14.5 KB) withbuildinstalledpython util/lint.py --pylint --black --isort→ black/isort PASS; pylint clean for the new files (the one remaining error is a pre-existing Windows-onlyos.geteuidfalse positive inlemonade_installer.py, untouched here)tests/unit/test_amd_gaia_urls.pypasses (new spec URL keeps the/docs/prefix)Refs #1093, #1179.