Skip to content

Commit 4d6f4cf

Browse files
stainless-app[bot]max-parke-scaleclaudesmoreinisdeclan-scale
authored
chore: release main (#411)
Co-authored-by: Max Parke <max.parke@scale.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com> Co-authored-by: Stas Moreinis <stas.moreinis@scale.com> Co-authored-by: Declan Brady <declan.brady@scale.com> Co-authored-by: Michael Chou <michael.chou@scale.com> Co-authored-by: Daniel Miller <daniel.miller@scale.com> Co-authored-by: Matteo Librizzi <matteo.librizzi@scale.com> Co-authored-by: Stas Moreinis <smoreinis@gmail.com> Co-authored-by: James Cardenas <james.cardenas@scale.com> Co-authored-by: Nitesh Dhanpal <NiteshDhanpal@users.noreply.github.com>
1 parent f312fef commit 4d6f4cf

20 files changed

Lines changed: 569 additions & 7 deletions

.release-please-manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
".": "0.13.1",
3-
"adk": "0.13.1"
2+
".": "0.14.0",
3+
"adk": "0.13.2"
44
}

.stats.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 64
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/sgp/agentex-sdk-dfcee301cded58822f489f034b6fcd42f392df406ca3780e7213698cec59c777.yml
3-
openapi_spec_hash: 3aae4790b24edf6ea9469c1680d513ae
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/sgp/agentex-sdk-ae2571b5ac5d337ba5ced527cec0ff6e3088296fa67c3c836ed5a06544b25cb8.yml
3+
openapi_spec_hash: 962a2f20444c7823fd3a34f95365146e
44
config_hash: 138b7c0b394e7393133c8ff16a6d0eb3

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@
66

77
* **tracing:** emit OTel metrics for async span queue depth, batch drain, and SGP export success/failure (HTTP status labels). Disable SDK-side recording with ``AGENTEX_TRACING_METRICS=0``.
88

