Skip to content

Commit 3eb5f22

Browse files
fix(packaging): guard agentex-client skew, bump floor, smoke-test wheel install
Harden the 0.13.0 split (agentex-sdk + agentex-client share the agentex.* namespace) against the partial-install/skew break: - Bump the agentex-client floor to >=0.13.0 (first release where it ships separately) so an old client can't satisfy the dep. Kept floor-only: a ceiling would exclude the co-versioned slim (release-please can't bump it). - Sync uv.lock to the 0.13.0 workspace versions (it lagged pyproject at 0.12.0) so frozen installs resolve a client that satisfies the new floor. - Add an import-time guard (agentex.lib) that raises a clear, actionable error when the installed agentex-client/agentex-sdk versions are skewed, or the client REST surface is incomplete (canary import) — instead of a cryptic `cannot import name 'Event' from 'agentex.types'`. Catches skews the floor pin can't (frozen locks, --no-deps, pre-built images). It can't preempt a fully missing client surface (import agentex fails in the client's own __init__ first) — the wheel smoke covers that. - Add scripts/check-wheel-install (wired into the build CI job): builds both wheels, installs them together into a fresh venv, and imports agentex.lib.adk plus the agentex.types/resources client surface — catching a wheel that ships an incomplete agentex.* namespace. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 23858df commit 3eb5f22

7 files changed

Lines changed: 101 additions & 3 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ jobs:
6161
# force-include can't build via the sdist-then-wheel default.
6262
run: uv build --all-packages --wheel
6363

64+
- name: Smoke-test wheel install
65+
# Both wheels must install together into one working agentex.* namespace.
66+
run: ./scripts/check-wheel-install
67+
6468
- name: Get GitHub OIDC Token
6569
if: |-
6670
github.repository == 'stainless-sdks/agentex-sdk-python' &&

adk/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ readme = "README.md"
1515
dependencies = [
1616
# Co-released in lockstep; floor-only by design — a ceiling would
1717
# eventually exclude the co-versioned slim (release-please can't bump it).
18-
"agentex-client>=0.12.0",
18+
"agentex-client>=0.13.0",
1919
# CLI surface (agentex.lib.cli.*, agentex.lib.sdk.config.*)
2020
"typer>=0.16,<0.17",
2121
"questionary>=2.0.1,<3",

scripts/check-wheel-install

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/env bash
2+
# Smoke: agentex-client + agentex-sdk must install together into one working
3+
# agentex.* namespace. Builds + installs in a clean temp dir to avoid stale dist/.
4+
5+
set -euo pipefail
6+
7+
cd "$(dirname "$0")/.."
8+
9+
work="$(mktemp -d)"
10+
echo "==> building both wheels into $work/dist"
11+
uv build --all-packages --wheel --out-dir "$work/dist"
12+
13+
venv="$work/venv"
14+
uv venv "$venv" >/dev/null
15+
echo "==> installing both wheels into a fresh venv"
16+
uv pip install --python "$venv" "$work"/dist/agentex_client-*.whl "$work"/dist/agentex_sdk-*.whl
17+
18+
echo "==> importing the merged namespace from the installed wheels"
19+
"$venv/bin/python" - <<'PY'
20+
import agentex.lib.adk # ADK overlay — ships in agentex-sdk
21+
from agentex.types import Event # client surface — ships in agentex-client
22+
from agentex.resources import states # client surface that "didn't land" in the incident
23+
24+
print("agentex namespace OK:", Event.__name__, states.__name__)
25+
PY

src/agentex/lib/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from agentex.lib._version_guard import verify_client_compatibility
2+
3+
# Fail fast + clearly on a skewed/incomplete agentex-client install.
4+
verify_client_compatibility()

src/agentex/lib/_version_guard.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""Fail fast with a clear error on a skewed/incomplete agentex-client install
2+
instead of a cryptic `cannot import name ... from agentex.types`."""
3+
4+
from __future__ import annotations
5+
6+
from importlib.metadata import PackageNotFoundError, version
7+
8+
9+
def _installed(package: str) -> str | None:
10+
try:
11+
return version(package)
12+
except PackageNotFoundError:
13+
return None
14+
15+
16+
def verify_client_compatibility() -> None:
17+
sdk, client = _installed("agentex-sdk"), _installed("agentex-client")
18+
19+
# Published in lockstep — a version mismatch means a skewed client (floor
20+
# pins can be bypassed by frozen locks, --no-deps, or pre-built images).
21+
if sdk and client and sdk != client:
22+
raise ImportError(
23+
f"agentex-sdk {sdk} requires the co-released agentex-client {sdk}, but "
24+
f"agentex-client {client} is installed — reinstall both at the same "
25+
f"version (`pip install --force-reinstall agentex-sdk`)."
26+
)
27+
28+
# Surface canary: a partial install can leave the client REST surface
29+
# incomplete even when versions match. `Event` went missing in the incident.
30+
try:
31+
from agentex.types import Event as _Event # noqa: F401
32+
except (ImportError, AttributeError) as exc:
33+
raise ImportError(
34+
f"agentex-sdk could not import the agentex-client REST surface "
35+
f"(agentex-sdk={sdk or '?'}, agentex-client={client or '?'}) — reinstall "
36+
f"both at the same version (`pip install --force-reinstall agentex-sdk`)."
37+
) from exc

tests/lib/test_version_guard.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""Tests for the agentex-client compatibility guard (0.13.0 split regression)."""
2+
3+
from __future__ import annotations
4+
5+
import pytest
6+
7+
import agentex.lib._version_guard as guard
8+
9+
10+
def test_passes_when_versions_match_and_surface_present() -> None:
11+
guard.verify_client_compatibility() # workspace: both members at the same version
12+
13+
14+
def test_raises_on_version_skew(monkeypatch: pytest.MonkeyPatch) -> None:
15+
versions = {"agentex-sdk": "0.13.0", "agentex-client": "0.12.0"}
16+
monkeypatch.setattr(guard, "version", lambda pkg: versions[pkg])
17+
with pytest.raises(ImportError, match="agentex-client 0.12.0 is installed"):
18+
guard.verify_client_compatibility()
19+
20+
21+
def test_raises_when_client_surface_incomplete(monkeypatch: pytest.MonkeyPatch) -> None:
22+
import agentex.types
23+
24+
# Versions match, but the canary symbol is gone — a partial/incomplete surface.
25+
monkeypatch.setattr(guard, "version", lambda pkg: "0.13.0")
26+
monkeypatch.delattr(agentex.types, "Event", raising=False)
27+
with pytest.raises(ImportError, match="could not import the agentex-client REST surface"):
28+
guard.verify_client_compatibility()

uv.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)