Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/agentex-tutorials-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,11 @@ jobs:

- name: Build AgentEx SDK
run: |
echo "🔨 Building AgentEx SDK wheel..."
uv build
echo "✅ SDK built successfully"
echo "🔨 Building both SDK wheels (slim client + heavy ADK overlay)..."
# uv workspace builds both members into the root dist/. --wheel: the
# heavy's cross-dir force-include can't build via the sdist default.
uv build --all-packages --wheel
echo "✅ Both SDK wheels built successfully"
ls -la dist/

- name: Test Tutorial
Expand Down
11 changes: 8 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,14 @@ jobs:
version: '0.10.2'

- name: Install dependencies
run: uv sync --all-extras
run: uv sync --all-packages --all-extras

- name: Run lints
run: ./scripts/lint

- name: Check slim dependency set
run: ./scripts/check-slim-deps

build:
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
timeout-minutes: 10
Expand All @@ -51,10 +54,12 @@ jobs:
version: '0.10.2'

- name: Install dependencies
run: uv sync --all-extras
run: uv sync --all-packages --all-extras

- name: Run build
run: uv build
# Both workspace members. --wheel is load-bearing: the heavy's cross-dir
# force-include can't build via the sdist-then-wheel default.
run: uv build --all-packages --wheel

- name: Get GitHub OIDC Token
if: |-
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,11 @@ jobs:
run: |
bash ./bin/publish-pypi
env:
# Heavy `agentex-sdk` package token (existing PyPI name).
AGENTEX_PYPI_TOKEN: ${{ secrets.AGENTEX_PYPI_TOKEN }}
# Slim `agentex-sdk-client` package token (new PyPI name; needs
# to be added to repo secrets when the slim is registered).
AGENTEX_SDK_CLIENT_PYPI_TOKEN: ${{ secrets.AGENTEX_SDK_CLIENT_PYPI_TOKEN }}
# Back-compat fallback — used by bin/publish-pypi when the
# dedicated tokens above are unset.
PYPI_TOKEN: ${{ secrets.AGENTEX_PYPI_TOKEN || secrets.PYPI_TOKEN }}
3 changes: 2 additions & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
".": "0.12.0"
".": "0.12.0",
"adk": "0.12.0"
}
32 changes: 32 additions & 0 deletions adk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# agentex-sdk

The Agent Development Kit (ADK) overlay for the Agentex API.

## What's in here

This package ships everything under `agentex.lib.*`:

- **ACP server** (`agentex.lib.sdk.fastacp`) — FastAPI-based agent control plane.
- **Temporal workflows** (`agentex.lib.core.temporal`) — durable agent execution.
- **CLI** (`agentex.lib.cli`) — `agentex init`, `agentex run`, deploy helpers.
- **LLM provider integrations** (`agentex.lib.adk.providers`, `agentex.lib.core.temporal.plugins`) — OpenAI Agents, Claude Agent SDK, pydantic-ai, langgraph, litellm.
- **Observability** (`agentex.lib.core.tracing`, `agentex.lib.core.observability`) — SGP, Datadog, OpenTelemetry tracing processors.

## Installation

```sh
pip install agentex-sdk
```

This automatically pulls in [`agentex-sdk-client`](../) (the slim Stainless-generated REST client) so `from agentex import Agentex, AsyncAgentex` works the same as before.

## When to use this vs `agentex-sdk-client`

