feat(packaging)!: introduce slim agentex-sdk-client + heavy agentex-sdk split#370
Open
max-parke-scale wants to merge 6 commits into
Open
feat(packaging)!: introduce slim agentex-sdk-client + heavy agentex-sdk split#370max-parke-scale wants to merge 6 commits into
max-parke-scale wants to merge 6 commits into
Conversation
0d7db31 to
4ad75f7
Compare
max-parke-scale
added a commit
that referenced
this pull request
May 26, 2026
…end_path Follows up the slim/heavy split with the test relocations + dev-install plumbing that should have been part of the original commit: - Move tests/lib/* and tests at tests/ root that exercise lib code (test_function_tool.py, test_model_utils.py, test_header_forwarding.py) into adk/tests/. Tests now live with the code they test. The Path-based source references in test_claude_agents_*.py (`_SRC = parents[2] / "src"`) resolve correctly to adk/src/ via the new location. - Fix test_function_tool.py's broken `src.agentex.lib.*` import — switch to the installed-package path `agentex.lib.*` so it works against the editable install. - Add `from pkgutil import extend_path; __path__ = extend_path(...)` to src/agentex/__init__.py. This is the load-bearing fix for dev workflow: without it, two editable installs (slim at root, heavy at adk/) each contributing files to `agentex/` get only the first source dir in `agentex.__path__`, so `import agentex.lib.*` fails. With it, Python discovers both source trees and the namespace merges. Wheel installs (production) already worked because both wheels' files land in the same site-packages/agentex/ directory. - scripts/bootstrap: after `rye sync`, also `pip install -e ./adk` so agentex-sdk's deps land in the dev venv. agentex-sdk-client is already installed via the root sync, so adk's dep on it resolves to the local editable install (no PyPI lookup needed). - pyproject.toml [tool.pytest.ini_options].testpaths includes "adk/tests". - pyproject.toml [tool.ruff.lint.per-file-ignores] extends test-friendly ignores to adk/tests/. - Drop the rye workspace config — pkgutil.extend_path + explicit pip install -e ./adk in bootstrap gives the same dev experience without rye-workspace-version-mismatch quirks. - .github/workflows/ci.yml: lint + test jobs call ./scripts/bootstrap instead of `rye sync` directly; build job builds both packages. Self-review took: I shipped the file move without running the test suite locally — that's why CI broke on PR #370. Mea culpa. The functional design is correct; the rollout was sloppy. Verified locally: - `ruff check .` → All checks passed - `pytest --collect-only adk/tests/` → 100+ tests collect cleanly - `pytest adk/tests/test_function_tool.py` → 10 passed - Dev install (`pip install -e .` + `pip install -e ./adk`): `from agentex import Agentex` and `from agentex.lib.* import …` both work Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2a08eeb to
3a95482
Compare
1198bf9 to
da5b8f0
Compare
Contributor
Author
|
Fixed in cdb3a48:
Verified locally: both wheels build with @greptile review 🤖 — posted via Claude Code |
Contributor
Author
|
@greptile review |
a1617e5 to
69316be
Compare
…dk split Stacks on #371 (which moved protocol types to agentex.protocol.*). Together they enable REST-only consumers to install just the slim package and use typed protocol types without pulling the full ADK stack. Two-package layout: - agentex-sdk-client (slim, root pyproject.toml) * 6 deps: httpx, pydantic, typing-extensions, anyio, distro, sniffio * Ships agentex/{__init__.py, _*.py, _utils/, types/, resources/, protocol/, py.typed} * requires-python >= 3.11 * Stainless-managed * Slim wheel excludes src/agentex/lib/** - agentex-sdk (heavy, adk/pyproject.toml) * Depends on agentex-sdk-client>=0.11.4,<0.12 (lockstep, see comment) * Plus 31 ADK deps: temporalio, fastapi, redis, MCP, LLM providers, observability, CLI surface * Ships only agentex/lib/* via hatchling force-include from ../src/agentex/lib (lib/ stays at its historical location) * requires-python >= 3.12 (lib uses `from typing import override`, a 3.12+ stdlib API) * Hand-authored overlay; entire adk/ directory must be preserved across Stainless codegen via `keep_files: ["adk/**"]` Existing consumers (`pip install agentex-sdk`) see no change: heavy depends on slim, so the slim deps install transitively. Both packages contribute disjoint files to the agentex.* namespace. Release / publish wiring: - bin/publish-pypi publishes slim BEFORE heavy. Heavy depends on slim, so if the slim publish errors we abort before shipping a broken heavy that pins an unreleased slim. (Staff-engineer review fix.) - bin/check-release-environment validates BOTH PyPI tokens, with the legacy PYPI_TOKEN as a back-compat fallback. - .github/workflows/publish-pypi.yml passes AGENTEX_SDK_CLIENT_PYPI_TOKEN and AGENTEX_PYPI_TOKEN to the script. - release-please-config.json: two-package mode with components (agentex-sdk-client at root, agentex-sdk at adk/), and include-component-in-tag=true so tags become e.g. `agentex-sdk-client-v0.11.4` and `agentex-sdk-v0.11.4`. - .release-please-manifest.json: seeds adk/ at 0.11.4 so the first release produces matched versions. CI build job runs `rye build --wheel` for both packages; --wheel only (not sdist) because adk's wheel uses cross-directory force-include which can't resolve from inside an unpacked sdist tarball. Tag scheme breakage flagged with `!` in commit/PR title — downstream tooling that filters by raw `v*` tags will need updates. Required maintainer follow-ups before this can ship: - Stainless dashboard: add `adk/**` to keep_files; reduce dashboard dep-list to the 6 slim-base deps so codegen doesn't re-add the 31 ADK deps to root pyproject's `dependencies = [...]`. - PyPI: claim `agentex-sdk-client` package name; add AGENTEX_SDK_CLIENT_PYPI_TOKEN to repo secrets. - (Defer to a follow-up PR) post-codegen CI guardrail that asserts root pyproject's `dependencies = [...]` matches the 6-dep slim set. Verified locally: - Both wheels build cleanly via `rye build --wheel` - Slim ships agentex/protocol/* and excludes agentex/lib/* - Heavy ships only agentex/lib/* (incl. the back-compat shims from #371) - Dual install on Python 3.13: from agentex import Agentex AND from agentex.protocol.acp import RPCMethod AND from agentex.lib.utils.logging import make_logger AND from agentex.lib.types.acp import RPCMethod (shim) — all resolve; shim and canonical identity-check as the same class objects. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…wheels to uv The tutorial test workflow boots an agentex agent via `uv run --with <wheel> agentex agents run ...`. After the package split: - The slim `agentex_sdk_client-*.whl` ships from `dist/` (root) - The heavy `agentex_sdk-*.whl` (which provides the `agentex` CLI) now ships from `adk/dist/` Old workflow ran `uv build` at root (produces slim only) and the script hunted for `dist/agentex_sdk-*.whl` (heavy) — which doesn't exist there anymore. CI saw "❌ No built wheel found in dist/agentex_sdk-*.whl". Also: heavy pins `agentex-sdk-client>=0.11.4,<0.12` as a runtime dep, and the slim isn't on PyPI yet, so uv must resolve the slim from the LOCAL wheel too. Single `--with` isn't enough; need both. Fixes: - `.github/workflows/agentex-tutorials-test.yml`: build both packages in the "Build AgentEx SDK" step. - `examples/tutorials/run_agent_test.sh`: three sites (agent run, pytest invocation, check_built_wheel) now find both wheels at their new paths (slim at `dist/agentex_sdk_client-*.whl`, heavy at `adk/dist/agentex_sdk-*.whl`) and pass both to `uv run --with`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Greptile P1 + P2 on the slim/heavy split: - `bin/publish-pypi` heavy `rye build --clean` missing `--wheel`. Without it, rye defaults to sdist-then-wheel-from-sdist; the wheel build off the unpacked sdist can't resolve adk/'s `force-include = "../src/agentex/lib"`, silently producing an empty agentex-sdk wheel. Apply to slim too for consistency with CI (which always passes --wheel). - `adk/pyproject.toml` sdist `include = ["/../src/agentex/lib/**"]` is malformed: leading `/` is project-root-relative, `..` navigation outside the project root isn't honored by hatchling. Drop the entry; sdist support for the dual-package layout is deferred (wheel-only is enforced by CI + publish-pypi via `--wheel`). Verified locally: both wheels build cleanly with `uvx pyproject-build --wheel` and have disjoint contents (slim: 0 agentex/lib/* files; heavy: 347 lib/* files + no top-level client surface). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
release-please-config.json uses include-component-in-tag = true, so each release cycle emits two GitHub release events (agentex-sdk-client-vX.Y.Z and agentex-sdk-vX.Y.Z). Both events fire publish-pypi.yml, which calls this script — the script unconditionally publishes both wheels per run, so the second event's run tries to re-upload artifacts already on PyPI. Under `set -eux`, twine's non-zero exit on a duplicate upload aborts the script and turns the GitHub Action red on every other release cycle. Add --skip-existing to both rye publish invocations. rye 0.44.0 forwards this flag to `python -mtwine upload --skip-existing`; twine then treats already-present files as exit 0 with a WARNING in place of HTTPError. Verified locally against pypiserver: without the flag the second publish exits non-zero, with the flag it logs `WARNING Skipping ... appears to already exist` and exits 0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
next bumped openai-agents to >=0.14.3 (#375) for the scale-sandbox oai_agents adapter. The packaging split relocated this dep to adk/, so carry the floor forward here rather than reverting to the old 0.14.1 pin. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Rebased onto next, which released 0.11.5. Both packages co-version: the manifest, the slim client pyproject, and the adk pyproject all read 0.11.5. release-please's include-component-in-tag keeps the tags distinct (agentex-sdk-client-v0.11.5 vs agentex-sdk-v0.11.5). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
69316be to
820c3be
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Publishes the existing
agentex-sdkwheel as two namespace-sharing packages so REST-only consumers can install just the Stainless REST surface without dragging in the full ADK runtime.pip install agentex-sdk-clientagentex/{__init__.py, _*.py, _utils/, types/, resources/, protocol/, py.typed}pip install agentex-sdkagentex/lib/*; depends onagentex-sdk-clientThe two packages contribute disjoint files to the
agentex.*namespace. Existingpip install agentex-sdkconsumers see no change: heavy depends on slim, so the slim deps install transitively.Motivating consumer:
packages/egp-api-backendhand-rolls a ~600-line JSON-RPC gateway today because it can't import the typed wire shapes without pulling in temporalio, fastapi, claude-agent-sdk, and 28 other deps. With this split + #371 (which moves protocol types toagentex.protocol.*), that gateway can pinagentex-sdk-clientand usefrom agentex.protocol.acp import RPCMethod, CreateTaskParams, ....Tracking: AGX1-292.
Note on the PR stack
This change was originally drafted as a single large PR (history visible in the force-push). On review it was clearly two distinct concerns, so it's been split:
agentex.lib.types.*toagentex.protocol.*with back-compat shims. Zero install-time impact.Reviewing them separately should be much more tractable.
Repo layout after merge
src/agentex/lib/stays where it is — Stainless already preserves it perCONTRIBUTING.md. The slim wheel's[tool.hatch.build.targets.wheel].excludekeeps lib/ out of the slim. The heavy wheel uses hatchlingforce-include "../src/agentex/lib" = "agentex/lib"to pull lib/ into the heavy. Same source file, two disjoint wheels.Python-version pins
agentex-sdk-client:requires-python = ">= 3.11,<4". Zero 3.12-only imports in the Stainless surface.agentex-sdk:requires-python = ">= 3.12,<4".agentex/lib/*usesfrom typing import override(3.12+ stdlib) in 19 files. The combined package's prior>= 3.11pin was de-facto broken on 3.11; this PR aligns the pin with what actually works.Release / publish wiring
bin/publish-pypi: publishes slim before heavy. Heavy depends on slim, so flipping the order means a slim-side failure (token, transient PyPI 5xx, name collision) aborts before we ship a heavy that pins an unreleased slim.bin/check-release-environment: validates bothAGENTEX_SDK_CLIENT_PYPI_TOKENandAGENTEX_PYPI_TOKEN, with legacyPYPI_TOKENas fallback..github/workflows/publish-pypi.yml: passes both token secrets to the script.release-please-config.json: two-package mode (.andadk/) withinclude-component-in-tag = true. Tag scheme changes fromv0.11.4→agentex-sdk-client-v0.11.4/agentex-sdk-v0.11.4— flagged with!in the title and commit. Any downstream tooling filtering by rawv*tags will need updating..release-please-manifest.json: seedsadk/at0.11.4so the first release produces matched versions.rye build --wheel(the--wheelflag is important — sdist-then-wheel-from-sdist can't resolve adk's cross-directoryforce-includefrom inside an unpacked sdist tarball).Required maintainer follow-ups before this can ship
adk/**tokeep_filesso the ADK overlay persists across codegen.pyproject.tomlto the 6 slim-base deps. (See "Risks" below — if this isn't done, every Stainless regen will silently re-add the 31 ADK deps to slim'sdependencies = [...].)agentex-sdk-clientpackage name; addAGENTEX_SDK_CLIENT_PYPI_TOKENto repo secrets.agentex-sdkpublishing continues usingAGENTEX_PYPI_TOKENfromadk/.adk/survives and root pyproject's slim shape isn't clobbered.Planned follow-up PRs
pyproject.toml'sdependencies = [...]is exactly the 6-dep slim set. Catches dashboard drift if Stainless re-adds ADK deps.scaleapi/scaleapi): migratepackages/egp-api-backendfrom hand-rolled JSON-RPC to typedagentex.protocol.acpshapes; pinagentex-sdk-client. ~600 lines of dict-literal construction become typed model usage.requirements.lock/requirements-dev.lockneed refresh afterrye syncagainst the workspace; not yet committed because the local env doesn't have rye installed. Easy to fix in this PR if reviewers want, or as a fast-follow.README.mddescribes capabilities the slim doesn't ship; deferring to its own PR so this one stays focused on packaging.agentex.__version__policy:release-please-config.json'sextra-filesupdates only root_version.py, so the runtime__version__reflects the slim only. Either lockstep-version both (recommended, since they're co-released) or add a separateagentex.lib.__version__.Verification (local)
Risks
exclude. The slim's[tool.hatch.build.targets.wheel].exclude = ["src/agentex/lib/**"]is a hand-edit to Stainless's emitted file. Manual edits todependencies = [...]survive Stainless 3-way merge historically (~7 confirmed examples fromgit log). We're betting the wheel-targetexcludesurvives the same way. If it gets clobbered, the slim wheel would start re-including lib/ and conflict with the heavy on install. Detection: PR D's guardrail check catches it. Mitigation if it happens: re-add manually or move the exclude to the Stainless dashboard if configurable.v*tags needs to update — flagged with!in title/commit per Conventional Commits.rye build's default sdist-then-wheel-from-sdist flow can't resolve the cross-directoryforce-include. PyPI tolerates wheel-only releases; if reviewers want sdist support, options are (a) configure hatchling to copy../src/agentex/libinto the sdist, or (b) explicitly disable sdist for adk/.agentex-sdk-clientversion pin inadk/pyproject.tomlis>=0.11.4,<0.12. Release-please doesn't automatically bump this when slim moves; bumping the range is a manual step each minor/major. Comment in the file flags it. A release-please plugin or a small CI substitution rule could automate this in a follow-up.Greptile Summary
This PR introduces a dual-wheel packaging split: the Stainless-generated REST surface ships as
agentex-sdk-client(6 deps, Python ≥ 3.11) and the hand-authored ADK overlay ships asagentex-sdk(31+ deps, Python ≥ 3.12), with the two packages contributing disjoint files to the sharedagentex.*namespace. Existingpip install agentex-sdkconsumers are unaffected because the heavy package declaresagentex-sdk-clientas a runtime dependency.pyproject.toml(root): renamed toagentex-sdk-client, 31 ADK deps removed, wheelexcludeforagentex/lib/**added to keep the slim disjoint from the heavy.adk/pyproject.toml(new): defines theagentex-sdkwheel with hatchlingforce-includepulling../src/agentex/libintoagentex/lib;--wheelflag is load-bearing throughout CI andbin/publish-pypibecause the sdist-then-wheel default can't resolve the cross-directory path.release-please-config.jsonswitches to two-package mode withinclude-component-in-tag: true;bin/publish-pypipublishes slim before heavy (correct dependency order) and bothrye publishcalls now carry--skip-existingso the second workflow trigger from the second component tag is idempotent.Confidence Score: 5/5
Safe to merge; the packaging split is structurally sound, publish order is correct, and --skip-existing handles the dual-trigger from two component tags.
The two previously flagged concerns (missing --wheel on the heavy build and publish idempotency) are both addressed. The remaining findings are advisory: the global extra-files scope could cause version drift if packages ever release independently, and run_test() silently falls back to PyPI wheels when one local wheel is absent. Neither causes a release or correctness failure under the current lockstep-release model.
release-please-config.json (global extra-files scope) and examples/tutorials/run_agent_test.sh (silent wheel fallback in run_test)
Important Files Changed
Sequence Diagram
sequenceDiagram participant RP as release-please participant GH as GitHub Actions participant Script as bin/publish-pypi participant PyPI as PyPI Registry RP->>GH: Create release (agentex-sdk-client-vX.Y.Z) GH->>Script: trigger publish-pypi.yml Script->>Script: rye build --clean --wheel (root) Script->>PyPI: rye publish --skip-existing (slim, AGENTEX_SDK_CLIENT_PYPI_TOKEN) Script->>Script: "cd adk && rye build --clean --wheel" Script->>PyPI: rye publish --skip-existing (heavy, AGENTEX_PYPI_TOKEN) RP->>GH: Create release (agentex-sdk-vX.Y.Z) GH->>Script: trigger publish-pypi.yml (second run) Script->>Script: rye build --clean --wheel (root) Script->>PyPI: rye publish --skip-existing (slim, already exists -- skip) Script->>Script: "cd adk && rye build --clean --wheel" Script->>PyPI: rye publish --skip-existing (heavy, already exists -- skip)Comments Outside Diff (1)
.github/workflows/publish-pypi.yml, line 8-9 (link)With
include-component-in-tag: true, release-please creates two separate GitHub releases:agentex-sdk-client-v*andagentex-sdk-v*. Each triggers this workflow independently. Every invocation ofbin/publish-pypipublishes both packages unconditionally, so the second triggered run will attempt to re-upload artifacts already present on PyPI. Sincerye publish(via twine) exits non-zero on a 409 Conflict and the script runs withset -eux, the second workflow run will fail. Adding--skip-existingto bothrye publishcalls inbin/publish-pypi(or switching totwine upload --skip-existing dist/*) would make the publish script idempotent and tolerate this re-trigger.Prompt To Fix With AI
Prompt To Fix All With AI
Reviews (7): Last reviewed commit: "chore(release): co-version agentex-sdk (..." | Re-trigger Greptile