From e9c34b87303716f3d9c0dd1e6f4201d996569ae2 Mon Sep 17 00:00:00 2001 From: Edgar Arakelyan Date: Mon, 26 Oct 2026 17:44:55 -0700 Subject: [PATCH 1/3] feat(openenv)!: make OpenEnv an optional extra with no version cap OpenEnv ships a minor release a week, so a two-minor cap expires every two weeks and holds every install of the framework to an old OpenEnv. openenv moves from the core dependencies to an `openenv` extra with a floor and no cap, as verifiers declares it, and a weekly workflow runs the OpenEnv tests against the floor, OpenEnv's latest release and its main branch. agent_env.openenv without the extra names it, and a plain install no longer pulls OpenEnv's 55 packages (gradio, pandas, numpy). BREAKING CHANGE: agent_env.openenv needs `pip install 'agentenv-framework[openenv]'`. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/openenv-compat.yml | 74 +++++++++++++++++++ AGENTS.md | 2 +- README.md | 2 +- THIRD_PARTY_NOTICES.md | 2 +- pyproject.toml | 9 ++- src/agent_env/openenv/__init__.py | 12 ++- .../test_optional_extras_stay_optional.py | 23 +++++- uv.lock | 14 +++- 8 files changed, 122 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/openenv-compat.yml diff --git a/.github/workflows/openenv-compat.yml b/.github/workflows/openenv-compat.yml new file mode 100644 index 00000000..6f2b5ac3 --- /dev/null +++ b/.github/workflows/openenv-compat.yml @@ -0,0 +1,74 @@ +name: OpenEnv compatibility + +# The openenv extra has a floor and no cap, and OpenEnv ships a minor release a week, so the OpenEnv tests run against +# the floor, OpenEnv's latest release and its main branch: weekly, after OpenEnv's Wednesday releases, and on changes +# to the code that uses it. + +on: + schedule: + - cron: "0 14 * * 4" + workflow_dispatch: + pull_request: + paths: + - src/agent_env/openenv/** + - tst/unit/openenv/** + - tst/integration/openenv/** + - pyproject.toml + - .github/workflows/openenv-compat.yml + +permissions: + contents: read + +concurrency: + group: openenv-compat-${{ github.ref }} + cancel-in-progress: true + +env: + AGENT_ENV_ENVIRONMENT: dev + PIP_DISABLE_PIP_VERSION_CHECK: "1" + +jobs: + openenv: + name: openenv ${{ matrix.openenv }} + strategy: + fail-fast: false + matrix: + openenv: [floor, release, main] + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.12" + - name: Install from uv.lock + run: | + python -m pip install --quiet -r .github/ci-requirements.txt + uv sync --locked --extra dev --python "$(command -v python)" + echo "$PWD/.venv/bin" >> "$GITHUB_PATH" + - name: Install OpenEnv (${{ matrix.openenv }}) + run: | + case "${{ matrix.openenv }}" in + floor) spec=$(python -c "import tomllib; print(next(d.replace('>=', '==') for d in tomllib.load(open('pyproject.toml', 'rb'))['project']['optional-dependencies']['openenv'] if d.startswith('openenv>=')))") ;; + release) spec="openenv" ;; + main) spec="openenv @ git+https://github.com/openenv/OpenEnv@main" ;; + esac + uv pip install --python .venv/bin/python --upgrade-package openenv "$spec" + .venv/bin/python -c "import importlib.metadata as m; print('openenv', m.version('openenv'))" + # ECR Public's anonymous pulls fail here for hours at a time. This can restart dockerd, so the registry + # starts afterwards. + - name: Pull the base images from mirror.gcr.io + run: python3 .github/scripts/mirror_base_images.py src tst + - name: Start the local registry + run: | + docker run --detach --name registry --publish 5000:5000 mirror.gcr.io/library/registry:2 + timeout 30 sh -c 'until curl -sf http://localhost:5000/v2/ >/dev/null; do sleep 1; done' + - name: OpenEnv tests + run: | + .venv/bin/python -m pytest -rA --timeout=1200 --junitxml=junit-openenv.xml \ + tst/unit/openenv tst/integration/openenv + - name: Skips must be declared capability gaps + if: always() + run: | + .venv/bin/python .github/scripts/check_skip_policy.py junit-openenv.xml \ + --allow model_endpoint_configured remote_sandbox default_a2a_agent mcp_server_sources diff --git a/AGENTS.md b/AGENTS.md index 65f1accb..200086b1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -112,7 +112,7 @@ and deserialized through a registry. | `config/` | The `Config` singleton (`get_config`, `configure`, `reset_config`) in `config/runtime.py`, file discovery in `config/loader.py`, and `load_impl`, which resolves `module:Class` pointers. `agent_env.store` re-exports the config names for compatibility. | | `providers/` | `providers/sandbox_providers/` holds the sandbox providers `local`, `modal`, `modal_vm`, `e2b`, `sail_vm` (the `sail` extra), `smol_vm` (the `smol` extra), `tensorlake` (the `tensorlake` extra), `tenki` (the `tenki` extra), `vercel` (the `vercel` extra), `boxd` (the `boxd` extra), `daytona` (the `daytona` extra); `[sandbox] default` and `agent_default` accept a comma-separated fallback chain, whose deploys skip a provider that can't run them. A sandbox provider declares what it runs (`ON_THIS_MACHINE`, and for VMs on this machine the address their requests to it arrive at, `REACHES_HOST_AT`; `CONTAINER_ACCEPTS`, `PRIVATE_NETWORK`; `SUPPORTED_SANDBOX_MODES`, derived from whether it implements `create_sandbox` and `create_vm`; and `SANDBOX_ACCEPTS`, the images an agent's sandbox runs, which defaults to those a VM loads when it implements `create_vm` and to `CONTAINER_ACCEPTS` otherwise, so only a provider that implements `create_vm` yet gives an agent a container, like the local one, declares it), each sandbox carries the `platform` of the images it runs (linux/amd64 unless its provider sets another when it creates it; None, any, unchecked, for a local sandbox), and code outside `providers/sandbox_providers/` reads those, never which provider it has. `providers/env_providers/` holds the environment providers: `EnvironmentProvider` (an env's containers and state store), `EnvironmentGatewayProvider` (`gateway`, an env's default), which renders a docker-compose for the gateway and its MCP servers inside the sandbox, and `EnvironmentServerProvider` (`server`), one MCP server in its own container with no gateway in front and no state store; `providers/env_state/` holds env-state providers (`local_postgres` built in). | | `a2a_agent/` | The `A2AAgent` entity (`a2a_agent`), its stores and the validator steps. The protocol package provides the agent-side framework. | -| `openenv/` | `build_app(bundle)`: the [OpenEnv](https://github.com/huggingface/OpenEnv) server for a bundle's tasks, each session playing one episode through the Episode API, with the envs' MCP tools, an added `end_turn` tool and the task's verifier score as the reward. It prepares bundles as `agent-env run` does (`prepare_bundle(played=True)`, without the agent the client plays) and keeps namespace routing on for the app's life. `openenv` is a hard dependency, and this package is the only one that imports it, so no other command loads it. | +| `openenv/` | `build_app(bundle)`: the [OpenEnv](https://github.com/openenv/OpenEnv) server for a bundle's tasks, each session playing one episode through the Episode API, with the envs' MCP tools, an added `end_turn` tool and the task's verifier score as the reward. It prepares bundles as `agent-env run` does (`prepare_bundle(played=True)`, without the agent the client plays) and keeps namespace routing on for the app's life. `openenv` comes with the `openenv` extra, a floor with no cap, and this package is the only one that imports it; the `openenv-compat` workflow runs its tests against OpenEnv's latest release and `main` every week. | | `runner/` | The `[runner]` seam: `Runner.submit()` returns a `RunHandle` (`run_id`, `instance_id`) before the run starts; `LocalRunner` is built in. `runner/store.py` is the run index every runner writes and the explorer reads. | | `bundle/` | A bundle is a folder of entities (`envs/`, `agents/`, `artifacts/`, `skills/`, `tasks/.json`, `evals/.toml`) that runs without a registry: `agent-env run` plans it, writes it into the `@local` namespace (reusing each version whose inputs haven't changed) and runs its evals or tasks; `agent-env bundle init` writes a stored task or eval as one, with every env, agent and artifact it uses in its own folder (each image as its build context, pruned to what its Dockerfile reads and pinned to the Python and npm versions of the stored image) so it runs with no store. | | `eval/` | `Eval` (`eval`), a versioned list of task references, and its store. | diff --git a/README.md b/README.md index 4cbd94d4..93e469d7 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ A package of your own can add env types, providers, task steps and stores to age ## OpenEnv -Every task in a bundle is an [OpenEnv](https://github.com/huggingface/OpenEnv) env. `agent_env.openenv.build_app(bundle)` returns the OpenEnv server for a bundle, a folder or an installed bundle's name, or for a mapping of split names to bundles. Any OpenEnv client resets it to a task, which deploys the task's envs and opens its agent's turn, acts through the envs' tools and an added `end_turn` tool, and gets the task's verifier score as its reward. +Every task in a bundle is an [OpenEnv](https://github.com/openenv/OpenEnv) env, with the `openenv` extra installed (`pip install 'agentenv-framework[openenv]'`). `agent_env.openenv.build_app(bundle)` returns the OpenEnv server for a bundle, a folder or an installed bundle's name, or for a mapping of split names to bundles. Any OpenEnv client resets it to a task, which deploys the task's envs and opens its agent's turn, acts through the envs' tools and an added `end_turn` tool, and gets the task's verifier score as its reward. ## Time zones diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 4933ae09..d8c2e022 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -14,7 +14,7 @@ marks, or contributor names to endorse or promote Scale AI, or related products. ## Python dependencies -The runtime dependencies of `agentenv-framework` and its `aws`, `gcp`, `sail`, `tensorlake`, `tenki`, `boxd` and `daytona` extras, at the versions resolved in `uv.lock`. Development-only dependencies are not listed. +The runtime dependencies of `agentenv-framework` and its `aws`, `gcp`, `sail`, `tensorlake`, `tenki`, `boxd`, `daytona` and `openenv` extras, at the versions resolved in `uv.lock`. Development-only dependencies are not listed. ### a2a-sdk 0.3.26 diff --git a/pyproject.toml b/pyproject.toml index 18e2a3b5..e34c8eda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,10 +46,15 @@ dependencies = [ "uvicorn>=0.47", # 0.47 adds ssl_context_factory, how the local grant server gets its TLS context "modal>=1.5.2.dev22", # dev22 adds _experimental_create(tags=); dev2 added VM-mode V2 (vm_runtime) "e2b~=2.46.4", # AsyncSandbox/AsyncTemplate APIs validated against the 2.46 line - "openenv>=0.9,<0.11", # serves bundle tasks as OpenEnv envs; the range spans OpenEnv's two-minor deprecation window ] [project.optional-dependencies] +# agent_env.openenv: bundle tasks served as OpenEnv envs. No cap: OpenEnv ships a minor a week, and the openenv-compat +# workflow runs the OpenEnv tests against its latest release and main. +openenv = [ + "fastapi>=0.116", # build_app's return type; openenv's own floor + "openenv>=0.9", +] # The AWS store backends (S3, Secrets Manager, DynamoDB, ECR), selected by impl pointer in config.toml. aws = [ "boto3>=1.35.0", @@ -89,7 +94,7 @@ daytona = [ "daytona~=0.224.0", # AsyncDaytona sandboxes, sessions, file API, preview links and egress lists validated against 0.224 ] dev = [ - "agentenv-framework[aws,boxd,daytona,gcp,sail,tensorlake,tenki,vercel]", # exercise portable optional providers and stores in CI + "agentenv-framework[aws,boxd,daytona,gcp,openenv,sail,tensorlake,tenki,vercel]", # exercise portable optional providers and stores in CI "fastmcp>=3.4,<4", # the standalone-fastmcp mount path of the protocol; <4 keeps mcp 1.x "Pillow>=10.4", # the protocol's computer extra, whose tests the unit tier runs "griffelib==2.3.0", # the plugin API check; exact, since what it reports as a break changes between releases diff --git a/src/agent_env/openenv/__init__.py b/src/agent_env/openenv/__init__.py index 92d82670..02d2afe2 100644 --- a/src/agent_env/openenv/__init__.py +++ b/src/agent_env/openenv/__init__.py @@ -1,6 +1,12 @@ -"""Every task of a bundle as an OpenEnv env (https://github.com/huggingface/OpenEnv): ``build_app`` returns the server -any OpenEnv client resets, steps and scores. The only part of agent-env that imports ``openenv``.""" +"""Every task of a bundle as an OpenEnv env (https://github.com/openenv/OpenEnv): ``build_app`` returns the server +any OpenEnv client resets, steps and scores. The only part of agent-env that imports ``openenv``, which the ``openenv`` +extra installs.""" -from .app import build_app +try: + from .app import build_app +except ModuleNotFoundError as exc: + if exc.name not in ("fastapi", "openenv"): + raise + raise ImportError("agent_env.openenv requires pip install 'agentenv-framework[openenv]'") from exc __all__ = ["build_app"] diff --git a/tst/unit/test_optional_extras_stay_optional.py b/tst/unit/test_optional_extras_stay_optional.py index 3399123c..e544071a 100644 --- a/tst/unit/test_optional_extras_stay_optional.py +++ b/tst/unit/test_optional_extras_stay_optional.py @@ -26,11 +26,11 @@ def find_spec(self, fullname, path=None, target=None): sys.meta_path.insert(0, Refuse()) """ -# Not core: the Sail and Smol providers need their optional extras, and the gateway runs only in -# its container image, and the code runner is a script that reads its arguments on import. +# Not core: the Sail and Smol providers and the OpenEnv server need their optional extras, and the gateway runs only +# in its container image, and the code runner is a script that reads its arguments on import. _NOT_CORE = ( "agent_env.providers.sandbox_providers.sail_vm", "agent_env.providers.sandbox_providers.smol_vm", - "agent_env.env.gateway", "agent_env.task_step.task_steps.run_code_runner", + "agent_env.openenv", "agent_env.env.gateway", "agent_env.task_step.task_steps.run_code_runner", ) _IMPORT_CORE = _BLOCKING + """ @@ -47,7 +47,10 @@ def imports_blocked(name): for alias in node.names ) bad = [] -for module in pkgutil.walk_packages(agent_env.__path__, "agent_env.", onerror=bad.append): +def unwalkable(name): + if not name.startswith(not_core): + bad.append(name) +for module in pkgutil.walk_packages(agent_env.__path__, "agent_env.", onerror=unwalkable): if module.name.startswith(not_core): continue try: @@ -68,6 +71,13 @@ def imports_blocked(name): print(e) """ +_IMPORT = _BLOCKING + """ +try: + importlib.import_module(sys.argv[1]) +except ImportError as e: + print(e) +""" + _STORE_PACKAGES = ( "agent_env.store", "agent_env.store.document_store", "agent_env.store.image_store", "agent_env.store.object_store", "agent_env.store.secret_store", @@ -241,6 +251,11 @@ def test_the_sail_vm_provider_without_the_extra_names_the_extra_to_install(): assert "pip install 'agentenv-framework[sail]'" in message, message +def test_the_openenv_server_without_the_extra_names_the_extra_to_install(): + message = _run(_IMPORT, _only_in("openenv"), "agent_env.openenv") + assert message.endswith("pip install 'agentenv-framework[openenv]'"), message + + @pytest.mark.parametrize("package", _STORE_PACKAGES) def test_a_backend_imported_on_first_use_is_visible_to_type_checkers(package): """A name only __getattr__ serves is Any to a type checker, which a strict consumer cannot subclass, so each one diff --git a/uv.lock b/uv.lock index fdedaf38..004f9908 100644 --- a/uv.lock +++ b/uv.lock @@ -57,7 +57,6 @@ dependencies = [ { name = "litellm" }, { name = "mcp" }, { name = "modal" }, - { name = "openenv" }, { name = "packaging" }, { name = "pydantic" }, { name = "pymongo" }, @@ -84,6 +83,7 @@ dev = [ { name = "botocore" }, { name = "boxd" }, { name = "daytona" }, + { name = "fastapi" }, { name = "fastmcp" }, { name = "google-api-core" }, { name = "google-auth" }, @@ -92,6 +92,7 @@ dev = [ { name = "griffelib" }, { name = "httpx2" }, { name = "moto" }, + { name = "openenv" }, { name = "pillow" }, { name = "psycopg2-binary" }, { name = "pytest" }, @@ -115,6 +116,10 @@ gcp = [ { name = "google-cloud-storage" }, { name = "requests" }, ] +openenv = [ + { name = "fastapi" }, + { name = "openenv" }, +] sail = [ { name = "sail" }, ] @@ -136,7 +141,7 @@ vercel = [ [package.metadata] requires-dist = [ { name = "a2a-sdk", specifier = "==0.3.26" }, - { name = "agentenv-framework", extras = ["aws", "boxd", "daytona", "gcp", "sail", "tensorlake", "tenki", "vercel"], marker = "extra == 'dev'" }, + { name = "agentenv-framework", extras = ["aws", "boxd", "daytona", "gcp", "openenv", "sail", "tensorlake", "tenki", "vercel"], marker = "extra == 'dev'" }, { name = "agentenv-framework-protocol", editable = "packages/agentenv-protocol" }, { name = "anyio", marker = "extra == 'vercel'", specifier = ">=4,<5" }, { name = "boto3", marker = "extra == 'aws'", specifier = ">=1.35.0" }, @@ -147,6 +152,7 @@ requires-dist = [ { name = "cryptography", specifier = ">=42" }, { name = "daytona", marker = "extra == 'daytona'", specifier = "~=0.224.0" }, { name = "e2b", specifier = "~=2.46.4" }, + { name = "fastapi", marker = "extra == 'openenv'", specifier = ">=0.116" }, { name = "fastmcp", marker = "extra == 'dev'", specifier = ">=3.4,<4" }, { name = "google-api-core", marker = "extra == 'gcp'", specifier = ">=2.24" }, { name = "google-auth", marker = "extra == 'gcp'", specifier = ">=2.40" }, @@ -159,7 +165,7 @@ requires-dist = [ { name = "mcp", specifier = ">=1.25,<2" }, { name = "modal", specifier = ">=1.5.2.dev22" }, { name = "moto", marker = "extra == 'dev'", specifier = ">=5.0.0" }, - { name = "openenv", specifier = ">=0.9,<0.11" }, + { name = "openenv", marker = "extra == 'openenv'", specifier = ">=0.9" }, { name = "packaging", specifier = ">=22" }, { name = "pillow", marker = "extra == 'dev'", specifier = ">=10.4" }, { name = "psycopg2-binary", marker = "extra == 'dev'", specifier = ">=2.9.0" }, @@ -184,7 +190,7 @@ requires-dist = [ { name = "uvicorn", specifier = ">=0.47" }, { name = "vercel-sandbox", marker = "extra == 'vercel'", specifier = ">=0.7.0,<0.8" }, ] -provides-extras = ["aws", "gcp", "sail", "vercel", "tensorlake", "tenki", "boxd", "daytona", "dev", "smol"] +provides-extras = ["openenv", "aws", "gcp", "sail", "vercel", "tensorlake", "tenki", "boxd", "daytona", "dev", "smol"] [[package]] name = "agentenv-framework-protocol" From baeafab8e587e3b6b1168c2d98d5650cdb48a55f Mon Sep 17 00:00:00 2001 From: Edgar Arakelyan Date: Mon, 26 Oct 2026 17:48:52 -0700 Subject: [PATCH 2/3] fix(deps): keep fastapi in core for the explorer The explorer (agent-env up) imports fastapi, which core had only through openenv since the explorer extra was retired; with openenv optional, a plain install could no longer import it. fastapi joins the core dependencies at the explorer extra's old floor, and a test imports the explorer with every extra's own modules blocked. Co-Authored-By: Claude Opus 5.5 --- pyproject.toml | 2 +- tst/unit/test_optional_extras_stay_optional.py | 4 ++++ uv.lock | 5 ++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e34c8eda..f6e01600 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,13 +46,13 @@ dependencies = [ "uvicorn>=0.47", # 0.47 adds ssl_context_factory, how the local grant server gets its TLS context "modal>=1.5.2.dev22", # dev22 adds _experimental_create(tags=); dev2 added VM-mode V2 (vm_runtime) "e2b~=2.46.4", # AsyncSandbox/AsyncTemplate APIs validated against the 2.46 line + "fastapi>=0.110", # the explorer, agent-env up ] [project.optional-dependencies] # agent_env.openenv: bundle tasks served as OpenEnv envs. No cap: OpenEnv ships a minor a week, and the openenv-compat # workflow runs the OpenEnv tests against its latest release and main. openenv = [ - "fastapi>=0.116", # build_app's return type; openenv's own floor "openenv>=0.9", ] # The AWS store backends (S3, Secrets Manager, DynamoDB, ECR), selected by impl pointer in config.toml. diff --git a/tst/unit/test_optional_extras_stay_optional.py b/tst/unit/test_optional_extras_stay_optional.py index e544071a..e8d31885 100644 --- a/tst/unit/test_optional_extras_stay_optional.py +++ b/tst/unit/test_optional_extras_stay_optional.py @@ -251,6 +251,10 @@ def test_the_sail_vm_provider_without_the_extra_names_the_extra_to_install(): assert "pip install 'agentenv-framework[sail]'" in message, message +def test_the_explorer_imports_without_any_extra(): + assert _run(_IMPORT, _not_in_core(), "agent_env.explorer.app") == "" + + def test_the_openenv_server_without_the_extra_names_the_extra_to_install(): message = _run(_IMPORT, _only_in("openenv"), "agent_env.openenv") assert message.endswith("pip install 'agentenv-framework[openenv]'"), message diff --git a/uv.lock b/uv.lock index 004f9908..2d87275f 100644 --- a/uv.lock +++ b/uv.lock @@ -53,6 +53,7 @@ dependencies = [ { name = "click" }, { name = "cryptography" }, { name = "e2b" }, + { name = "fastapi" }, { name = "httpx" }, { name = "litellm" }, { name = "mcp" }, @@ -83,7 +84,6 @@ dev = [ { name = "botocore" }, { name = "boxd" }, { name = "daytona" }, - { name = "fastapi" }, { name = "fastmcp" }, { name = "google-api-core" }, { name = "google-auth" }, @@ -117,7 +117,6 @@ gcp = [ { name = "requests" }, ] openenv = [ - { name = "fastapi" }, { name = "openenv" }, ] sail = [ @@ -152,7 +151,7 @@ requires-dist = [ { name = "cryptography", specifier = ">=42" }, { name = "daytona", marker = "extra == 'daytona'", specifier = "~=0.224.0" }, { name = "e2b", specifier = "~=2.46.4" }, - { name = "fastapi", marker = "extra == 'openenv'", specifier = ">=0.116" }, + { name = "fastapi", specifier = ">=0.110" }, { name = "fastmcp", marker = "extra == 'dev'", specifier = ">=3.4,<4" }, { name = "google-api-core", marker = "extra == 'gcp'", specifier = ">=2.24" }, { name = "google-auth", marker = "extra == 'gcp'", specifier = ">=2.40" }, From 3f1dd6331b935b1c669fc32c81085d1d957ac65d Mon Sep 17 00:00:00 2001 From: Edgar Arakelyan Date: Mon, 26 Oct 2026 20:03:16 -0700 Subject: [PATCH 3/3] ci: drop the scheduled OpenEnv compatibility workflow Keep it simple: the normal jobs test the OpenEnv that uv.lock pins, and a break a new OpenEnv release brings is fixed when it's found. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/openenv-compat.yml | 74 ---------------------------- AGENTS.md | 2 +- pyproject.toml | 3 +- 3 files changed, 2 insertions(+), 77 deletions(-) delete mode 100644 .github/workflows/openenv-compat.yml diff --git a/.github/workflows/openenv-compat.yml b/.github/workflows/openenv-compat.yml deleted file mode 100644 index 6f2b5ac3..00000000 --- a/.github/workflows/openenv-compat.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: OpenEnv compatibility - -# The openenv extra has a floor and no cap, and OpenEnv ships a minor release a week, so the OpenEnv tests run against -# the floor, OpenEnv's latest release and its main branch: weekly, after OpenEnv's Wednesday releases, and on changes -# to the code that uses it. - -on: - schedule: - - cron: "0 14 * * 4" - workflow_dispatch: - pull_request: - paths: - - src/agent_env/openenv/** - - tst/unit/openenv/** - - tst/integration/openenv/** - - pyproject.toml - - .github/workflows/openenv-compat.yml - -permissions: - contents: read - -concurrency: - group: openenv-compat-${{ github.ref }} - cancel-in-progress: true - -env: - AGENT_ENV_ENVIRONMENT: dev - PIP_DISABLE_PIP_VERSION_CHECK: "1" - -jobs: - openenv: - name: openenv ${{ matrix.openenv }} - strategy: - fail-fast: false - matrix: - openenv: [floor, release, main] - runs-on: ubuntu-latest - timeout-minutes: 45 - steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - with: - python-version: "3.12" - - name: Install from uv.lock - run: | - python -m pip install --quiet -r .github/ci-requirements.txt - uv sync --locked --extra dev --python "$(command -v python)" - echo "$PWD/.venv/bin" >> "$GITHUB_PATH" - - name: Install OpenEnv (${{ matrix.openenv }}) - run: | - case "${{ matrix.openenv }}" in - floor) spec=$(python -c "import tomllib; print(next(d.replace('>=', '==') for d in tomllib.load(open('pyproject.toml', 'rb'))['project']['optional-dependencies']['openenv'] if d.startswith('openenv>=')))") ;; - release) spec="openenv" ;; - main) spec="openenv @ git+https://github.com/openenv/OpenEnv@main" ;; - esac - uv pip install --python .venv/bin/python --upgrade-package openenv "$spec" - .venv/bin/python -c "import importlib.metadata as m; print('openenv', m.version('openenv'))" - # ECR Public's anonymous pulls fail here for hours at a time. This can restart dockerd, so the registry - # starts afterwards. - - name: Pull the base images from mirror.gcr.io - run: python3 .github/scripts/mirror_base_images.py src tst - - name: Start the local registry - run: | - docker run --detach --name registry --publish 5000:5000 mirror.gcr.io/library/registry:2 - timeout 30 sh -c 'until curl -sf http://localhost:5000/v2/ >/dev/null; do sleep 1; done' - - name: OpenEnv tests - run: | - .venv/bin/python -m pytest -rA --timeout=1200 --junitxml=junit-openenv.xml \ - tst/unit/openenv tst/integration/openenv - - name: Skips must be declared capability gaps - if: always() - run: | - .venv/bin/python .github/scripts/check_skip_policy.py junit-openenv.xml \ - --allow model_endpoint_configured remote_sandbox default_a2a_agent mcp_server_sources diff --git a/AGENTS.md b/AGENTS.md index 200086b1..3172f87b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -112,7 +112,7 @@ and deserialized through a registry. | `config/` | The `Config` singleton (`get_config`, `configure`, `reset_config`) in `config/runtime.py`, file discovery in `config/loader.py`, and `load_impl`, which resolves `module:Class` pointers. `agent_env.store` re-exports the config names for compatibility. | | `providers/` | `providers/sandbox_providers/` holds the sandbox providers `local`, `modal`, `modal_vm`, `e2b`, `sail_vm` (the `sail` extra), `smol_vm` (the `smol` extra), `tensorlake` (the `tensorlake` extra), `tenki` (the `tenki` extra), `vercel` (the `vercel` extra), `boxd` (the `boxd` extra), `daytona` (the `daytona` extra); `[sandbox] default` and `agent_default` accept a comma-separated fallback chain, whose deploys skip a provider that can't run them. A sandbox provider declares what it runs (`ON_THIS_MACHINE`, and for VMs on this machine the address their requests to it arrive at, `REACHES_HOST_AT`; `CONTAINER_ACCEPTS`, `PRIVATE_NETWORK`; `SUPPORTED_SANDBOX_MODES`, derived from whether it implements `create_sandbox` and `create_vm`; and `SANDBOX_ACCEPTS`, the images an agent's sandbox runs, which defaults to those a VM loads when it implements `create_vm` and to `CONTAINER_ACCEPTS` otherwise, so only a provider that implements `create_vm` yet gives an agent a container, like the local one, declares it), each sandbox carries the `platform` of the images it runs (linux/amd64 unless its provider sets another when it creates it; None, any, unchecked, for a local sandbox), and code outside `providers/sandbox_providers/` reads those, never which provider it has. `providers/env_providers/` holds the environment providers: `EnvironmentProvider` (an env's containers and state store), `EnvironmentGatewayProvider` (`gateway`, an env's default), which renders a docker-compose for the gateway and its MCP servers inside the sandbox, and `EnvironmentServerProvider` (`server`), one MCP server in its own container with no gateway in front and no state store; `providers/env_state/` holds env-state providers (`local_postgres` built in). | | `a2a_agent/` | The `A2AAgent` entity (`a2a_agent`), its stores and the validator steps. The protocol package provides the agent-side framework. | -| `openenv/` | `build_app(bundle)`: the [OpenEnv](https://github.com/openenv/OpenEnv) server for a bundle's tasks, each session playing one episode through the Episode API, with the envs' MCP tools, an added `end_turn` tool and the task's verifier score as the reward. It prepares bundles as `agent-env run` does (`prepare_bundle(played=True)`, without the agent the client plays) and keeps namespace routing on for the app's life. `openenv` comes with the `openenv` extra, a floor with no cap, and this package is the only one that imports it; the `openenv-compat` workflow runs its tests against OpenEnv's latest release and `main` every week. | +| `openenv/` | `build_app(bundle)`: the [OpenEnv](https://github.com/openenv/OpenEnv) server for a bundle's tasks, each session playing one episode through the Episode API, with the envs' MCP tools, an added `end_turn` tool and the task's verifier score as the reward. It prepares bundles as `agent-env run` does (`prepare_bundle(played=True)`, without the agent the client plays) and keeps namespace routing on for the app's life. `openenv` comes with the `openenv` extra, a floor with no cap, and this package is the only one that imports it. | | `runner/` | The `[runner]` seam: `Runner.submit()` returns a `RunHandle` (`run_id`, `instance_id`) before the run starts; `LocalRunner` is built in. `runner/store.py` is the run index every runner writes and the explorer reads. | | `bundle/` | A bundle is a folder of entities (`envs/`, `agents/`, `artifacts/`, `skills/`, `tasks/.json`, `evals/.toml`) that runs without a registry: `agent-env run` plans it, writes it into the `@local` namespace (reusing each version whose inputs haven't changed) and runs its evals or tasks; `agent-env bundle init` writes a stored task or eval as one, with every env, agent and artifact it uses in its own folder (each image as its build context, pruned to what its Dockerfile reads and pinned to the Python and npm versions of the stored image) so it runs with no store. | | `eval/` | `Eval` (`eval`), a versioned list of task references, and its store. | diff --git a/pyproject.toml b/pyproject.toml index f6e01600..194e2edd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,8 +50,7 @@ dependencies = [ ] [project.optional-dependencies] -# agent_env.openenv: bundle tasks served as OpenEnv envs. No cap: OpenEnv ships a minor a week, and the openenv-compat -# workflow runs the OpenEnv tests against its latest release and main. +# agent_env.openenv: bundle tasks served as OpenEnv envs. No cap: OpenEnv ships a minor release a week. openenv = [ "openenv>=0.9", ]