- **`agentex-sdk`** — you're authoring agents. Pulls everything: ACP server, Temporal, MCP, LLM providers, observability, CLI. ~37 deps.
- **`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.

The two packages contribute disjoint files to the `agentex.*` namespace — `agentex/lib/*` ships only from `agentex-sdk`.

## Repo layout

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.
41 changes: 41 additions & 0 deletions adk/hatch_build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Builds the agentex/lib force-include map per-file so test files can be pruned
— force-include ignores `exclude` (hatchling #1395)."""

from __future__ import annotations

import os

from hatchling.builders.hooks.plugin.interface import BuildHookInterface

_SKIP_DIRS = {"__pycache__", "tests"}
_SKIP_NAMES = {"conftest.py", "pytest.ini", "run_tests.py"}
# Floor below the ~333 shippable files: a collapse means the walk broke — fail
# loud rather than ship a near-empty wheel.
_MIN_FILES = 320


def _is_test_file(name: str) -> bool:
return name in _SKIP_NAMES or (name.startswith("test_") and name.endswith(".py"))


class CustomBuildHook(BuildHookInterface):
PLUGIN_NAME = "custom"

def initialize(self, version: str, build_data: dict) -> None: # noqa: ARG002
lib_root = os.path.normpath(os.path.join(self.root, "..", "src", "agentex", "lib"))
force_include = build_data.setdefault("force_include", {})
collected = 0
for dirpath, dirnames, filenames in os.walk(lib_root):
dirnames[:] = [d for d in dirnames if d not in _SKIP_DIRS]
for name in filenames:
if _is_test_file(name):
continue
src = os.path.join(dirpath, name)
rel = os.path.relpath(src, lib_root)
force_include[src] = os.path.join("agentex", "lib", rel)
collected += 1
if collected < _MIN_FILES:
raise RuntimeError(
f"agentex/lib force-include collected only {collected} files "
f"(expected >= {_MIN_FILES}); aborting build."
)
105 changes: 105 additions & 0 deletions adk/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
[project]
# Hand-authored ADK overlay for agentex. This package contributes only
# `agentex/lib/*` to the agentex.* namespace; the REST client surface
# (agentex/{__init__.py, _*.py, types/, resources/}) ships from the slim
# sibling package `agentex-sdk-client` which is pinned as a runtime dep.
#
# This entire `adk/` directory must be preserved across Stainless codegen
# via `keep_files: ["adk/**"]` in the Stainless dashboard config.
name = "agentex-sdk"
version = "0.12.0"
description = "Agent Development Kit (ADK) overlay for the Agentex API — FastACP server, Temporal workflows, LLM provider integrations, observability"
license = "Apache-2.0"
authors = [
{ name = "Agentex", email = "roxanne.farhad@scale.com" },
]
readme = "README.md"

dependencies = [
# Co-released in lockstep; floor-only by design — a ceiling would
# eventually exclude the co-versioned slim (release-please can't bump it).
"agentex-sdk-client>=0.12.0",
# CLI surface (agentex.lib.cli.*, agentex.lib.sdk.config.*)
"typer>=0.16,<0.17",
"questionary>=2.0.1,<3",
"rich>=13.9.2,<14",
"yaspin>=3.1.0",
"pyyaml>=6.0.2,<7",
"python-on-whales>=0.73.0,<0.74",
"kubernetes>=25.0.0,<36.0.0",
"jsonref>=1.1.0,<2",
"jsonschema>=4.23.0,<5",
"jinja2>=3.1.3,<4",
"watchfiles>=0.24.0,<1.0",
# ACP server (FastAPI app surface)
"fastapi>=0.115.0",
"starlette>=0.49.1",
"uvicorn>=0.31.1",
"aiohttp>=3.10.10,<4",
# Temporal workflows
"temporalio>=1.26.0,<2",
"cloudpickle>=3.1.1",
# Async streaming infra
"redis>=5.2.0,<8",
# LLM provider integrations
"litellm>=1.83.7,<2",
"openai-agents>=0.14.3,<0.15",
"openai>=2.2,<3", # Required by openai-agents; litellm now supports openai 2.x (issue #13711 resolved: https://github.com/BerriAI/litellm/issues/13711)
"claude-agent-sdk>=0.1.0",
"pydantic-ai-slim>=1.0,<2",
"langgraph-checkpoint>=2.0.0",
"scale-gp>=0.1.0a59",
"scale-gp-beta>=0.2.0",
"mcp>=1.4.1",
# Observability
"ddtrace>=3.13.0",
"opentelemetry-api>=1.20.0",
"opentelemetry-sdk>=1.20.0",
"json_log_formatter>=1.1.1",
]

# agentex/lib/* uses `from typing import override` (3.12+) in 19 files.
# The slim agentex-sdk-client keeps 3.11 support.
requires-python = ">= 3.12,<4"
classifiers = [
"Typing :: Typed",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Operating System :: OS Independent",
"Topic :: Software Development :: Libraries :: Python Modules",
"License :: OSI Approved :: Apache Software License",
]

[project.urls]
Homepage = "https://github.com/scaleapi/scale-agentex-python"
Repository = "https://github.com/scaleapi/scale-agentex-python"

[project.scripts]
agentex = "agentex.lib.cli.commands.main:app"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

# Ship only agentex/lib/*, pulled in from the parent repo's `src/agentex/lib`
# tree. The rest of agentex.* (the Stainless-generated client) ships from the
# sibling agentex-sdk-client package, which this package pins as a runtime dep.
# Stainless explicitly preserves `src/agentex/lib/` across codegen (per
# CONTRIBUTING.md), so it's safe to keep the source where it is.
[tool.hatch.build.targets.wheel]
bypass-selection = true

# Builds the ../src/agentex/lib force-include map per-file (see hatch_build.py)
# so test files can be pruned — force-include ignores `exclude` (hatchling #1395).
[tool.hatch.build.targets.wheel.hooks.custom]
path = "hatch_build.py"

# Sdist deferred: hatchling can't represent the wheel's ../src/agentex/lib
# force-include in an sdist include list. CI + bin/publish-pypi pass --wheel.
[tool.hatch.build.targets.sdist]
include = [
"/pyproject.toml",
"/README.md",
]
Comment thread
greptile-apps[bot] marked this conversation as resolved.
16 changes: 14 additions & 2 deletions bin/check-release-environment
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
#!/usr/bin/env bash

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

errors=()

if [ -z "${PYPI_TOKEN}" ]; then
errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.")
# Heavy `agentex-sdk` token (existing PyPI name).
if [ -z "${AGENTEX_PYPI_TOKEN}" ] && [ -z "${PYPI_TOKEN}" ]; then
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.")
fi

# Slim `agentex-sdk-client` token (new PyPI name).
if [ -z "${AGENTEX_SDK_CLIENT_PYPI_TOKEN}" ] && [ -z "${PYPI_TOKEN}" ]; then
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.")
fi

lenErrors=${#errors[@]}
Expand Down
13 changes: 10 additions & 3 deletions bin/publish-pypi
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
#!/usr/bin/env bash

# Publish slim (root) before heavy (adk/): heavy pins slim, so a slim-first
# failure aborts before shipping a heavy that needs an unreleased client.

set -eux

rm -rf dist
mkdir -p dist
uv build
uv publish --token=$PYPI_TOKEN
# --wheel: the heavy's cross-dir force-include can't build via sdist.
uv build --all-packages --wheel

# --check-url makes the per-component-tag double-trigger idempotent.
uv publish --check-url https://pypi.org/simple/ --token="${AGENTEX_SDK_CLIENT_PYPI_TOKEN:-$PYPI_TOKEN}" dist/agentex_sdk_client-*.whl
uv publish --check-url https://pypi.org/simple/ --token="${AGENTEX_PYPI_TOKEN:-$PYPI_TOKEN}" dist/agentex_sdk-*.whl
69 changes: 45 additions & 24 deletions examples/tutorials/run_agent_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -126,18 +126,26 @@ start_agent() {

if [ "$BUILD_CLI" = true ]; then

# Use wheel from dist directory at repo root
local wheel_file=$(ls /home/runner/work/*/*/dist/agentex_sdk-*.whl 2>/dev/null | head -n1)
if [[ -z "$wheel_file" ]]; then
echo -e "${RED}❌ No built wheel found in dist/agentex_sdk-*.whl${NC}"
echo -e "${YELLOW}💡 Please build the local SDK first by running: uv build${NC}"
echo -e "${YELLOW}💡 From the repo root directory${NC}"
# uv workspace builds both wheels into the root dist/ (slim + heavy ADK).
# We need both: heavy pins agentex-sdk-client which isn't on PyPI yet,
# so uv must resolve both from local wheels rather than the registry.
local heavy_wheel=$(ls /home/runner/work/*/*/dist/agentex_sdk-*.whl 2>/dev/null | head -n1)
local slim_wheel=$(ls /home/runner/work/*/*/dist/agentex_sdk_client-*.whl 2>/dev/null | head -n1)
if [[ -z "$heavy_wheel" ]]; then
echo -e "${RED}❌ No built heavy wheel found in dist/agentex_sdk-*.whl${NC}"
echo -e "${YELLOW}💡 Build it first: uv build --all-packages --wheel${NC}"
cd "$original_dir"
return 1
fi
if [[ -z "$slim_wheel" ]]; then
echo -e "${RED}❌ No built slim wheel found in dist/agentex_sdk_client-*.whl${NC}"
echo -e "${YELLOW}💡 Build it first: uv build --wheel${NC}"
cd "$original_dir"
return 1
fi

