Skip to content

Commit 715209b

Browse files
feat(packaging)!: split agentex-sdk into slim client + heavy ADK overlay
Publishes the existing wheel as two namespace-sharing packages so REST-only consumers install just the Stainless client without the ADK runtime. - agentex-sdk-client (slim, root pyproject): Stainless client + types + protocol; 6 deps; requires-python >=3.11; wheel excludes src/agentex/lib/**. - agentex-sdk (heavy, adk/): the ADK overlay (agentex/lib/*) via a hatchling build hook that force-includes ../src/agentex/lib and prunes test files (force-include ignores `exclude`, hatchling #1395); pins agentex-sdk-client floor-only; requires-python >=3.12. Heavy depends on slim, so existing `pip install agentex-sdk` consumers are unchanged. Both contribute disjoint files to the agentex.* namespace. Release/publish wiring: - release-please two-component mode (`.` + `adk/`), include-component-in-tag. - bin/publish-pypi publishes slim before heavy; both --wheel + --skip-existing. - bin/check-release-environment + publish-pypi.yml validate/pass both tokens. - scripts/check-slim-deps CI guardrail fails if the slim dep set drifts from the 6-dep base (catches Stainless re-adding ADK deps). BREAKING CHANGE: release tag scheme changes from v* to <component>-v*. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 413b755 commit 715209b

16 files changed

Lines changed: 396 additions & 275 deletions

.github/workflows/agentex-tutorials-test.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,13 @@ jobs:
124124
125125
- name: Build AgentEx SDK
126126
run: |
127-
echo "🔨 Building AgentEx SDK wheel..."
128-
uv build
129-
echo "✅ SDK built successfully"
127+
echo "🔨 Building slim agentex-sdk-client wheel (root)..."
128+
uv build --wheel
129+
echo "🔨 Building heavy agentex-sdk wheel (adk/)..."
130+
(cd adk && uv build --wheel)
131+
echo "✅ Both SDK wheels built successfully"
130132
ls -la dist/
133+
ls -la adk/dist/
131134
132135
- name: Test Tutorial
133136
id: run-test

.github/workflows/ci.yml

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,14 @@ jobs:
3232
RYE_INSTALL_OPTION: '--yes'
3333

3434
- name: Install dependencies
35-
run: rye sync --all-features
35+
run: ./scripts/bootstrap
3636

3737
- name: Run lints
3838
run: ./scripts/lint
3939

40+
- name: Check slim dependency set
41+
run: ./scripts/check-slim-deps
42+
4043
build:
4144
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
4245
timeout-minutes: 10
@@ -57,10 +60,19 @@ jobs:
5760
RYE_INSTALL_OPTION: '--yes'
5861

5962
- name: Install dependencies
60-
run: rye sync --all-features
63+
run: ./scripts/bootstrap
64+
65+
- name: Run build (slim agentex-sdk-client)
66+
# --wheel only: avoid the sdist intermediate step, which would
67+
# otherwise force the heavy build below to resolve cross-directory
68+
# paths from inside a sdist tarball.
69+
run: rye build --wheel
6170

62-
- name: Run build
63-
run: rye build
71+
- name: Run build (ADK overlay agentex-sdk)
72+
# Heavy wheel uses hatchling force-include to pull
73+
# ../src/agentex/lib into agentex/lib. Building --wheel directly
74+
# (vs sdist-then-wheel) keeps the relative path resolvable.
75+
run: (cd adk && rye build --wheel)
6476

6577
- name: Get GitHub OIDC Token
6678
if: |-

.github/workflows/publish-pypi.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,11 @@ jobs:
2828
run: |
2929
bash ./bin/publish-pypi
3030
env:
31+
# Heavy `agentex-sdk` package token (existing PyPI name).
32+
AGENTEX_PYPI_TOKEN: ${{ secrets.AGENTEX_PYPI_TOKEN }}
33+
# Slim `agentex-sdk-client` package token (new PyPI name; needs
34+
# to be added to repo secrets when the slim is registered).
35+
AGENTEX_SDK_CLIENT_PYPI_TOKEN: ${{ secrets.AGENTEX_SDK_CLIENT_PYPI_TOKEN }}
36+
# Back-compat fallback — used by bin/publish-pypi when the
37+
# dedicated tokens above are unset.
3138
PYPI_TOKEN: ${{ secrets.AGENTEX_PYPI_TOKEN || secrets.PYPI_TOKEN }}

.release-please-manifest.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
{
2-
".": "0.11.9"
2+
".": "0.11.9",
3+
"adk": "0.11.9"
34
}

adk/README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# agentex-sdk
2+
3+
The Agent Development Kit (ADK) overlay for the Agentex API.
4+
5+
## What's in here
6+
7+
This package ships everything under `agentex.lib.*`:
8+
9+
- **ACP server** (`agentex.lib.sdk.fastacp`) — FastAPI-based agent control plane.
10+
- **Temporal workflows** (`agentex.lib.core.temporal`) — durable agent execution.
11+
- **CLI** (`agentex.lib.cli`) — `agentex init`, `agentex run`, deploy helpers.
12+
- **LLM provider integrations** (`agentex.lib.adk.providers`, `agentex.lib.core.temporal.plugins`) — OpenAI Agents, Claude Agent SDK, pydantic-ai, langgraph, litellm.
13+
- **Observability** (`agentex.lib.core.tracing`, `agentex.lib.core.observability`) — SGP, Datadog, OpenTelemetry tracing processors.
14+
15+
## Installation
16+
17+
```sh
18+
pip install agentex-sdk
19+
```
20+
21+
This automatically pulls in [`agentex-sdk-client`](../) (the slim Stainless-generated REST client) so `from agentex import Agentex, AsyncAgentex` works the same as before.
22+
23+
## When to use this vs `agentex-sdk-client`
24+
25+
- **`agentex-sdk`** — you're authoring agents. Pulls everything: ACP server, Temporal, MCP, LLM providers, observability, CLI. ~37 deps.
26+
- **`agentex-sdk-client`** — you only need to call the Agentex REST API. No agent authoring, no Temporal workflows, no FastACP server, no provider integrations. 6 deps.
27+
28+
The two packages contribute disjoint files to the `agentex.*` namespace — `agentex/lib/*` ships only from `agentex-sdk`.
29+
30+
## Repo layout
31+
32+
This package is hand-authored and lives at `adk/` inside [scaleapi/scale-agentex-python](https://github.com/scaleapi/scale-agentex-python). The Stainless generator preserves `adk/**` via `keep_files` so its codegen never touches anything here. The sibling `agentex-sdk-client` package lives at the repo root and IS Stainless-generated.

adk/hatch_build.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""Builds the agentex/lib force-include map per-file so test files can be pruned
2+
— force-include ignores `exclude` (hatchling #1395)."""
3+
4+
from __future__ import annotations
5+
6+
import os
7+
8+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
9+
10+
_SKIP_DIRS = {"__pycache__", "tests"}
11+
_SKIP_NAMES = {"conftest.py", "pytest.ini", "run_tests.py"}
12+
# Floor below the ~333 shippable files: a collapse means the walk broke — fail
13+
# loud rather than ship a near-empty wheel.
14+
_MIN_FILES = 320
15+
16+
17+
def _is_test_file(name: str) -> bool:
18+
return name in _SKIP_NAMES or (name.startswith("test_") and name.endswith(".py"))
19+
20+
21+
class CustomBuildHook(BuildHookInterface):
22+
PLUGIN_NAME = "custom"
23+
24+
def initialize(self, version: str, build_data: dict) -> None: # noqa: ARG002
25+
lib_root = os.path.normpath(os.path.join(self.root, "..", "src", "agentex", "lib"))
26+
force_include = build_data.setdefault("force_include", {})
27+
collected = 0
28+
for dirpath, dirnames, filenames in os.walk(lib_root):
29+
dirnames[:] = [d for d in dirnames if d not in _SKIP_DIRS]
30+
for name in filenames:
31+
if _is_test_file(name):
32+
continue
33+
src = os.path.join(dirpath, name)
34+
rel = os.path.relpath(src, lib_root)
35+
force_include[src] = os.path.join("agentex", "lib", rel)
36+
collected += 1
37+
if collected < _MIN_FILES:
38+
raise RuntimeError(
39+
f"agentex/lib force-include collected only {collected} files "
40+
f"(expected >= {_MIN_FILES}); aborting build."
41+
)

adk/pyproject.toml

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
[project]
2+
# Hand-authored ADK overlay for agentex. This package contributes only
3+
# `agentex/lib/*` to the agentex.* namespace; the REST client surface
4+
# (agentex/{__init__.py, _*.py, types/, resources/}) ships from the slim
5+
# sibling package `agentex-sdk-client` which is pinned as a runtime dep.
6+
#
7+
# This entire `adk/` directory must be preserved across Stainless codegen
8+
# via `keep_files: ["adk/**"]` in the Stainless dashboard config.
9+
name = "agentex-sdk"
10+
version = "0.11.9"
11+
description = "Agent Development Kit (ADK) overlay for the Agentex API — FastACP server, Temporal workflows, LLM provider integrations, observability"
12+
license = "Apache-2.0"
13+
authors = [
14+
{ name = "Agentex", email = "roxanne.farhad@scale.com" },
15+
]
16+
readme = "README.md"
17+
18+
dependencies = [
19+
# Co-released in lockstep; floor-only by design — a ceiling would
20+
# eventually exclude the co-versioned slim (release-please can't bump it).
21+
"agentex-sdk-client>=0.11.9",
22+
# CLI surface (agentex.lib.cli.*, agentex.lib.sdk.config.*)
23+
"typer>=0.16,<0.17",
24+
"questionary>=2.0.1,<3",
25+
"rich>=13.9.2,<14",
26+
"yaspin>=3.1.0",
27+
"pyyaml>=6.0.2,<7",
28+
"python-on-whales>=0.73.0,<0.74",
29+
"kubernetes>=25.0.0,<36.0.0",
30+
"jsonref>=1.1.0,<2",
31+
"jsonschema>=4.23.0,<5",
32+
"jinja2>=3.1.3,<4",
33+
"watchfiles>=0.24.0,<1.0",
34+
# ACP server (FastAPI app surface)
35+
"fastapi>=0.115.0",
36+
"starlette>=0.49.1",
37+
"uvicorn>=0.31.1",
38+
"aiohttp>=3.10.10,<4",
39+
# Temporal workflows
40+
"temporalio>=1.26.0,<2",
41+
"cloudpickle>=3.1.1",
42+
# Async streaming infra
43+
"redis>=5.2.0,<8",
44+
# LLM provider integrations
45+
"litellm>=1.83.7,<2",
46+
"openai-agents>=0.14.3,<0.15",
47+
"openai>=2.2,<3", # Required by openai-agents; litellm now supports openai 2.x (issue #13711 resolved: https://github.com/BerriAI/litellm/issues/13711)
48+
"claude-agent-sdk>=0.1.0",
49+
"pydantic-ai-slim>=1.0,<2",
50+
"langgraph-checkpoint>=2.0.0",
51+
"scale-gp>=0.1.0a59",
52+
"scale-gp-beta>=0.2.0",
53+
"mcp>=1.4.1",
54+
# Observability
55+
"ddtrace>=3.13.0",
56+
"opentelemetry-api>=1.20.0",
57+
"opentelemetry-sdk>=1.20.0",
58+
"json_log_formatter>=1.1.1",
59+
]
60+
61+
# agentex/lib/* uses `from typing import override` (3.12+) in 19 files.
62+
# The slim agentex-sdk-client keeps 3.11 support.
63+
requires-python = ">= 3.12,<4"
64+
classifiers = [
65+
"Typing :: Typed",
66+
"Intended Audience :: Developers",
67+
"Programming Language :: Python :: 3.12",
68+
"Programming Language :: Python :: 3.13",
69+
"Programming Language :: Python :: 3.14",
70+
"Operating System :: OS Independent",
71+
"Topic :: Software Development :: Libraries :: Python Modules",
72+
"License :: OSI Approved :: Apache Software License",
73+
]
74+
75+
[project.urls]
76+
Homepage = "https://github.com/scaleapi/scale-agentex-python"
77+
Repository = "https://github.com/scaleapi/scale-agentex-python"
78+
79+
[project.scripts]
80+
agentex = "agentex.lib.cli.commands.main:app"
81+
82+
[build-system]
83+
requires = ["hatchling"]
84+
build-backend = "hatchling.build"
85+
86+
# Ship only agentex/lib/*, pulled in from the parent repo's `src/agentex/lib`
87+
# tree. The rest of agentex.* (the Stainless-generated client) ships from the
88+
# sibling agentex-sdk-client package, which this package pins as a runtime dep.
89+
# Stainless explicitly preserves `src/agentex/lib/` across codegen (per
90+
# CONTRIBUTING.md), so it's safe to keep the source where it is.
91+
[tool.hatch.build.targets.wheel]
92+
bypass-selection = true
93+
94+
# Builds the ../src/agentex/lib force-include map per-file (see hatch_build.py)
95+
# so test files can be pruned — force-include ignores `exclude` (hatchling #1395).
96+
[tool.hatch.build.targets.wheel.hooks.custom]
97+
path = "hatch_build.py"
98+
99+
# Sdist deferred: hatchling can't represent the wheel's ../src/agentex/lib
100+
# force-include in an sdist include list. CI + bin/publish-pypi pass --wheel.
101+
[tool.hatch.build.targets.sdist]
102+
include = [
103+
"/pyproject.toml",
104+
"/README.md",
105+
]

bin/check-release-environment

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
11
#!/usr/bin/env bash
22

3+
# This script is run by Release Doctor to validate the release environment.
4+
# After the dual-package split (slim agentex-sdk-client + heavy agentex-sdk),
5+
# both PyPI tokens must be present — one for each package name. If only
6+
# PYPI_TOKEN is set, fall back to using it for both (back-compat for legacy
7+
# single-token setups, which forces an account-scoped token).
8+
39
errors=()
410

5-
if [ -z "${PYPI_TOKEN}" ]; then
6-
errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.")
11+
# Heavy `agentex-sdk` token (existing PyPI name).
12+
if [ -z "${AGENTEX_PYPI_TOKEN}" ] && [ -z "${PYPI_TOKEN}" ]; then
13+
errors+=("The AGENTEX_PYPI_TOKEN secret has not been set (and no fallback PYPI_TOKEN). Add it in repo secrets so the heavy 'agentex-sdk' package can be published.")
14+
fi
15+
16+
# Slim `agentex-sdk-client` token (new PyPI name).
17+
if [ -z "${AGENTEX_SDK_CLIENT_PYPI_TOKEN}" ] && [ -z "${PYPI_TOKEN}" ]; then
18+
errors+=("The AGENTEX_SDK_CLIENT_PYPI_TOKEN secret has not been set (and no fallback PYPI_TOKEN). Add it in repo secrets so the slim 'agentex-sdk-client' package can be published. Falling back to PYPI_TOKEN requires an account-scoped token.")
719
fi
820

921
lenErrors=${#errors[@]}

bin/publish-pypi

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,35 @@
11
#!/usr/bin/env bash
22

3+
# Publish both the Stainless-managed slim `agentex-sdk-client` package (built
4+
# from this repo's root) and the hand-authored `agentex-sdk` ADK overlay
5+
# (built from adk/). The two packages contribute disjoint files to the same
6+
# `agentex.*` namespace; `agentex-sdk` pins `agentex-sdk-client` as a runtime
7+
# dep so installing `agentex-sdk` transitively pulls in the slim client.
8+
#
9+
# Publish ORDER matters: slim first, then heavy. Heavy's dep on slim means
10+
# anyone installing `agentex-sdk` hits resolver failure if slim hasn't shipped
11+
# yet. Failing the slim publish before the heavy gives us a chance to abort
12+
# without leaving an inconsistent registry state.
13+
#
14+
# Tokens:
15+
# - $AGENTEX_SDK_CLIENT_PYPI_TOKEN — auths publishing the slim client
16+
# - $AGENTEX_PYPI_TOKEN (alias $PYPI_TOKEN for back-compat) — auths
17+
# publishing the heavy ADK overlay (which continues to claim the
18+
# `agentex-sdk` name on PyPI)
19+
320
set -eux
21+
22+
# Slim Stainless-managed client (root) — `agentex-sdk-client` on PyPI.
423
mkdir -p dist
5-
rye build --clean
6-
rye publish --yes --token=$PYPI_TOKEN
24+
rye build --clean --wheel # --wheel: sdist deferred (see adk/pyproject.toml).
25+
rye publish --yes --skip-existing --token="${AGENTEX_SDK_CLIENT_PYPI_TOKEN:-$PYPI_TOKEN}"
26+
27+
# Heavy ADK overlay (adk/) — `agentex-sdk` on PyPI; pins the slim above.
28+
(
29+
cd adk
30+
mkdir -p dist
31+
# --wheel load-bearing: rye's sdist-then-wheel default can't resolve
32+
# the force-include of ../src/agentex/lib → silent empty wheel.
33+
rye build --clean --wheel
34+
rye publish --yes --skip-existing --token="${AGENTEX_PYPI_TOKEN:-$PYPI_TOKEN}"
35+
)

0 commit comments

Comments
 (0)