diff --git a/.circleci/config.yml b/.circleci/config.yml index 4473d7a5..3ed8de58 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -79,19 +79,24 @@ jobs: --region us-west-2 - run: - name: Build and publish agentenv-protocol + name: Build and publish the protocol package command: | + set -euo pipefail + # This index carries the protocol under its former distribution name, agentenv-protocol; + # PyPI has it as agentenv-framework-protocol (publish-pypi.yml). + sed -i 's/^name = "agentenv-framework-protocol"$/name = "agentenv-protocol"/' packages/agentenv-protocol/pyproject.toml + grep -q '^name = "agentenv-protocol"$' packages/agentenv-protocol/pyproject.toml python -m build packages/agentenv-protocol --outdir packages/agentenv-protocol/dist twine upload --repository codeartifact packages/agentenv-protocol/dist/* - run: - name: Build package (pin exact agentenv-protocol version) + name: Build package (pin the exact protocol version, by this index's name) command: | set -euo pipefail PV=$(sed -n 's/^version = "\(.*\)"/\1/p' packages/agentenv-protocol/pyproject.toml) - # Only the quoted requirement: the bare name also appears in the sdist include - # list, the uv workspace members and [tool.uv.sources], which must not be rewritten. - sed -i "s/\"agentenv-protocol[^\"]*\"/\"agentenv-protocol==${PV}\"/" pyproject.toml + # Only the quoted requirement: the directory name also appears in the sdist include list + # and the uv workspace members, and the bare name keys [tool.uv.sources]; none may change. + sed -i "s/\"agentenv-framework-protocol[^\"]*\"/\"agentenv-protocol==${PV}\"/" pyproject.toml python -c "import tomllib; tomllib.load(open('pyproject.toml','rb'))" grep -q "\"agentenv-protocol==${PV}\"" pyproject.toml python -m build diff --git a/.github/scripts/check_clean_install.py b/.github/scripts/check_clean_install.py index 73133c97..1a9040ec 100644 --- a/.github/scripts/check_clean_install.py +++ b/.github/scripts/check_clean_install.py @@ -33,7 +33,7 @@ from agent_env.task.store import TASK_INSTANCES_COLLECTION, TASKS_COLLECTION, TaskInstance, TaskStepStatus REUSED = (ARTIFACTS_COLLECTION, TASKS_COLLECTION, LEDGER_COLLECTION) -OWN_DISTRIBUTIONS = {"agentenv-framework", "agentenv-protocol"} +OWN_DISTRIBUTIONS = {"agentenv-framework", "agentenv-framework-protocol"} def main() -> int: diff --git a/.github/scripts/clean_install.py b/.github/scripts/clean_install.py index 472c6966..e35ef1f6 100644 --- a/.github/scripts/clean_install.py +++ b/.github/scripts/clean_install.py @@ -50,7 +50,7 @@ def main() -> int: for package in ("packages/agentenv-protocol", "."): _run([uv, "tool", "run", "--python", sys.executable, "--from", "build", "pyproject-build", "--outdir", str(work / "dist"), package], env=env, cwd=REPO) - protocol = _one(work / "dist", "agentenv_protocol-*.whl") + protocol = _one(work / "dist", "agentenv_framework_protocol-*.whl") framework = _one(work / "dist", "agentenv_framework-*.whl") _step(f"Check {framework.name} ships every tracked example and its entry points") diff --git a/.github/workflows/local-backends.yml b/.github/workflows/local-backends.yml index 8aa0503d..afd250c9 100644 --- a/.github/workflows/local-backends.yml +++ b/.github/workflows/local-backends.yml @@ -42,7 +42,7 @@ jobs: - name: Lockfile resolves from PyPI only run: | if grep 'source = { registry = ' uv.lock | grep -v 'registry = "https://pypi.org/simple"' | grep -q .; then echo "uv.lock has a registry source other than PyPI"; exit 1; fi - grep -A2 '^name = "agentenv-protocol"$' uv.lock | grep -q 'source = { editable = "packages/agentenv-protocol" }' + grep -A2 '^name = "agentenv-framework-protocol"$' uv.lock | grep -q 'source = { editable = "packages/agentenv-protocol" }' - name: Lockfile is current, before and after a release bump run: | uv lock --check diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml new file mode 100644 index 00000000..55860ba2 --- /dev/null +++ b/.github/workflows/publish-pypi.yml @@ -0,0 +1,72 @@ +name: Publish to PyPI + +# On a release tag, uploads agentenv-framework-protocol and then agentenv-framework to PyPI through trusted +# publishing, so no token is stored anywhere. Each job builds its distribution the way the release does +# (agentenv-framework pinned to the exact agentenv-framework-protocol it was released with) and runs in its own +# environment, because PyPI binds a pending publisher to one workflow and environment per project. Files already on +# PyPI are skipped, so a rerun is safe. + +on: + push: + tags: ["v*"] + +permissions: + contents: read + +jobs: + protocol: + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: pypi-protocol + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.12" + - name: Install uv from public PyPI + run: python -m pip install --quiet --index-url https://pypi.org/simple uv + - name: Check the tag names the version being built + run: | + version=$(sed -n 's/^version = "\(.*\)"/\1/p' pyproject.toml) + [ "v${version}" = "${GITHUB_REF_NAME}" ] || { echo "::error::tag ${GITHUB_REF_NAME}, but pyproject.toml is at ${version}"; exit 1; } + - name: Build agentenv-framework-protocol + run: uv build --no-sources packages/agentenv-protocol --out-dir dist + - name: Check its metadata renders + run: uvx --index-url https://pypi.org/simple twine check --strict dist/* + - name: Publish agentenv-framework-protocol + run: uv publish --trusted-publishing always --check-url https://pypi.org/simple/ dist/* + + framework: + needs: protocol + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: pypi + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.12" + - name: Install uv from public PyPI + run: python -m pip install --quiet --index-url https://pypi.org/simple uv + - name: Build agentenv-framework, pinned to this release's agentenv-framework-protocol + run: | + protocol=$(sed -n 's/^version = "\(.*\)"/\1/p' packages/agentenv-protocol/pyproject.toml) + # Only the quoted requirement: the directory name also appears in the sdist include list and the uv + # workspace members, and the bare name keys [tool.uv.sources]; none of those may be rewritten. + sed -i "s/\"agentenv-framework-protocol[^\"]*\"/\"agentenv-framework-protocol==${protocol}\"/" pyproject.toml + grep -q "\"agentenv-framework-protocol==${protocol}\"" pyproject.toml + uv build --no-sources --out-dir dist + - name: Check its metadata renders + run: uvx --index-url https://pypi.org/simple twine check --strict dist/* + - name: Publish agentenv-framework + run: uv publish --trusted-publishing always --check-url https://pypi.org/simple/ dist/* diff --git a/AGENTS.md b/AGENTS.md index f1b9099d..df629243 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -162,7 +162,7 @@ The required checks on pull requests and `main` are `unit`, `integration-local`, `integration-local-slow`, `installer`, `plugin-api` and `clean-install`. `.github/workflows/local-backends.yml` runs the first four, all installed from public PyPI with no secrets and no external services (a `registry:2` service container for the integration jobs). The `unit` job also fails if `uv.lock` resolves anything from a registry other than PyPI or if -`agentenv-protocol` is not the editable workspace member. The integration jobs run +`agentenv-framework-protocol` is not the editable workspace member. The integration jobs run `.github/scripts/check_skip_policy.py` over the JUnit report: only `agentenv-capability-missing` skips for the allowed capabilities pass, and the fast job allows no skips under `tst/integration/store/`. The `installer` job runs `tst/installer` when a pull request or push diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f3519828..409f4054 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,7 +37,7 @@ This is the same install CI performs. `make install` is equivalent. | Command | What it runs | Needs | |---|---|---| -| `make unit-test` | unit suite for `agentenv-framework` and `agentenv-protocol` | nothing external | +| `make unit-test` | unit suite for `agentenv-framework` and `agentenv-framework-protocol` | nothing external | | `make int-test-fast` | integration tests against the local backends | Docker | | `make int-test-slow` | tests that build images or deploy sandboxes, minutes each | Docker, and a sandbox backend for some | | `make clean-install-test` | both distributions built as the release builds them, installed into a fresh venv from public PyPI, and `agent-env run hello` run twice by name | Python 3.11, uv and git | @@ -79,8 +79,9 @@ Every pull request needs an approving review from a code owner (see `CODEOWNERS` ## Releases Maintainers cut releases. Version bumps are automated when a labelled pull request merges, so -contributors should not edit `version` in `pyproject.toml`. Releases publish the distribution -`agentenv-framework`; the import package `agent_env` and the command `agent-env` keep their names. +contributors should not edit `version` in `pyproject.toml`. Each release publishes +`agentenv-framework` and `agentenv-framework-protocol` to PyPI (`.github/workflows/publish-pypi.yml`, trusted publishing +on the release tag); the import package `agent_env` and the command `agent-env` keep their names. ## Getting help diff --git a/README.md b/README.md index fb84d2fd..ee0cfed8 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,15 @@ agent-env is a Python SDK and CLI for building, deploying and running agentic en ## Install -Neither package is on PyPI yet, so install both from a clone. You need Python 3.11 or newer, [uv](https://docs.astral.sh/uv/) and a running Docker daemon: +Both packages are on PyPI. You need Python 3.11 or newer and, to run environments locally, a running Docker daemon: + +```bash +pip install agentenv-framework +``` + +The distribution is named `agentenv-framework`, the import package is `agent_env` and the command is `agent-env`. It depends on `agentenv-framework-protocol`, whose import package is `agentenv_protocol`, and installs it too. + +To work on agent-env itself, install from a clone with [uv](https://docs.astral.sh/uv/): ```bash git clone https://github.com/scaleapi/agentenv-framework && cd agentenv-framework @@ -19,8 +27,6 @@ uv sync --extra dev source .venv/bin/activate ``` -The distribution is named `agentenv-framework`, the import package is `agent_env` and the command is `agent-env`. - ## Plugin contract What a package that extends agent-env relies on. Unit tests and the plugin-API check in CI hold the code to these sections, so a pull request that changes the contract changes this README too. @@ -201,7 +207,7 @@ A contribution that is not `active` also has a code saying why, shown in bracket |---|---|---|---| | `load-failed` | `failed` | The plugin's code raised: when it was imported (`SystemExit` included), when an explorer plugin was constructed, or in a root option's callback with the flag absent | Install what it needs, or report it to the plugin's author | | `invalid-plugin` | `failed` | It imported but does not fit its group: not a subclass of the group's base class; a `type` that is missing, inherited or not the entry-point name; a method its base requires left unimplemented; not a `click.Command` or `click.Option`; a root option that is required or exposes a value; a command click refused; an extra artifact name that reads no document. A bundle is never imported: it is invalid when its value isn't an installed, unpacked package holding that folder, when its metadata can't be read, or when the folder isn't a valid bundle (with plugins loading, one whose step, env or artifact types don't resolve) | Report it to the plugin's author; for a bundle whose package is installed zipped, reinstall it unpacked. For an extra name whose class's own type is in conflict, settle that conflict | -| `incompatible-core` | `failed` | The plugin's requirement on `agentenv-framework` or `agentenv-protocol` excludes the installed version, so it was not imported. See [Plugin compatibility](#plugin-compatibility) | Upgrade agent-env, or install a version of the plugin that fits | +| `incompatible-core` | `failed` | The plugin's requirement on `agentenv-framework` or `agentenv-framework-protocol` excludes the installed version, so it was not imported. See [Plugin compatibility](#plugin-compatibility) | Upgrade agent-env, or install a version of the plugin that fits | | `builtin-name` | `skipped` | agent-env owns the name: a built-in type, a core command or a core root option | The plugin has to rename it | | `name-conflict` | `conflict`, `skipped` | More than one entry point claims the name, from two packages or twice from one; for bundles, only twice from one package (see `qualified-only`). In a type group none of them is registered, and the rest of the group loads; two different root options on one flag are each a `conflict`, and neither is attached. A CLI command whose name a plugin loaded earlier took is `skipped`, and the earlier one stays | Remove all but one: `agent-env plugin remove PACKAGE`. A package that declares a name twice has to be fixed by its author | | `replaced-by-config` | `replaced` | The config registers another class under the name | Nothing, unless you did not mean it | @@ -296,7 +302,7 @@ agent-env ships `py.typed`, so mypy and pyright check a plugin against its annot **Declaring the agent-env your plugin needs.** Declare a floor, `agentenv-framework>=X`, where `X` is the oldest release you test against. Leave out a ceiling such as `<1`: before 1.0 it would not guard against a change in a 0.9 release, and it would keep your plugin from installing with the next major one. Pin exact versions in the application or image that installs your plugin, not in the plugin. If your plugin imports `agentenv_protocol` itself, declare that as well. -**What agent-env checks.** Before it imports a plugin, agent-env compares the plugin's requirements on `agentenv-framework` and `agentenv-protocol` with the versions installed. A plugin they exclude is not loaded: +**What agent-env checks.** Before it imports a plugin, agent-env compares the plugin's requirements on `agentenv-framework` and `agentenv-framework-protocol` with the versions installed. A plugin they exclude is not loaded: - `plugin list`, `show` and `check` report each of its contributions as `failed` with the code `incompatible-core`, with or without `--no-load`, and `check` exits 1; - using one of its types names the requirement, as in `needs agentenv-framework>=0.9.1220 (installed: 0.9.1218)`; @@ -306,29 +312,29 @@ An installer that resolves dependencies never gets you there; `pip install --no- ## Contribute, release, license -[CONTRIBUTING.md](CONTRIBUTING.md) is the contributor guide: open an issue before anything larger than a bug fix, one logical change per pull request with tests, pull request titles in the form `type(scope): summary`, an approving review from a code owner (`CODEOWNERS`) and green CI. [AGENTS.md](AGENTS.md) is the repository map and conventions file for contributors and coding agents; `CLAUDE.md` imports it. +[CONTRIBUTING.md](https://github.com/scaleapi/agentenv-framework/blob/main/CONTRIBUTING.md) is the contributor guide: open an issue before anything larger than a bug fix, one logical change per pull request with tests, pull request titles in the form `type(scope): summary`, an approving review from a code owner (`CODEOWNERS`) and green CI. [AGENTS.md](https://github.com/scaleapi/agentenv-framework/blob/main/AGENTS.md) is the repository map and conventions file for contributors and coding agents; `CLAUDE.md` imports it. ### Documentation map | Document | Covers | |---|---| | [www.agentenvframework.com/docs](https://www.agentenvframework.com/docs) | the user guide: environments, artifacts, agents, tasks, the registry and plugins | -| [`packages/agentenv-protocol/README.md`](packages/agentenv-protocol/README.md) | wire contract, environment server SDK, A2A agent framework | -| [`CONTRIBUTING.md`](CONTRIBUTING.md) | development setup, test tiers, CI jobs, pull request rules | -| [`AGENTS.md`](AGENTS.md) | repository map, configuration and extension-point summary, conventions for contributors and coding agents | -| [`SECURITY.md`](SECURITY.md) | private vulnerability reporting and supported versions | -| [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) | Contributor Covenant | -| [`.agentenv/config.example.toml`](.agentenv/config.example.toml) | the all-local configuration to copy | -| [`.env.example`](.env.example) | a commented reference of the `AGENT_ENV_*` variables; agent-env never loads this file, export what you need yourself. Its `AGENT_ENV_ENVIRONMENT` line is read only by an installed plugin, never by agent-env | +| [`packages/agentenv-protocol/README.md`](https://github.com/scaleapi/agentenv-framework/blob/main/packages/agentenv-protocol/README.md) | wire contract, environment server SDK, A2A agent framework | +| [`CONTRIBUTING.md`](https://github.com/scaleapi/agentenv-framework/blob/main/CONTRIBUTING.md) | development setup, test tiers, CI jobs, pull request rules | +| [`AGENTS.md`](https://github.com/scaleapi/agentenv-framework/blob/main/AGENTS.md) | repository map, configuration and extension-point summary, conventions for contributors and coding agents | +| [`SECURITY.md`](https://github.com/scaleapi/agentenv-framework/blob/main/SECURITY.md) | private vulnerability reporting and supported versions | +| [`CODE_OF_CONDUCT.md`](https://github.com/scaleapi/agentenv-framework/blob/main/CODE_OF_CONDUCT.md) | Contributor Covenant | +| [`.agentenv/config.example.toml`](https://github.com/scaleapi/agentenv-framework/blob/main/.agentenv/config.example.toml) | the all-local configuration to copy | +| [`.env.example`](https://github.com/scaleapi/agentenv-framework/blob/main/.env.example) | a commented reference of the `AGENT_ENV_*` variables; agent-env never loads this file, export what you need yourself. Its `AGENT_ENV_ENVIRONMENT` line is read only by an installed plugin, never by agent-env | ### Versioning and compatibility -The `agentenv-framework` distribution and `agentenv-protocol` are versioned separately (`0.9.x` and `0.1.x` today), both in their `pyproject.toml`; agent-env releases carry a `vX.Y.Z` tag, and agentenv-protocol is bumped in the same commit and has no separate tag today. There is no `agent_env.__version__` attribute; `agent-env --version` prints the installed version. Protocol extensions carry their version in the URI (`urn:agentenv:clock/v1`, `urn:agentenv:agent-config/v1`, `urn:agentenv:trajectory/v1`). One version can take more than one request shape: under `v1` the skill, trajectory, snapshot and changelog extensions accept object-transfer requests next to their older shapes, and the request field lists on an agent's card say which ones that agent takes. Renamed CLI commands are removed outright; no deprecated aliases exist at this version. What plugins may rely on, and how changes to it are made, is in [Plugin compatibility](#plugin-compatibility); the plugin commands' `--json` output has its own format version and rules ([Plugin report format](#plugin-report-format)). Beyond those, a written compatibility and deprecation policy does not exist yet. +The `agentenv-framework` distribution and `agentenv-framework-protocol` are versioned separately (`0.9.x` and `0.1.x` today), both in their `pyproject.toml`; agent-env releases carry a `vX.Y.Z` tag, and agentenv-framework-protocol is bumped in the same commit and has no separate tag today. There is no `agent_env.__version__` attribute; `agent-env --version` prints the installed version. Protocol extensions carry their version in the URI (`urn:agentenv:clock/v1`, `urn:agentenv:agent-config/v1`, `urn:agentenv:trajectory/v1`). One version can take more than one request shape: under `v1` the skill, trajectory, snapshot and changelog extensions accept object-transfer requests next to their older shapes, and the request field lists on an agent's card say which ones that agent takes. Renamed CLI commands are removed outright; no deprecated aliases exist at this version. What plugins may rely on, and how changes to it are made, is in [Plugin compatibility](#plugin-compatibility); the plugin commands' `--json` output has its own format version and rules ([Plugin report format](#plugin-report-format)). Beyond those, a written compatibility and deprecation policy does not exist yet. ### Releases -A release is a version bump in both `pyproject.toml` files plus a `vX.Y.Z` tag. Maintainers cut releases: the bump is automated when a labelled pull request merges, so contributors do not edit `version` or push tags (see the Releases section of [CONTRIBUTING.md](CONTRIBUTING.md)). Neither package is published to a public index yet, and there is no `CHANGELOG.md`. +A release is a version bump in both `pyproject.toml` files plus a `vX.Y.Z` tag. Maintainers cut releases: the bump is automated when a labelled pull request merges, so contributors do not edit `version` or push tags (see the Releases section of [CONTRIBUTING.md](https://github.com/scaleapi/agentenv-framework/blob/main/CONTRIBUTING.md)). Neither package is published to a public index yet, and there is no `CHANGELOG.md`. ### Support, security, license -Report bugs and gaps as issues against this repository, with the installed `agentenv-framework` version and the sandbox backend in use. Report vulnerabilities privately through the contact in [SECURITY.md](SECURITY.md), not in public issues; only the latest release is supported, so reproduce against it first. Contributors follow [CONTRIBUTING.md](CONTRIBUTING.md) and the [Code of Conduct](CODE_OF_CONDUCT.md); every pull request needs a code-owner review. agent-env and agentenv-protocol are licensed under the Apache License 2.0; see [`LICENSE`](LICENSE) and [`NOTICE`](NOTICE). Their third-party dependencies and those dependencies' licenses are listed in [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md). +Report bugs and gaps as issues against this repository, with the installed `agentenv-framework` version and the sandbox backend in use. Report vulnerabilities privately through the contact in [SECURITY.md](https://github.com/scaleapi/agentenv-framework/blob/main/SECURITY.md), not in public issues; only the latest release is supported, so reproduce against it first. Contributors follow [CONTRIBUTING.md](https://github.com/scaleapi/agentenv-framework/blob/main/CONTRIBUTING.md) and the [Code of Conduct](https://github.com/scaleapi/agentenv-framework/blob/main/CODE_OF_CONDUCT.md); every pull request needs a code-owner review. agent-env and agentenv-framework-protocol are licensed under the Apache License 2.0; see [`LICENSE`](https://github.com/scaleapi/agentenv-framework/blob/main/LICENSE) and [`NOTICE`](https://github.com/scaleapi/agentenv-framework/blob/main/NOTICE). Their third-party dependencies and those dependencies' licenses are listed in [`THIRD_PARTY_NOTICES.md`](https://github.com/scaleapi/agentenv-framework/blob/main/THIRD_PARTY_NOTICES.md). diff --git a/packages/agentenv-protocol/NOTICE b/packages/agentenv-protocol/NOTICE index aa3b43a7..347427f4 100644 --- a/packages/agentenv-protocol/NOTICE +++ b/packages/agentenv-protocol/NOTICE @@ -1,4 +1,4 @@ -agentenv-protocol +agentenv-framework-protocol Copyright 2026 Scale AI This product includes software developed at Scale AI (https://scale.com). diff --git a/packages/agentenv-protocol/README.md b/packages/agentenv-protocol/README.md index 45f6f3f8..1e36fb1a 100644 --- a/packages/agentenv-protocol/README.md +++ b/packages/agentenv-protocol/README.md @@ -1,6 +1,7 @@ -# agentenv-protocol +# agentenv-framework-protocol -Open data-plane protocol and server SDK for agent environments. +Open data-plane protocol and server SDK for agent environments. Install it with +`pip install agentenv-framework-protocol`; the import package is `agentenv_protocol`. An environment author writes a class with decorated methods and serves it: @@ -142,7 +143,7 @@ agent. Import the decorator from the namespace matching the application you are building. Install the optional agent dependencies with -`agentenv-protocol[agent]`. The framework generates the Agent Card, +`agentenv-framework-protocol[agent]`. The framework generates the Agent Card, extension routes, A2A task lifecycle, and detached task boundary from one agent definition: @@ -472,7 +473,7 @@ tools with (agent-env relays the env card's name: the MultiEnv's declared name, `env` + 4 random digits, giving e.g. `mcp__env4821__`); when absent the agent mints `mcp_<8 hex>`. -Runnable, self-contained reference agents live in [`examples/`](examples/): +Runnable, self-contained reference agents live in [`examples/`](https://github.com/scaleapi/agentenv-framework/tree/main/packages/agentenv-protocol/examples): the normal, streaming, and multimodal `run(request)` paths, single- and multi-operation custom extensions, and advanced ASGI-lifespan plus common SDK-operation override hooks. @@ -562,7 +563,7 @@ it signs with long-term credentials), and keeps the older `s3_prefix`, `skill_s3 agent built on this protocol therefore needs an agent-env release that includes it: an older release sends the older shapes, which such an agent refuses apart from inline skills and trajectories. Roll out in this -order: release agent-env and `agentenv-protocol` together, move every service +order: release agent-env and `agentenv-framework-protocol` together, move every service that embeds agent-env to that release, and only then build agents on the new SDK. A snapshot or changelog is restored in the form it was captured in: one captured as objects only through the object variants, an older one only diff --git a/packages/agentenv-protocol/pyproject.toml b/packages/agentenv-protocol/pyproject.toml index 3b6960af..299047d9 100644 --- a/packages/agentenv-protocol/pyproject.toml +++ b/packages/agentenv-protocol/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "agentenv-protocol" +name = "agentenv-framework-protocol" version = "0.1.268" description = "Open data-plane protocol and server SDK for agent environments." readme = "README.md" diff --git a/packages/agentenv-protocol/src/agentenv_protocol/a2a_agent/framework.py b/packages/agentenv-protocol/src/agentenv_protocol/a2a_agent/framework.py index 7fa47d44..54495f33 100644 --- a/packages/agentenv-protocol/src/agentenv_protocol/a2a_agent/framework.py +++ b/packages/agentenv-protocol/src/agentenv_protocol/a2a_agent/framework.py @@ -413,7 +413,7 @@ def serve( import uvicorn except ImportError as exc: # pragma: no cover - installation error raise ImportError( - "Serving A2A agents requires: pip install 'agentenv-protocol[agent]'" + "Serving A2A agents requires: pip install 'agentenv-framework-protocol[agent]'" ) from exc resolved_host = host or os.environ.get("A2A_HOST", "0.0.0.0") @@ -917,7 +917,7 @@ def __init__(self, agent: Any, definition: _AgentDefinition | None = None) -> No from a2a.server.tasks import InMemoryTaskStore except ImportError as exc: # pragma: no cover - depends on installation extra raise ImportError( - "A2A agent applications require: pip install 'agentenv-protocol[agent]'" + "A2A agent applications require: pip install 'agentenv-framework-protocol[agent]'" ) from exc request_handler = DefaultRequestHandler( @@ -941,7 +941,7 @@ def _build_card(self) -> Any: from a2a.types import AgentCard, AgentExtension, AgentSkill except ImportError as exc: # pragma: no cover - depends on installation extra raise ImportError( - "A2A agent applications require: pip install 'agentenv-protocol[agent]'" + "A2A agent applications require: pip install 'agentenv-framework-protocol[agent]'" ) from exc identity = self.definition.identity diff --git a/pyproject.toml b/pyproject.toml index ea3c9f21..45e2df07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,14 +5,14 @@ build-backend = "hatchling.build" [project] name = "agentenv-framework" version = "0.9.1253" -description = "Agent environment package" +description = "SDK and CLI for building, deploying and running agent environments and the tasks that grade agents in them." readme = "README.md" license = "Apache-2.0" license-files = ["LICENSE", "NOTICE", "THIRD_PARTY_NOTICES.md"] authors = [{ name = "Scale AI" }] requires-python = ">=3.11" dependencies = [ - "agentenv-protocol>=0.1.0", + "agentenv-framework-protocol>=0.1.0", "boto3>=1.35.0", "click>=8.0.0", "httpx>=0.27.0", @@ -80,7 +80,7 @@ only-include = ["src", "tst", "packages/agentenv-protocol", ".github/scripts"] members = ["packages/agentenv-protocol"] [tool.uv.sources] -agentenv-protocol = { workspace = true } +agentenv-framework-protocol = { workspace = true } [tool.pytest.ini_options] asyncio_mode = "strict" diff --git a/scripts/bump_version.py b/scripts/bump_version.py index 18d80f05..c34159ad 100644 --- a/scripts/bump_version.py +++ b/scripts/bump_version.py @@ -76,12 +76,12 @@ def main() -> int: print(f"ERROR: pyproject version '{current_str}' is not X.Y.Z", file=sys.stderr) return 1 - # agentenv-protocol's own patch version moves on every release too. + # agentenv-framework-protocol's own patch version moves on every release too. pdoc = tomlkit.parse(PROTOCOL_PYPROJECT.read_text()) pcur_str = str(pdoc["project"]["version"]) pcur = parse_semver(pcur_str) if pcur is None: - print(f"ERROR: agentenv-protocol version '{pcur_str}' is not X.Y.Z", file=sys.stderr) + print(f"ERROR: agentenv-framework-protocol version '{pcur_str}' is not X.Y.Z", file=sys.stderr) return 1 tag = highest_tag_version() @@ -108,7 +108,7 @@ def main() -> int: f"-> {new_str}", file=sys.stderr, ) - print(f"Bumped agentenv-protocol: {pcur_str} -> {pnew_str}", file=sys.stderr) + print(f"Bumped agentenv-framework-protocol: {pcur_str} -> {pnew_str}", file=sys.stderr) print("Updated both editable entries in uv.lock", file=sys.stderr) print(new_str) return 0 diff --git a/src/agent_env/cli/_installers.py b/src/agent_env/cli/_installers.py index 759b01ae..2b775b2d 100644 --- a/src/agent_env/cli/_installers.py +++ b/src/agent_env/cli/_installers.py @@ -29,7 +29,7 @@ FORCEABLE = {"uv-tool": UV_TOOL, "pipx": PIPX, "uv-project": UV_PROJECT, "pip": VIRTUALENV} # agent-env itself, the protocol package it pins exactly, and the command's own name, which a spec may # use for agent-env; `plugin add` and `remove` never change them. -CORE = ("agentenv-framework", "agentenv-protocol", "agent-env") +CORE = ("agentenv-framework", "agentenv-framework-protocol", "agentenv-protocol", "agent-env") _NAME = re.compile(r"[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?") diff --git a/src/agent_env/explorer/openapi_docs.py b/src/agent_env/explorer/openapi_docs.py index 32a3b97d..385cb860 100644 --- a/src/agent_env/explorer/openapi_docs.py +++ b/src/agent_env/explorer/openapi_docs.py @@ -237,6 +237,6 @@ def docs_metadata(schema: dict[str, Any]) -> dict[str, Any]: "openapi_version": schema.get("openapi", ""), "versions": { "agentenv-framework": _package_version("agentenv-framework"), - "agentenv-protocol": _package_version("agentenv-protocol"), + "agentenv-protocol": _package_version("agentenv-framework-protocol") or _package_version("agentenv-protocol"), }, } diff --git a/src/agent_env/plugins/_requirements.py b/src/agent_env/plugins/_requirements.py index d724ee7c..7009a2aa 100644 --- a/src/agent_env/plugins/_requirements.py +++ b/src/agent_env/plugins/_requirements.py @@ -14,7 +14,7 @@ from packaging.utils import canonicalize_name # What a plugin may constrain: agent-env, and the protocol package it pins exactly. -CORE = ("agentenv-framework", "agentenv-protocol") +CORE = ("agentenv-framework", "agentenv-framework-protocol") @functools.cache diff --git a/tst/installer/conftest.py b/tst/installer/conftest.py index c9e82ee2..19ae086f 100644 --- a/tst/installer/conftest.py +++ b/tst/installer/conftest.py @@ -1,6 +1,6 @@ """Real installers, offline: `agent-env plugin add` and `remove` in each environment setup. -Every run builds agentenv-framework and agentenv-protocol from the checkout, so it tests the +Every run builds agentenv-framework and agentenv-framework-protocol from the checkout, so it tests the change under review. Their dependencies and build backend come from a wheelhouse downloaded once per `uv.lock` and cached under `.cache/`; that download is the only step that uses the network. After it, the build and every pip, uv and pipx install run with the package index diff --git a/tst/unit/cli/plugin_install_test.py b/tst/unit/cli/plugin_install_test.py index 67743cbb..af678f90 100644 --- a/tst/unit/cli/plugin_install_test.py +++ b/tst/unit/cli/plugin_install_test.py @@ -973,6 +973,7 @@ def test_a_symlink_planted_at_the_lock_path_is_refused_and_its_target_untouched( ("agentenv-framework==0.9.2", "is agent-env itself"), ("agentenv-framework @ git+https://github.com/mycorp/agentenv-framework@main", "is agent-env itself"), ("./dist/agentenv_framework-0.9.2-py3-none-any.whl", "is agent-env itself"), + ("agentenv-framework-protocol==0.1.1", "is agent-env itself"), ("agentenv-protocol==0.1.1", "is agent-env itself"), ("agent-env", "is agent-env itself"), ]) diff --git a/tst/unit/plugin_requirements_test.py b/tst/unit/plugin_requirements_test.py index 428ec6a6..be55192a 100644 --- a/tst/unit/plugin_requirements_test.py +++ b/tst/unit/plugin_requirements_test.py @@ -13,7 +13,7 @@ def __init__(self, *requires: str): @pytest.fixture(autouse=True) def _installed(monkeypatch): - versions = {"agentenv-framework": "0.9.1218", "agentenv-protocol": "0.1.231"} + versions = {"agentenv-framework": "0.9.1218", "agentenv-framework-protocol": "0.1.231"} monkeypatch.setattr(_requirements, "installed", versions.get) @@ -21,7 +21,7 @@ def _installed(monkeypatch): [], ["agentenv-framework>=0.9.1193"], ["agentenv_framework[explorer] >=0.9"], - ["agentenv-framework>=0.9,<0.10", "agentenv-protocol==0.1.231"], + ["agentenv-framework>=0.9,<0.10", "agentenv-framework-protocol==0.1.231"], ["agentenv-framework>=999; extra == 'dev'"], ["agentenv-framework>=999; python_version < '3'"], ["requests>=999", "not a requirement !!"], @@ -34,9 +34,9 @@ def test_a_requirement_the_installed_agent_env_meets_or_that_does_not_apply_pass (["agentenv-framework>=0.9.1220"], "needs agentenv-framework>=0.9.1220 (installed: 0.9.1218)"), (["agentenv-framework<0.9.1218"], "needs agentenv-framework<0.9.1218 (installed: 0.9.1218)"), (["Agentenv.Framework==0.9.1200"], "needs agentenv-framework==0.9.1200 (installed: 0.9.1218)"), - (["agentenv-protocol>=0.2"], "needs agentenv-protocol>=0.2 (installed: 0.1.231)"), - (["agentenv-framework>=999", "agentenv-protocol>=999"], - "needs agentenv-framework>=999 (installed: 0.9.1218); agentenv-protocol>=999 (installed: 0.1.231)"), + (["agentenv-framework-protocol>=0.2"], "needs agentenv-framework-protocol>=0.2 (installed: 0.1.231)"), + (["agentenv-framework>=999", "agentenv-framework-protocol>=999"], + "needs agentenv-framework>=999 (installed: 0.9.1218); agentenv-framework-protocol>=999 (installed: 0.1.231)"), ]) def test_a_requirement_the_installed_agent_env_does_not_meet_names_both_versions(requires, why): assert incompatibility(_Dist(*requires)) == why diff --git a/tst/unit/plugins_test.py b/tst/unit/plugins_test.py index d4f8c520..62688b19 100644 --- a/tst/unit/plugins_test.py +++ b/tst/unit/plugins_test.py @@ -592,7 +592,7 @@ def imported(): def test_a_name_only_plugins_that_cannot_load_claim_says_why_it_is_missing(monkeypatch): _install(monkeypatch, envs=[ _EP("browser", "_BrowserEnv", dist="agentenv-a", requires=["agentenv-framework>=999"]), - _EP("browser", "_OtherBrowserEnv", dist="agentenv-b", requires=["agentenv-protocol>=999"]), + _EP("browser", "_OtherBrowserEnv", dist="agentenv-b", requires=["agentenv-framework-protocol>=999"]), ]) assert "browser" not in get_config().env_registry() diff --git a/tst/unit/test_runtime_dependencies_are_imported.py b/tst/unit/test_runtime_dependencies_are_imported.py index 0b8555a6..6dd17afb 100644 --- a/tst/unit/test_runtime_dependencies_are_imported.py +++ b/tst/unit/test_runtime_dependencies_are_imported.py @@ -51,8 +51,22 @@ def _import_names(distribution: str) -> frozenset[str]: top for top, dists in importlib.metadata.packages_distributions().items() if any(_normalize(d) == distribution for d in dists) } - # Editable installs (the in-repo agentenv-protocol) do not always publish a top-level map. - return frozenset(names or {distribution.replace("-", "_")}) + # Editable installs (the in-repo protocol package) publish no top-level map, and that distribution's name + # differs from its import package's, so read the packages under the directory its .pth file adds. + return frozenset(names or _editable_import_names(distribution) or {distribution.replace("-", "_")}) + + +def _editable_import_names(distribution: str) -> set[str]: + dist = importlib.metadata.distribution(distribution) + names = set() + for file in dist.files or (): + if file.suffix != ".pth": + continue + for line in Path(dist.locate_file(file)).read_text().splitlines(): + root = Path(line.strip()) + if root.is_dir(): + names |= {child.name for child in root.iterdir() if (child / "__init__.py").is_file()} + return names @functools.cache diff --git a/uv.lock b/uv.lock index 0cb48569..2e5f5350 100644 --- a/uv.lock +++ b/uv.lock @@ -15,7 +15,7 @@ resolution-markers = [ [manifest] members = [ "agentenv-framework", - "agentenv-protocol", + "agentenv-framework-protocol", ] [[package]] @@ -47,7 +47,7 @@ version = "0.9.1253" source = { editable = "." } dependencies = [ { name = "a2a-sdk" }, - { name = "agentenv-protocol" }, + { name = "agentenv-framework-protocol" }, { name = "boto3" }, { name = "click" }, { name = "e2b" }, @@ -96,7 +96,7 @@ gcp = [ requires-dist = [ { name = "a2a-sdk", specifier = "==0.3.26" }, { name = "agentenv-framework", extras = ["explorer", "gcp"], marker = "extra == 'dev'" }, - { name = "agentenv-protocol", editable = "packages/agentenv-protocol" }, + { name = "agentenv-framework-protocol", editable = "packages/agentenv-protocol" }, { name = "boto3", specifier = ">=1.35.0" }, { name = "click", specifier = ">=8.0.0" }, { name = "e2b", specifier = "~=2.46.4" }, @@ -128,7 +128,7 @@ requires-dist = [ provides-extras = ["explorer", "gcp", "dev"] [[package]] -name = "agentenv-protocol" +name = "agentenv-framework-protocol" version = "0.1.268" source = { editable = "packages/agentenv-protocol" } dependencies = [