From ea34dfdca48526db61f8b5ab70a7ed9984c7dabf Mon Sep 17 00:00:00 2001 From: Edgar Arakelyan Date: Wed, 30 Sep 2026 07:36:20 -1000 Subject: [PATCH 1/3] ci: publish releases to PyPI A release tag now also uploads agentenv-protocol and agentenv-framework to PyPI through trusted publishing, from a GitHub Actions job in the `pypi` environment. It builds them the way the release does, with agentenv-framework pinned to the exact agentenv-protocol it was released with, checks the metadata renders, and skips files already on PyPI so a rerun is safe. It uses only GitHub-owned actions and uv from PyPI. The README becomes the PyPI project page, so it now installs from PyPI, keeps the clone for development, and uses absolute links and image URLs, which PyPI doesn't resolve relatively. The package description says what the SDK is. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/publish-pypi.yml | 51 +++++++++++++++++++++++++ CONTRIBUTING.md | 5 ++- README.md | 56 +++++++++++++++------------- packages/agentenv-protocol/README.md | 2 +- pyproject.toml | 2 +- 5 files changed, 87 insertions(+), 29 deletions(-) create mode 100644 .github/workflows/publish-pypi.yml diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml new file mode 100644 index 00000000..5f773e60 --- /dev/null +++ b/.github/workflows/publish-pypi.yml @@ -0,0 +1,51 @@ +name: Publish to PyPI + +# On a release tag, builds both distributions the way the release does (agentenv-framework pinned to the exact +# agentenv-protocol version it was released with) and uploads them to PyPI through trusted publishing, so no token is +# stored anywhere. agentenv-protocol goes first, since agentenv-framework pins it. Files already on PyPI are skipped, +# so a rerun is safe. + +on: + push: + tags: ["v*"] + +permissions: + contents: read + +jobs: + pypi: + 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: 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-protocol + run: uv build --no-sources packages/agentenv-protocol --out-dir dist/protocol + - name: Build agentenv-framework, pinned to that agentenv-protocol + run: | + protocol=$(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==${protocol}\"/" pyproject.toml + grep -q "\"agentenv-protocol==${protocol}\"" pyproject.toml + uv build --no-sources --out-dir dist/framework + - name: Check the distributions' metadata renders + run: uvx --index-url https://pypi.org/simple twine check --strict dist/protocol/* dist/framework/* + - name: Publish agentenv-protocol + run: uv publish --trusted-publishing always --check-url https://pypi.org/simple/ dist/protocol/* + - name: Publish agentenv-framework + run: uv publish --trusted-publishing always --check-url https://pypi.org/simple/ dist/framework/* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f3519828..4cb11203 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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-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 cebd52b7..fba5bfd8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ - - AgentEnv Framework + + AgentEnv Framework # agent-env @@ -49,7 +49,7 @@ A task deploys an environment and an agent, prompts the agent, runs one or more | `agentenv-protocol` (`packages/agentenv-protocol/`) | The wire contract plus the server and agent SDKs: `AgentEnvEnvironment`, the card, data-plane and extension decorators, and the A2A (agent-to-agent protocol) agent framework behind the `agent` extra. Depends only on `pydantic`, `starlette` and `httpx`; `mcp` is imported lazily. | Environment images and agent images. An environment image does not need `agentenv-framework`. | | `agentenv-framework` (this repository; import package `agent_env`, command `agent-env`) | The runtime and CLI: stores, image builds, the gateway, sandbox providers, task steps, verifiers, evals, the local explorer. | The machine that builds, deploys and runs. | -Every backend in `agent-env` is a seam: a TOML table naming `impl = "module.path:ClassName"` plus a `config` table. Stores (document, object, image, secret), sandbox providers, state providers, the runner, task steps, artifacts, environment kinds, CLI groups and explorer routes all plug in this way, and a bad pointer fails loudly with `ConfigError` the first time the seam is used. An installed package can also register its types through entry points, with no config at all. Conformance suites exist for the four store seams only. Details are in [Extend agent-env](#extend-agent-env); server and agent authoring depth is in [`packages/agentenv-protocol/README.md`](packages/agentenv-protocol/README.md). +Every backend in `agent-env` is a seam: a TOML table naming `impl = "module.path:ClassName"` plus a `config` table. Stores (document, object, image, secret), sandbox providers, state providers, the runner, task steps, artifacts, environment kinds, CLI groups and explorer routes all plug in this way, and a bad pointer fails loudly with `ConfigError` the first time the seam is used. An installed package can also register its types through entry points, with no config at all. Conformance suites exist for the four store seams only. Details are in [Extend agent-env](#extend-agent-env); server and agent authoring depth is in [`packages/agentenv-protocol/README.md`](https://github.com/scaleapi/agentenv-framework/blob/main/packages/agentenv-protocol/README.md). ## Install @@ -63,10 +63,16 @@ Every backend in `agent-env` is a seam: a TOML table naming `impl = "module.path ### Install the packages -Neither package is on PyPI yet. Install both from a clone. The distribution is named `agentenv-framework`; the import package is `agent_env` and the command is `agent-env`. The repository is a uv workspace, so one command installs `agentenv-framework` and `agentenv-protocol` as editable packages with their dependencies from PyPI: +Both packages are on PyPI. The distribution is named `agentenv-framework`; the import package is `agent_env` and the command is `agent-env`. It depends on `agentenv-protocol`, which installs with it: ```bash -git clone agent-env && cd agent-env +pip install agentenv-framework +``` + +To work on agent-env itself, install from a clone. The repository is a uv workspace, so one command installs `agentenv-framework` and `agentenv-protocol` as editable packages with their dependencies from PyPI: + +```bash +git clone https://github.com/scaleapi/agentenv-framework.git && cd agentenv-framework uv sync --extra dev ``` @@ -74,7 +80,7 @@ uv sync --extra dev The committed `uv.lock` is kept current: the release bump writes both packages' new versions into it, and CI fails a pull request that leaves it stale, so `uv sync --locked --extra dev` installs exactly what it records. -With plain `pip`, install the protocol package first, because no public index has it and `pip install -e '.[dev]'` on its own fails with `No matching distribution found for agentenv-protocol>=0.1.0`: +With plain `pip`, install the protocol package from the checkout first, so the editable `agentenv-framework` uses it rather than the `agentenv-protocol` release on PyPI: ```bash python3.11 -m venv .venv @@ -82,11 +88,11 @@ python3.11 -m venv .venv .venv/bin/pip install -e '.[dev]' ``` -`make install` runs those two installs (about 50 s, 410 MB); it is what [CONTRIBUTING.md](CONTRIBUTING.md) and CI use. +`make install` runs those two installs (about 50 s, 410 MB); it is what [CONTRIBUTING.md](https://github.com/scaleapi/agentenv-framework/blob/main/CONTRIBUTING.md) and CI use. ### Install the command on its own -To use `agent-env` rather than develop it, install it as a uv tool from an index that has both packages, then add plugins to it with `agent-env plugin add` (see [Manage plugins](#manage-plugins)): +To use `agent-env` rather than develop it, install it as a uv tool, then add plugins to it with `agent-env plugin add` (see [Manage plugins](#manage-plugins)): ```bash uv tool install agentenv-framework @@ -340,7 +346,7 @@ Terminating the agent container, what `close()` runs and what is left behind are ## Build an environment -An environment is an MCP server that also speaks the agent-env data plane: one Python class with decorated methods, run as a process, then packaged as a Docker image that contains `agentenv-protocol` but not `agent-env`. This section walks the bundled example in `tst/data/agentenv_mcp/`; decorator and wire-format depth is in [`packages/agentenv-protocol/README.md`](packages/agentenv-protocol/README.md). +An environment is an MCP server that also speaks the agent-env data plane: one Python class with decorated methods, run as a process, then packaged as a Docker image that contains `agentenv-protocol` but not `agent-env`. This section walks the bundled example in `tst/data/agentenv_mcp/`; decorator and wire-format depth is in [`packages/agentenv-protocol/README.md`](https://github.com/scaleapi/agentenv-framework/blob/main/packages/agentenv-protocol/README.md). ### Environment card, naming and tools @@ -436,7 +442,7 @@ ENV PYTHONPATH=/app CMD ["python", "server.py"] ``` -The Dockerfile expects `agentenv_protocol/` in the build context, and `tst/data/agentenv_mcp/` does not ship it, so building that directory directly fails at the `COPY`. Assemble a context first, as shown in [Build and register the example environment](#build-and-register-the-example-environment) (or `pip install agentenv-protocol` in the Dockerfile once published). On Apple Silicon, build with `--platform linux/arm64` for the local sandbox (see [Verify and platform notes](#verify-and-platform-notes)). +The Dockerfile expects `agentenv_protocol/` in the build context, and `tst/data/agentenv_mcp/` does not ship it, so building that directory directly fails at the `COPY`. Assemble a context first, as shown in [Build and register the example environment](#build-and-register-the-example-environment) (or `pip install agentenv-protocol` in the Dockerfile). On Apple Silicon, build with `--platform linux/arm64` for the local sandbox (see [Verify and platform notes](#verify-and-platform-notes)). ### Environment kinds @@ -751,7 +757,7 @@ In a second terminal: curl -sf http://127.0.0.1:18777/.well-known/agent-card.json ``` -Stop the server with Ctrl-C. `serve()` reads `A2A_HOST` and `A2A_PORT`. The card lists `protocolVersion 0.3.0`, the `/a2a` endpoint, and the extensions the agent implements: `urn:agentenv:agent-config/v1` (endpoint `/ext/agent-config`, accepting `model`, `system_prompt`, `max_input_chars`, `provider_token`, `role`, `name`, `description`, `timeout_seconds`) and `urn:agentenv:trajectory/v1`. Model credentials are read only when a prompt arrives, so the card serves without them. The a2a-sdk logs a deprecation for the older `/.well-known/agent.json` alias; use `agent-card.json`. agent-env itself reads the card from `/.well-known/agent.json` (`A2AAgent.deploy` polls that path for up to 300 s); an agent must keep serving it too, or `deploy_agent`, `a2a-agent deploy` and `a2a-agent validate` fail with `did not serve /.well-known/agent.json`. The protocol framework and the a2a-sdk default server apps serve both paths. For the class model, streaming, error semantics and custom extensions, see the [A2A agent framework](packages/agentenv-protocol/README.md#a2a-agent-framework) section of the protocol README. +Stop the server with Ctrl-C. `serve()` reads `A2A_HOST` and `A2A_PORT`. The card lists `protocolVersion 0.3.0`, the `/a2a` endpoint, and the extensions the agent implements: `urn:agentenv:agent-config/v1` (endpoint `/ext/agent-config`, accepting `model`, `system_prompt`, `max_input_chars`, `provider_token`, `role`, `name`, `description`, `timeout_seconds`) and `urn:agentenv:trajectory/v1`. Model credentials are read only when a prompt arrives, so the card serves without them. The a2a-sdk logs a deprecation for the older `/.well-known/agent.json` alias; use `agent-card.json`. agent-env itself reads the card from `/.well-known/agent.json` (`A2AAgent.deploy` polls that path for up to 300 s); an agent must keep serving it too, or `deploy_agent`, `a2a-agent deploy` and `a2a-agent validate` fail with `did not serve /.well-known/agent.json`. The protocol framework and the a2a-sdk default server apps serve both paths. For the class model, streaming, error semantics and custom extensions, see the [A2A agent framework](https://github.com/scaleapi/agentenv-framework/blob/main/packages/agentenv-protocol/README.md#a2a-agent-framework) section of the protocol README. ### Model configuration (owns precedence) @@ -783,7 +789,7 @@ temperature = 0.2 ### Agent configuration and agent-side triggers -`prompt_agent` sends its per-prompt configuration to the agent's `urn:agentenv:agent-config/v1` endpoint before the prompt: `model`, `system_prompt`, `effort`, `harness`, `max_turns`, `max_thinking_tokens`, `output_format`, `timeout_seconds`, `project_id`, `task_id`, `agentenv_tools`, and `model_params` (`[model.params]` merged with the step's `model_params`). The set is negotiated: only the fields the agent card lists as supported are sent, the rest are dropped silently. `agentenv_tools` is therefore an opaque list of tool names for an agent that implements in-process tools of its own; no bundled agent does, and the echo agent accepts only `model`, `system_prompt`, `project_id`, `task_id` and `model_params`. On the agent side, `register_agent_triggers` (`agent_name`, `triggers`) pushes trigger definitions to the agent's `urn:agentenv:triggers/v1` extension; environment-side triggers live on the gateway (see [Gateway and agent controls for experiments](#gateway-and-agent-controls-for-experiments)). The extension contract is in the [A2A agent framework](packages/agentenv-protocol/README.md#a2a-agent-framework) section of the protocol README. +`prompt_agent` sends its per-prompt configuration to the agent's `urn:agentenv:agent-config/v1` endpoint before the prompt: `model`, `system_prompt`, `effort`, `harness`, `max_turns`, `max_thinking_tokens`, `output_format`, `timeout_seconds`, `project_id`, `task_id`, `agentenv_tools`, and `model_params` (`[model.params]` merged with the step's `model_params`). The set is negotiated: only the fields the agent card lists as supported are sent, the rest are dropped silently. `agentenv_tools` is therefore an opaque list of tool names for an agent that implements in-process tools of its own; no bundled agent does, and the echo agent accepts only `model`, `system_prompt`, `project_id`, `task_id` and `model_params`. On the agent side, `register_agent_triggers` (`agent_name`, `triggers`) pushes trigger definitions to the agent's `urn:agentenv:triggers/v1` extension; environment-side triggers live on the gateway (see [Gateway and agent controls for experiments](#gateway-and-agent-controls-for-experiments)). The extension contract is in the [A2A agent framework](https://github.com/scaleapi/agentenv-framework/blob/main/packages/agentenv-protocol/README.md#a2a-agent-framework) section of the protocol README. ### Validate an agent @@ -1081,7 +1087,7 @@ The aggregator reads `result`, `score` and `weight`; rows flagged `skipped` are ### Trajectories -The agent trajectory is written to the object store and referenced from `prompt_responses[i].agent_trajectory_s3_uri`. The field names keep their legacy `s3` spelling, but the value is an object URL for whatever store is configured: `file://...` on the local store, `s3://...` on an S3 store. The object is whatever the agent's `urn:agentenv:trajectory/v1` extension reports for the prompt, as JSON; the format is agent-defined. An agent that advertises the object form uploads it itself through a signed grant when the store issues grants, as `S3ObjectStore` does; otherwise, and always on the local default store, the agent returns it inline and agent-env writes it (see [Object transfer](packages/agentenv-protocol/README.md#object-transfer)). The bundled echo agent reports its native format, one `{"type": "echo", "input": ..., "output": ...}` entry per prompt. An agent that reports OpenTelemetry spans stores a list of spans, each with `name`, `context`, `kind`, `parent_id`, `start_time`, `end_time`, `status`, `attributes`, `events`, `links` and `resource`; the LLM judge reads them through the GenAI semantic conventions: it selects spans by `gen_ai.operation.name` (`chat` for model turns, `execute_tool` for tool calls, `chain` for the conversation root), takes the tool label from the span `name`, the arguments from `gen_ai.prompt` (`input`) and the result from `gen_ai.completion` (`output`). No span-name convention is required; the judge compacts such trajectories before grading, and a list with no `gen_ai.operation.name` attribute is passed through unchanged. +The agent trajectory is written to the object store and referenced from `prompt_responses[i].agent_trajectory_s3_uri`. The field names keep their legacy `s3` spelling, but the value is an object URL for whatever store is configured: `file://...` on the local store, `s3://...` on an S3 store. The object is whatever the agent's `urn:agentenv:trajectory/v1` extension reports for the prompt, as JSON; the format is agent-defined. An agent that advertises the object form uploads it itself through a signed grant when the store issues grants, as `S3ObjectStore` does; otherwise, and always on the local default store, the agent returns it inline and agent-env writes it (see [Object transfer](https://github.com/scaleapi/agentenv-framework/blob/main/packages/agentenv-protocol/README.md#object-transfer)). The bundled echo agent reports its native format, one `{"type": "echo", "input": ..., "output": ...}` entry per prompt. An agent that reports OpenTelemetry spans stores a list of spans, each with `name`, `context`, `kind`, `parent_id`, `start_time`, `end_time`, `status`, `attributes`, `events`, `links` and `resource`; the LLM judge reads them through the GenAI semantic conventions: it selects spans by `gen_ai.operation.name` (`chat` for model turns, `execute_tool` for tool calls, `chain` for the conversation root), takes the tool label from the span `name`, the arguments from `gen_ai.prompt` (`input`) and the result from `gen_ai.completion` (`output`). No span-name convention is required; the judge compacts such trajectories before grading, and a list with no `gen_ai.operation.name` attribute is passed through unchanged. No CLI downloads a trajectory. Read it through the object store in Python, or via the explorer's `GET /api/v1/objects/content?object_url=...`: @@ -1471,7 +1477,7 @@ Put the bucket, the Artifact Registry repository and the machines that run agent ### What works, and what does not yet -- **Agents that take the object-transfer forms** (see [Object transfer](packages/agentenv-protocol/README.md#object-transfer)) get HTTPS grants for everything they move: skill bundles, trajectories, snapshot saves and loads, and changelog capture and replay. None needs Google credentials. A grant lasts at most as long as the signer can sign, twelve hours through IAM, so a changelog capture configured to last longer fails its step for an agent that takes only the object form, and sends an agent that also takes `s3_prefix` that form, which it cannot use here. +- **Agents that take the object-transfer forms** (see [Object transfer](https://github.com/scaleapi/agentenv-framework/blob/main/packages/agentenv-protocol/README.md#object-transfer)) get HTTPS grants for everything they move: skill bundles, trajectories, snapshot saves and loads, and changelog capture and replay. None needs Google credentials. A grant lasts at most as long as the signer can sign, twelve hours through IAM, so a changelog capture configured to last longer fails its step for an agent that takes only the object form, and sends an agent that also takes `s3_prefix` that form, which it cannot use here. - **Agents that take only the older S3-named forms** are handed `gs://` URLs as `skill_s3_url` or `s3_prefix`, which they cannot use without their own Google credentials. A legacy snapshot save also carries a `presigned_post` that Cloud Storage accepts; an agent that uploads through it instead of `s3_prefix` works. A legacy agent that returns its trajectory inline works, since agent-env uploads it; one that answers with a `trajectory_s3_prefix` it wrote itself does not. - **No credentials are shared.** agent-env hands agents and environment services no Google credentials: `GcsObjectStore` shares none, and `urn:agentenv:add-s3-credentials/v1` applies to S3 only. What they move goes through grants and signed URLs. - **Without a signer**, the store still reads and writes, but a remote sandbox receives each object over its sandbox connection instead of fetching it, trajectories come back inline, and `env snapshot`, GitHub image builds, and the skill bundles, snapshots and changelogs agents move themselves are unavailable; see [Stores and secrets](#stores-and-secrets). @@ -1600,7 +1606,7 @@ An `ImageStore`'s `owns(ref)` says whether a ref is on the store's registry, the Core calls a store from worker threads, several at once: every sign, registry login and grant runs off the event loop, since for a remote backend each is a network round trip. A store must be safe to call concurrently. -An `ObjectStore` has three optional methods that sign the HTTPS grants through which A2A agents move skill bundles, trajectories, snapshots and changelog increments without storage credentials (see [Object transfer](packages/agentenv-protocol/README.md#object-transfer)). To offer them, set `supports_transfer_grants = True`, on the class or per instance, and implement all three; agent-env requests grants only from a store that sets the flag. The defaults raise `NotImplementedError`, so one the store cannot offer raises `GrantUnavailableError` instead. `S3ObjectStore` sets the flag unless its endpoint is plain HTTP, as a local S3 emulator's usually is, because grants are HTTPS URLs. A store whose provider caps what one upload can create sets `max_single_upload_bytes`, and no write grant promises more. +An `ObjectStore` has three optional methods that sign the HTTPS grants through which A2A agents move skill bundles, trajectories, snapshots and changelog increments without storage credentials (see [Object transfer](https://github.com/scaleapi/agentenv-framework/blob/main/packages/agentenv-protocol/README.md#object-transfer)). To offer them, set `supports_transfer_grants = True`, on the class or per instance, and implement all three; agent-env requests grants only from a store that sets the flag. The defaults raise `NotImplementedError`, so one the store cannot offer raises `GrantUnavailableError` instead. `S3ObjectStore` sets the flag unless its endpoint is plain HTTP, as a local S3 emulator's usually is, because grants are HTTPS URLs. A store whose provider caps what one upload can create sets `max_single_upload_bytes`, and no write grant promises more. | Method | Returns | |---|---| @@ -1977,7 +1983,7 @@ An installer that resolves dependencies never gets you there; `pip install --no- ### Development setup and test tiers -[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. Set up with `uv sync --extra dev` or `make install` (both in [Install the packages](#install-the-packages)). Tests are tiered by path: @@ -2008,13 +2014,13 @@ cd && PYTHONPATH=. python -m pytest -p no:cacheprovider -q | Document | Covers | |---|---| -| [`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 | Full configuration, CLI, step and extension references, and the [Run on Google Cloud](#run-on-google-cloud) guide, have not been split out of this README yet. @@ -2024,8 +2030,8 @@ The `agentenv-framework` distribution and `agentenv-protocol` are versioned sepa ### 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-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/README.md b/packages/agentenv-protocol/README.md index 45f6f3f8..b0d86190 100644 --- a/packages/agentenv-protocol/README.md +++ b/packages/agentenv-protocol/README.md @@ -472,7 +472,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. diff --git a/pyproject.toml b/pyproject.toml index ea3c9f21..66df5a71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ 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"] From b3d2c1bf977c446ad7b90a2feb7dfa68a34b1510 Mon Sep 17 00:00:00 2001 From: Edgar Arakelyan Date: Wed, 30 Sep 2026 08:13:17 -1000 Subject: [PATCH 2/3] build!: rename the protocol distribution to agentenv-framework-protocol PyPI refuses `agentenv-protocol`: an unrelated `agent-env-protocol` already exists and normalizes to the same name. The protocol package is now the distribution `agentenv-framework-protocol`. Its import package stays `agentenv_protocol`, and its directory stays `packages/agentenv-protocol`. Everything that names the distribution follows: - agentenv-framework's dependency and the uv workspace source; - the release's exact-pin rewrite and the PyPI workflow, which publishes it from its own `pypi-protocol` environment; - the clean-install gate and the lock check; - the explorer's version lookup (its JSON key is unchanged); - the plugin compatibility and self-install checks, and the A2A install hints; - the docs. The runtime-dependency test now reads an editable install's import packages from its .pth instead of guessing them from the distribution name. BREAKING CHANGE: the protocol distribution is `agentenv-framework-protocol`. Plugins that constrain it should name the new distribution. Co-Authored-By: Claude Opus 5.5 (1M context) --- .circleci/config.yml | 8 +-- .github/scripts/check_clean_install.py | 2 +- .github/scripts/clean_install.py | 2 +- .github/workflows/local-backends.yml | 2 +- .github/workflows/publish-pypi.yml | 59 +++++++++++++------ AGENTS.md | 2 +- CONTRIBUTING.md | 4 +- README.md | 30 +++++----- packages/agentenv-protocol/NOTICE | 2 +- packages/agentenv-protocol/README.md | 9 +-- packages/agentenv-protocol/pyproject.toml | 2 +- .../agentenv_protocol/a2a_agent/framework.py | 6 +- pyproject.toml | 4 +- scripts/bump_version.py | 6 +- src/agent_env/cli/_installers.py | 2 +- src/agent_env/explorer/openapi_docs.py | 2 +- src/agent_env/plugins/_requirements.py | 2 +- tst/installer/conftest.py | 2 +- tst/unit/cli/plugin_install_test.py | 1 + tst/unit/plugin_requirements_test.py | 10 ++-- tst/unit/plugins_test.py | 2 +- .../test_runtime_dependencies_are_imported.py | 18 +++++- uv.lock | 8 +-- 23 files changed, 111 insertions(+), 74 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4473d7a5..8446d801 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -79,21 +79,21 @@ jobs: --region us-west-2 - run: - name: Build and publish agentenv-protocol + name: Build and publish agentenv-framework-protocol command: | 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 exact agentenv-framework-protocol version) 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 + sed -i "s/\"agentenv-framework-protocol[^\"]*\"/\"agentenv-framework-protocol==${PV}\"/" pyproject.toml python -c "import tomllib; tomllib.load(open('pyproject.toml','rb'))" - grep -q "\"agentenv-protocol==${PV}\"" pyproject.toml + grep -q "\"agentenv-framework-protocol==${PV}\"" pyproject.toml python -m build - run: 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 index 5f773e60..55860ba2 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -1,9 +1,10 @@ name: Publish to PyPI -# On a release tag, builds both distributions the way the release does (agentenv-framework pinned to the exact -# agentenv-protocol version it was released with) and uploads them to PyPI through trusted publishing, so no token is -# stored anywhere. agentenv-protocol goes first, since agentenv-framework pins it. Files already on PyPI are skipped, -# so a rerun is safe. +# 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: @@ -13,10 +14,10 @@ permissions: contents: read jobs: - pypi: + protocol: runs-on: ubuntu-latest timeout-minutes: 15 - environment: pypi + environment: pypi-protocol permissions: contents: read id-token: write @@ -33,19 +34,39 @@ jobs: 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-protocol - run: uv build --no-sources packages/agentenv-protocol --out-dir dist/protocol - - name: Build agentenv-framework, pinned to that agentenv-protocol + - 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 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==${protocol}\"/" pyproject.toml - grep -q "\"agentenv-protocol==${protocol}\"" pyproject.toml - uv build --no-sources --out-dir dist/framework - - name: Check the distributions' metadata renders - run: uvx --index-url https://pypi.org/simple twine check --strict dist/protocol/* dist/framework/* - - name: Publish agentenv-protocol - run: uv publish --trusted-publishing always --check-url https://pypi.org/simple/ dist/protocol/* + # 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/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 4cb11203..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 | @@ -80,7 +80,7 @@ Every pull request needs an approving review from a code owner (see `CODEOWNERS` Maintainers cut releases. Version bumps are automated when a labelled pull request merges, so contributors should not edit `version` in `pyproject.toml`. Each release publishes -`agentenv-framework` and `agentenv-protocol` to PyPI (`.github/workflows/publish-pypi.yml`, trusted publishing +`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 fba5bfd8..1a91910a 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ A task deploys an environment and an agent, prompts the agent, runs one or more | Package | What it is | Who installs it | |---|---|---| -| `agentenv-protocol` (`packages/agentenv-protocol/`) | The wire contract plus the server and agent SDKs: `AgentEnvEnvironment`, the card, data-plane and extension decorators, and the A2A (agent-to-agent protocol) agent framework behind the `agent` extra. Depends only on `pydantic`, `starlette` and `httpx`; `mcp` is imported lazily. | Environment images and agent images. An environment image does not need `agentenv-framework`. | +| `agentenv-framework-protocol` (`packages/agentenv-protocol/`) | The wire contract plus the server and agent SDKs: `AgentEnvEnvironment`, the card, data-plane and extension decorators, and the A2A (agent-to-agent protocol) agent framework behind the `agent` extra. Depends only on `pydantic`, `starlette` and `httpx`; `mcp` is imported lazily. | Environment images and agent images. An environment image does not need `agentenv-framework`. | | `agentenv-framework` (this repository; import package `agent_env`, command `agent-env`) | The runtime and CLI: stores, image builds, the gateway, sandbox providers, task steps, verifiers, evals, the local explorer. | The machine that builds, deploys and runs. | Every backend in `agent-env` is a seam: a TOML table naming `impl = "module.path:ClassName"` plus a `config` table. Stores (document, object, image, secret), sandbox providers, state providers, the runner, task steps, artifacts, environment kinds, CLI groups and explorer routes all plug in this way, and a bad pointer fails loudly with `ConfigError` the first time the seam is used. An installed package can also register its types through entry points, with no config at all. Conformance suites exist for the four store seams only. Details are in [Extend agent-env](#extend-agent-env); server and agent authoring depth is in [`packages/agentenv-protocol/README.md`](https://github.com/scaleapi/agentenv-framework/blob/main/packages/agentenv-protocol/README.md). @@ -63,13 +63,13 @@ Every backend in `agent-env` is a seam: a TOML table naming `impl = "module.path ### Install the packages -Both packages are on PyPI. The distribution is named `agentenv-framework`; the import package is `agent_env` and the command is `agent-env`. It depends on `agentenv-protocol`, which installs with it: +Both packages are on PyPI. The distribution is named `agentenv-framework`; the import package is `agent_env` and the command is `agent-env`. It depends on `agentenv-framework-protocol`, which installs with it: ```bash pip install agentenv-framework ``` -To work on agent-env itself, install from a clone. The repository is a uv workspace, so one command installs `agentenv-framework` and `agentenv-protocol` as editable packages with their dependencies from PyPI: +To work on agent-env itself, install from a clone. The repository is a uv workspace, so one command installs `agentenv-framework` and `agentenv-framework-protocol` as editable packages with their dependencies from PyPI: ```bash git clone https://github.com/scaleapi/agentenv-framework.git && cd agentenv-framework @@ -80,7 +80,7 @@ uv sync --extra dev The committed `uv.lock` is kept current: the release bump writes both packages' new versions into it, and CI fails a pull request that leaves it stale, so `uv sync --locked --extra dev` installs exactly what it records. -With plain `pip`, install the protocol package from the checkout first, so the editable `agentenv-framework` uses it rather than the `agentenv-protocol` release on PyPI: +With plain `pip`, install the protocol package from the checkout first, so the editable `agentenv-framework` uses it rather than the `agentenv-framework-protocol` release on PyPI: ```bash python3.11 -m venv .venv @@ -108,9 +108,9 @@ Upgrade it with `uv tool upgrade agentenv-framework`, which keeps the plugins. R | `agentenv-framework[explorer]` | `fastapi`, `uvicorn` | `agent-env up` (refuses to start without it) | | `agentenv-framework[gcp]` | `google-api-core`, `google-auth`, `google-cloud-secret-manager`, `google-cloud-storage`, `requests` | `GcsObjectStore`, `GcpSecretManagerSecretStore`, `FirestoreMongoDocumentStore`, `GoogleAccessTokenCredentials` | | `agentenv-framework[dev]` | `explorer` and `gcp` plus `moto`, `psycopg2-binary`, `pytest` and its plugins (`pytest-asyncio`, `pytest-dependency`, `pytest-socket`, `pytest-timeout`, `pytest-xdist`) | running the test suite | -| `agentenv-protocol[agent]` | `a2a-sdk[http-server]`, `uvicorn`, `regex` | authoring and serving your own A2A agent | +| `agentenv-framework-protocol[agent]` | `a2a-sdk[http-server]`, `uvicorn`, `regex` | authoring and serving your own A2A agent | -The runtime dependencies of `agentenv-framework` are `agentenv-protocol`, `boto3`, `click`, `httpx`, `litellm` (the 1.96 line), `mcp` (below 2.0), `a2a-sdk` (pinned to 0.3.26), `pydantic`, `pymongo`, `pyyaml`, `modal` and `e2b` (the 2.46 line). The cloud SDKs install every time and stay inert until a config table or a `--sandbox` flag selects them; the default sandbox and stores are local. `google-cloud-storage` and `google-cloud-secret-manager`, and what they pull in, come only with the `gcp` extra; the extra also names `google-api-core`, `google-auth` and `requests`, which core already installs, because the stores import them. +The runtime dependencies of `agentenv-framework` are `agentenv-framework-protocol`, `boto3`, `click`, `httpx`, `litellm` (the 1.96 line), `mcp` (below 2.0), `a2a-sdk` (pinned to 0.3.26), `pydantic`, `pymongo`, `pyyaml`, `modal` and `e2b` (the 2.46 line). The cloud SDKs install every time and stay inert until a config table or a `--sandbox` flag selects them; the default sandbox and stores are local. `google-cloud-storage` and `google-cloud-secret-manager`, and what they pull in, come only with the `gcp` extra; the extra also names `google-api-core`, `google-auth` and `requests`, which core already installs, because the stores import them. An extra belongs to `agentenv-framework` itself, so `agent-env plugin add` does not install one: name it where you install agent-env, as in `uv tool install 'agentenv-framework[gcp]'`, `pipx install 'agentenv-framework[gcp]'` or `pip install 'agentenv-framework[gcp]'`. Re-running `uv tool install` drops the plugins it does not name. A store impl whose extra is missing fails to load with an error that names the extra. There is no `asyncpg`; `psycopg2-binary` comes only with the `dev` extra. @@ -207,7 +207,7 @@ agent-env up ### Register an agent (the bundled echo agent) -Tasks (see [Define a task](#define-a-task)) deploy an agent by id; the default id is `a2a-default` (`[agents] default_a2a_agent_id` in `.agentenv/config.toml` changes it). An A2A agent is a container that agent-env prompts over the agent-to-agent protocol and configures through the `urn:agentenv:*` extensions; see [Agents](#agents). The repository ships no model-backed agent. It ships the deterministic agent its own test suites deploy, at `tst/data/a2a_agent/` (`Dockerfile`, `agent.py`): it answers `Echo: `, records a trajectory, and calls no tool and no model. Its Dockerfile installs `agentenv-protocol[agent]` from a copy of the protocol package in the build context, so assemble a context and register it under the default id: +Tasks (see [Define a task](#define-a-task)) deploy an agent by id; the default id is `a2a-default` (`[agents] default_a2a_agent_id` in `.agentenv/config.toml` changes it). An A2A agent is a container that agent-env prompts over the agent-to-agent protocol and configures through the `urn:agentenv:*` extensions; see [Agents](#agents). The repository ships no model-backed agent. It ships the deterministic agent its own test suites deploy, at `tst/data/a2a_agent/` (`Dockerfile`, `agent.py`): it answers `Echo: `, records a trajectory, and calls no tool and no model. Its Dockerfile installs `agentenv-framework-protocol[agent]` from a copy of the protocol package in the build context, so assemble a context and register it under the default id: ```bash mkdir echo_agent @@ -346,7 +346,7 @@ Terminating the agent container, what `close()` runs and what is left behind are ## Build an environment -An environment is an MCP server that also speaks the agent-env data plane: one Python class with decorated methods, run as a process, then packaged as a Docker image that contains `agentenv-protocol` but not `agent-env`. This section walks the bundled example in `tst/data/agentenv_mcp/`; decorator and wire-format depth is in [`packages/agentenv-protocol/README.md`](https://github.com/scaleapi/agentenv-framework/blob/main/packages/agentenv-protocol/README.md). +An environment is an MCP server that also speaks the agent-env data plane: one Python class with decorated methods, run as a process, then packaged as a Docker image that contains `agentenv-framework-protocol` but not `agent-env`. This section walks the bundled example in `tst/data/agentenv_mcp/`; decorator and wire-format depth is in [`packages/agentenv-protocol/README.md`](https://github.com/scaleapi/agentenv-framework/blob/main/packages/agentenv-protocol/README.md). ### Environment card, naming and tools @@ -442,7 +442,7 @@ ENV PYTHONPATH=/app CMD ["python", "server.py"] ``` -The Dockerfile expects `agentenv_protocol/` in the build context, and `tst/data/agentenv_mcp/` does not ship it, so building that directory directly fails at the `COPY`. Assemble a context first, as shown in [Build and register the example environment](#build-and-register-the-example-environment) (or `pip install agentenv-protocol` in the Dockerfile). On Apple Silicon, build with `--platform linux/arm64` for the local sandbox (see [Verify and platform notes](#verify-and-platform-notes)). +The Dockerfile expects `agentenv_protocol/` in the build context, and `tst/data/agentenv_mcp/` does not ship it, so building that directory directly fails at the `COPY`. Assemble a context first, as shown in [Build and register the example environment](#build-and-register-the-example-environment) (or `pip install agentenv-framework-protocol` in the Dockerfile). On Apple Silicon, build with `--platform linux/arm64` for the local sandbox (see [Verify and platform notes](#verify-and-platform-notes)). ### Environment kinds @@ -744,7 +744,7 @@ Inside a task, `deploy_agent` does the same deploy and then configures the insta ### Author your own agent -The protocol package contains the agent-side framework and runnable examples (`basic_agent.py`, `streaming_agent.py`, `multimodal_agent.py`, `custom_extensions_agent.py`, `advanced_agent.py`). Install `agentenv-protocol[agent]` and serve one: +The protocol package contains the agent-side framework and runnable examples (`basic_agent.py`, `streaming_agent.py`, `multimodal_agent.py`, `custom_extensions_agent.py`, `advanced_agent.py`). Install `agentenv-framework-protocol[agent]` and serve one: ```bash cd packages/agentenv-protocol/examples @@ -1876,7 +1876,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 | @@ -1971,7 +1971,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)`; @@ -1996,7 +1996,7 @@ Set up with `uv sync --extra dev` or `make install` (both in [Install the packag Run the unit tier from the checkout root with `AGENT_ENV_CONFIG` unset; the Makefile targets hardcode `.venv/bin/python`. The integration tiers were not run for this guide. A test may skip only for a declared capability gap, with the reason `agentenv-capability-missing: ` where `` is `model_endpoint_configured`, `remote_sandbox`, `default_a2a_agent` or `mcp_server_sources` (see `tst/util/capabilities.py`); CI rejects any other skip reason. -CI is GitHub Actions. `.github/workflows/local-backends.yml` runs the `unit`, `integration-local` and `integration-local-slow` jobs on Python 3.12, installed from public PyPI with no secrets and 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, if `agentenv-protocol` is not the editable workspace member, or if `uv.lock` is out of date, before or after a trial run of the release bump (`scripts/bump_version.py`). The public jobs skip `tst/integration/env/gateway/gateway_test.py`, which needs an x86 Chromium build and, for its virtual-clock tests, MCP server sources named by `AGENT_ENV_TEST_MCP_SERVERS_DIR`; `make int-test-slow` runs it, so run that locally when a change touches the gateway and say so in the pull request. `.github/workflows/plugin-api.yml` runs the `plugin-api` job on every pull request, including a title edit, and on `main`; it fails a break to the plugin surface that the title does not mark (see [Plugin compatibility](#plugin-compatibility)). `.github/workflows/clean-install.yml` runs the `clean-install` job on every pull request and on `main`, on Python 3.11: it builds both distributions as the release does, fails if the wheel leaves out a tracked example file or bundle entry point, installs the two wheels into a fresh venv from public PyPI with an allowlisted environment (no AWS credentials, config or plugin), runs `agent-env run hello` twice by name, and checks through the local store that both runs completed with a score of 1, left no sandbox work folder, and the second changed no artifact, task, ledger row or stored object the first wrote. The required checks on `main` are `unit`, `integration-local`, `integration-local-slow`, `installer`, `plugin-api` and `clean-install`. Dependabot (`.github/dependabot.yml`) opens weekly updates for the uv lock and the pinned actions. +CI is GitHub Actions. `.github/workflows/local-backends.yml` runs the `unit`, `integration-local` and `integration-local-slow` jobs on Python 3.12, installed from public PyPI with no secrets and 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, if `agentenv-framework-protocol` is not the editable workspace member, or if `uv.lock` is out of date, before or after a trial run of the release bump (`scripts/bump_version.py`). The public jobs skip `tst/integration/env/gateway/gateway_test.py`, which needs an x86 Chromium build and, for its virtual-clock tests, MCP server sources named by `AGENT_ENV_TEST_MCP_SERVERS_DIR`; `make int-test-slow` runs it, so run that locally when a change touches the gateway and say so in the pull request. `.github/workflows/plugin-api.yml` runs the `plugin-api` job on every pull request, including a title edit, and on `main`; it fails a break to the plugin surface that the title does not mark (see [Plugin compatibility](#plugin-compatibility)). `.github/workflows/clean-install.yml` runs the `clean-install` job on every pull request and on `main`, on Python 3.11: it builds both distributions as the release does, fails if the wheel leaves out a tracked example file or bundle entry point, installs the two wheels into a fresh venv from public PyPI with an allowlisted environment (no AWS credentials, config or plugin), runs `agent-env run hello` twice by name, and checks through the local store that both runs completed with a score of 1, left no sandbox work folder, and the second changed no artifact, task, ledger row or stored object the first wrote. The required checks on `main` are `unit`, `integration-local`, `integration-local-slow`, `installer`, `plugin-api` and `clean-install`. Dependabot (`.github/dependabot.yml`) opens weekly updates for the uv lock and the pinned actions. ### Conformance suites @@ -2026,7 +2026,7 @@ Full configuration, CLI, step and extension references, and the [Run on Google C ### 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 (see [Deprecated aliases and legacy paths](#deprecated-aliases-and-legacy-paths)). 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 (see [Deprecated aliases and legacy paths](#deprecated-aliases-and-legacy-paths)). 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 @@ -2034,4 +2034,4 @@ A release is a version bump in both `pyproject.toml` files plus a `vX.Y.Z` tag. ### 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](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-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). +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 b0d86190..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: @@ -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 66df5a71..45e2df07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ 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..0ab78ea7 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"), }, } 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 = [ From 0e5a3f1d396e1470cdb50578dd17357c20f38b6e Mon Sep 17 00:00:00 2001 From: Edgar Arakelyan Date: Wed, 30 Sep 2026 08:31:19 -1000 Subject: [PATCH 3/3] ci: keep the protocol's former name on the internal release index The release job's CodeArtifact publish builds the protocol as agentenv-protocol and pins agentenv-framework to that name. That index and its consumers don't change. PyPI gets agentenv-framework-protocol from publish-pypi.yml. The explorer's docs metadata reads the protocol version under either name. Co-Authored-By: Claude Opus 5.5 (1M context) --- .circleci/config.yml | 17 +++++++++++------ src/agent_env/explorer/openapi_docs.py | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8446d801..3ed8de58 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -79,21 +79,26 @@ jobs: --region us-west-2 - run: - name: Build and publish agentenv-framework-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-framework-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-framework-protocol[^\"]*\"/\"agentenv-framework-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-framework-protocol==${PV}\"" pyproject.toml + grep -q "\"agentenv-protocol==${PV}\"" pyproject.toml python -m build - run: diff --git a/src/agent_env/explorer/openapi_docs.py b/src/agent_env/explorer/openapi_docs.py index 0ab78ea7..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-framework-protocol"), + "agentenv-protocol": _package_version("agentenv-framework-protocol") or _package_version("agentenv-protocol"), }, }