# Use the built wheel
uv run --with "$wheel_file" agentex agents run --manifest "$manifest_path" > "$logfile" 2>&1 &
# Pass both wheels so the local heavy resolves its slim dep locally
uv run --with "$heavy_wheel" --with "$slim_wheel" agentex agents run --manifest "$manifest_path" > "$logfile" 2>&1 &
else
uv run agentex agents run --manifest manifest.yaml > "$logfile" 2>&1 &
fi
Expand Down Expand Up @@ -269,14 +277,20 @@ run_test() {
# robust across all tutorials regardless of how each declares test deps.
local -a pytest_cmd=("uv" "run" "--with" "pytest" "--with" "pytest-asyncio" "pytest")
if [ "$BUILD_CLI" = true ]; then
local wheel_file
wheel_file=$(ls /home/runner/work/*/*/dist/agentex_sdk-*.whl 2>/dev/null | head -n1)
if [[ -z "$wheel_file" ]]; then
wheel_file=$(ls "${SCRIPT_DIR}/../../dist/agentex_sdk-*.whl" 2>/dev/null | head -n1)
local heavy_wheel slim_wheel
heavy_wheel=$(ls /home/runner/work/*/*/dist/agentex_sdk-*.whl 2>/dev/null | head -n1)
if [[ -z "$heavy_wheel" ]]; then
heavy_wheel=$(ls "${SCRIPT_DIR}"/../../dist/agentex_sdk-*.whl 2>/dev/null | head -n1)
fi
slim_wheel=$(ls /home/runner/work/*/*/dist/agentex_sdk_client-*.whl 2>/dev/null | head -n1)
if [[ -z "$slim_wheel" ]]; then
slim_wheel=$(ls "${SCRIPT_DIR}"/../../dist/agentex_sdk_client-*.whl 2>/dev/null | head -n1)
fi
if [[ -n "$wheel_file" ]]; then
pytest_cmd=("uv" "run" "--with" "$wheel_file" "--with" "pytest" "--with" "pytest-asyncio" "pytest")
if [[ -z "$heavy_wheel" || -z "$slim_wheel" ]]; then
echo -e "${RED}❌ BUILD_CLI=true but a wheel is missing (heavy='${heavy_wheel}' slim='${slim_wheel}'); refusing to test against the pre-installed SDK${NC}"
return 1
fi
pytest_cmd=("uv" "run" "--with" "$heavy_wheel" "--with" "$slim_wheel" "--with" "pytest" "--with" "pytest-asyncio" "pytest")
fi

local max_retries=5
Expand Down Expand Up @@ -350,7 +364,7 @@ execute_tutorial_test() {
fi
}

# Function to check if built wheel is available
# Function to check if both built wheels are available
check_built_wheel() {

# Navigate to the repo root (two levels up from examples/tutorials)
Expand All @@ -362,19 +376,26 @@ check_built_wheel() {
return 1
}

# Check if wheel exists in dist directory at repo root
local wheel_file=$(ls /home/runner/work/*/*/dist/agentex_sdk-*.whl 2>/dev/null | head -n1)
if [[ -z "$wheel_file" ]]; then
echo -e "${RED}❌ No built wheel found in dist/agentex_sdk-*.whl${NC}"
echo -e "${YELLOW}💡 Please build the local SDK first by running: uv build${NC}"
echo -e "${YELLOW}💡 From the repo root directory${NC}"
# Heavy ADK wheel + slim client wheel — we need both because heavy pins
# agentex-sdk-client which isn't on PyPI yet.
local heavy_wheel=$(ls /home/runner/work/*/*/dist/agentex_sdk-*.whl 2>/dev/null | head -n1)
local slim_wheel=$(ls /home/runner/work/*/*/dist/agentex_sdk_client-*.whl 2>/dev/null | head -n1)
if [[ -z "$heavy_wheel" ]]; then
echo -e "${RED}❌ No built heavy wheel found in dist/agentex_sdk-*.whl${NC}"
echo -e "${YELLOW}💡 Build it first: uv build --all-packages --wheel${NC}"
cd "$original_dir"
return 1
fi
if [[ -z "$slim_wheel" ]]; then
echo -e "${RED}❌ No built slim wheel found in dist/agentex_sdk_client-*.whl${NC}"
echo -e "${YELLOW}💡 Build it first: uv build --wheel${NC}"
cd "$original_dir"
return 1
fi

# Test the wheel by running agentex --help
if ! uv run --with "$wheel_file" agentex --help >/dev/null 2>&1; then
echo -e "${RED}❌ Failed to run agentex with built wheel${NC}"
# Test the heavy wheel by running agentex --help (uses both wheels for resolution)
if ! uv run --with "$heavy_wheel" --with "$slim_wheel" agentex --help >/dev/null 2>&1; then
echo -e "${RED}❌ Failed to run agentex with built wheels${NC}"
cd "$original_dir"
return 1
fi
Expand Down
Loading
Loading