9+
## 0.14.0 (2026-06-22)
10+
11+
Full Changelog: [agentex-client-v0.13.1...agentex-client-v0.14.0](https://github.com/scaleapi/scale-agentex-python/compare/agentex-client-v0.13.1...agentex-client-v0.14.0)
12+
13+
### Features
14+
15+
* **api:** add is error to tools ([8ddd960](https://github.com/scaleapi/scale-agentex-python/commit/8ddd9604290d23ed59586a68bd6db46bf452104b))
16+
* **compat:** runtime SDK↔backend version guard at ACP startup ([#408](https://github.com/scaleapi/scale-agentex-python/issues/408)) ([433c999](https://github.com/scaleapi/scale-agentex-python/commit/433c999bbdb4817d2048c5454cb65b54812950af))
17+
18+
19+
### Bug Fixes
20+
21+
* **types:** add missing Optional import to ToolResponseContent ([3439f6e](https://github.com/scaleapi/scale-agentex-python/commit/3439f6edec9ab89d685b5b1c99e567a67c911522))
22+
923
## 0.13.1 (2026-06-17)
1024

1125
Full Changelog: [agentex-client-v0.13.0...agentex-client-v0.13.1](https://github.com/scaleapi/scale-agentex-python/compare/agentex-client-v0.13.0...agentex-client-v0.13.1)

adk/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 0.13.2 (2026-06-22)
4+
5+
Full Changelog: [agentex-sdk-v0.13.1...agentex-sdk-v0.13.2](https://github.com/scaleapi/scale-agentex-python/compare/agentex-sdk-v0.13.1...agentex-sdk-v0.13.2)
6+
37
## 0.13.1 (2026-06-17)
48

59
Full Changelog: [agentex-sdk-v0.13.0...agentex-sdk-v0.13.1](https://github.com/scaleapi/scale-agentex-python/compare/agentex-sdk-v0.13.0...agentex-sdk-v0.13.1)

adk/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# (agentex/{__init__.py, _*.py, types/, resources/}) ships from the slim
55
# sibling package `agentex-client` which is pinned as a runtime dep.
66
name = "agentex-sdk"
7-
version = "0.13.1"
7+
version = "0.13.2"
88
description = "Agent Development Kit (ADK) overlay for the Agentex API — FastACP server, Temporal workflows, LLM provider integrations, observability"
99
license = "Apache-2.0"
1010
authors = [

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# overlay (formerly `src/agentex/lib/*`) now lives in `adk/` and ships
44
# as the sibling `agentex-sdk` package — see `adk/pyproject.toml`.
55
name = "agentex-client"
6-
version = "0.13.1"
6+
version = "0.14.0"
77
description = "The official Python REST client for the Agentex API"
88
dynamic = ["readme"]
99
license = "Apache-2.0"

src/agentex/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

33
__title__ = "agentex"
4-
__version__ = "0.13.1" # x-release-please-version
4+
__version__ = "0.14.0" # x-release-please-version
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
"""Runtime SDK ↔ backend contract-version guard.
2+
3+
Complements the *build-time* cross-version compatibility tests (``tests/compat``):
4+
5+
- **Build-time** (CI): is this *client* compatible with the window of supported server
6+
contracts (``min-supported``..``current``)?
7+
- **Runtime** (this module): is the *server* the SDK is pointed at within that window?
8+
9+
It runs once at ACP/worker startup, reads the backend's contract version (the version
10+
the server already reports via ``/openapi.json`` ``info.version``), and **fails fast with
11+
an actionable error** if the backend is older than this SDK supports — instead of the
12+
mismatch surfacing later as opaque 500s / missing-field errors deep in a request.
13+
14+
``MIN_BACKEND_CONTRACT`` is the same source of truth as the ``min-supported`` server
15+
contract in ``tests/compat/server_specs/manifest.json``: the oldest agentex backend this
16+
SDK version supports. Bump both together when a breaking change raises the floor.
17+
"""
18+
19+
from __future__ import annotations
20+
21+
import os
22+
import re
23+
24+
import httpx
25+
26+
from agentex.lib.utils.logging import make_logger
27+
28+
logger = make_logger(__name__)
29+
30+
# Oldest agentex backend contract this SDK is compatible with.
31+
# Keep in sync with the `min-supported` spec in tests/compat (#407); the version axis
32+
# itself comes from scale-agentex release tags (#321). Bump on a breaking SDK change.
33+
MIN_BACKEND_CONTRACT = "0.1.0"
34+
35+
SKIP_ENV = "AGENTEX_SKIP_VERSION_CHECK"
36+
37+
# Full-string SemVer. Accepts: `1.2.3`, leading `v`, surrounding whitespace, `-prerelease`
38+
# (captured), `+build` (ignored). Anchored at both ends so a malformed tail (`0.1.0rc1`,
39+
# `0.1.0.1`) is rejected → None → "unknown, proceed", not silently coerced to stable `0.1.0`.
40+
_VERSION_RE = re.compile(
41+
r"^\s*v?(\d+)\.(\d+)\.(\d+)" # major.minor.patch
42+
r"(?:-([0-9A-Za-z.-]+))?" # optional -prerelease (captured)
43+
r"(?:\+[0-9A-Za-z.-]+)?" # optional +build metadata (ignored)
44+
r"\s*$"
45+
)
46+
47+
48+
class IncompatibleBackendError(RuntimeError):
49+
"""Raised when the agentex backend is older than this SDK's minimum supported contract."""
50+
51+
52+
def _parse(version: str | None) -> tuple[int, int, int, str | None] | None:
53+
"""Parse ``major.minor.patch[-prerelease]`` → ``(major, minor, patch, prerelease)``.
54+
55+
``prerelease`` is the raw dot-separated identifier string (e.g. ``"rc.1"``), or None for
56+
a stable release. Build metadata (after ``+``) is ignored. Returns None if unparseable.
57+
"""
58+
m = _VERSION_RE.match(version or "")
59+
if not m:
60+
return None
61+
return (int(m.group(1)), int(m.group(2)), int(m.group(3)), m.group(4) or None)
62+
63+
64+
# Comparable SemVer precedence key. The 4th element keeps a uniform shape across stable and
65+
# prerelease so the whole tuple is orderable: (rank, identifiers), where stable rank 1 > prerelease
66+
# rank 0 (and the identifier list is only ever compared when both sides are prereleases, rank 0).
67+
_PreKey = tuple[int, int, int, tuple[int, list[tuple[int, int, str]]]]
68+
69+
70+
def _precedence_key(parsed: tuple[int, int, int, str | None]) -> _PreKey:
71+
"""SemVer §11 precedence key (directly comparable with ``<``).
72+
73+
A stable release outranks any prerelease of the same triplet (``0.1.0-rc.1 < 0.1.0``);
74+
among prereleases, numeric identifiers rank below alphanumeric and compare field-by-field,
75+
with a longer identifier list outranking a shorter prefix-equal one.
76+
"""
77+
major, minor, patch, prerelease = parsed
78+
if prerelease is None:
79+
return (major, minor, patch, (1, [])) # stable sorts above every prerelease
80+
identifiers: list[tuple[int, int, str]] = []
81+
for ident in prerelease.split("."):
82+
if ident.isdigit():
83+
identifiers.append((0, int(ident), "")) # numeric: lowest class, numeric order
84+
else:
85+
identifiers.append((1, 0, ident)) # alphanumeric: higher class, lexical order
86+
return (major, minor, patch, (0, identifiers))
87+
88+
89+
def _truthy(name: str) -> bool:
90+
return os.environ.get(name, "").strip().lower() in ("1", "true", "yes", "on")
91+
92+
93+
async def fetch_backend_version(base_url: str, *, timeout: float = 5.0) -> str | None:
94+
"""Return the backend's reported contract version (``/openapi.json`` ``info.version``), or None."""
95+
url = base_url.rstrip("/") + "/openapi.json"
96+
try:
97+
async with httpx.AsyncClient(timeout=timeout) as client:
98+
resp = await client.get(url)
99+
resp.raise_for_status()
100+
return (resp.json().get("info") or {}).get("version")
101+
except Exception as exc: # noqa: BLE001 - any failure → unknown, handled by caller
102+
logger.warning("backend version guard: could not fetch %s (%s)", url, exc)
103+
return None
104+
105+
106+
async def assert_backend_compatible(
107+
base_url: str | None,
108+
*,
109+
min_version: str = MIN_BACKEND_CONTRACT,
110+
sdk_version: str | None = None,
111+
) -> None:
112+
"""Fail fast at startup if the backend is older than ``min_version``.
113+
114+
No-op (warns, does not raise) when:
115+
- ``AGENTEX_SKIP_VERSION_CHECK`` is set (explicit bypass),
116+
- ``base_url`` is unset,
117+
- the backend version can't be determined (unreachable / unparseable) — a transient
118+
blip or a contract-less server shouldn't crash startup.
119+
120+
Raises ``IncompatibleBackendError`` only when the backend version is *known* and older
121+
than ``min_version``.
122+
"""
123+
if _truthy(SKIP_ENV):
124+
logger.warning("%s set — skipping backend version guard", SKIP_ENV)
125+
return
126+
if not base_url:
127+
return
128+
129+
if sdk_version is None:
130+
from agentex._version import __version__ as sdk_version # local import to avoid cycles
131+
132+
backend_version = await fetch_backend_version(base_url)
133+
if backend_version is None:
134+
logger.warning(
135+
"backend version guard: could not determine backend version at %s; proceeding "
136+
"(set %s=1 to silence).",
137+
base_url,
138+
SKIP_ENV,
139+
)
140+
return
141+
142+
backend, minimum = _parse(backend_version), _parse(min_version)
143+
if backend is None or minimum is None:
144+
logger.warning(
145+
"backend version guard: unparseable version(s) backend=%r min=%r; proceeding.",
146+
backend_version,
147+
min_version,
148+
)
149+
return
150+
151+
if _precedence_key(backend) < _precedence_key(minimum):
152+
raise IncompatibleBackendError(
153+
f"agentex-sdk {sdk_version} requires agentex backend >= {min_version}, "
154+
f"but {base_url} reports {backend_version}. "
155+
f"Upgrade the backend, or pin agentex-sdk to a version compatible with backend "
156+
f"{backend_version}. (Set {SKIP_ENV}=1 to bypass at your own risk.)"
157+
)
158+
159+
logger.info(
160+
"backend version guard OK: sdk=%s backend=%s (min=%s)",
161+
sdk_version,
162+
backend_version,
163+
min_version,
164+
)

src/agentex/lib/core/temporal/workers/worker.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from agentex.lib.utils.logging import make_logger
3131
from agentex.lib.utils.registration import register_agent
3232
from agentex.lib.environment_variables import EnvironmentVariables
33+
from agentex.lib.core.compat.version_guard import assert_backend_compatible
3334

3435
logger = make_logger(__name__)
3536

@@ -278,6 +279,10 @@ async def start_health_check_server(self):
278279
async def _register_agent(self):
279280
env_vars = EnvironmentVariables.refresh()
280281
if env_vars and env_vars.AGENTEX_BASE_URL:
282+
# Fail fast if this worker is pointed at a backend older than the SDK supports —
283+
# the worker process never goes through the ACP server lifespan, so it needs its
284+
# own guard (mirrors base_acp_server.lifespan_context).
285+
await assert_backend_compatible(env_vars.AGENTEX_BASE_URL)
281286
await register_agent(env_vars)
282287
else:
283288
logger.warning("AGENTEX_BASE_URL not set, skipping worker registration")

0 commit comments

Comments
 (0)