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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
| `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/<name>.json`, `evals/<name>.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. |
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,14 @@ 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
"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 release a week.
openenv = [
"openenv>=0.9",
]
# The AWS store backends (S3, Secrets Manager, DynamoDB, ECR), selected by impl pointer in config.toml.
aws = [
"boto3>=1.35.0",
Expand Down Expand Up @@ -89,7 +93,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
Expand Down
12 changes: 9 additions & 3 deletions src/agent_env/openenv/__init__.py
Original file line number Diff line number Diff line change
@@ -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"]
27 changes: 23 additions & 4 deletions tst/unit/test_optional_extras_stay_optional.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 + """
Expand All @@ -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:
Expand All @@ -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",
Expand Down Expand Up @@ -241,6 +251,15 @@ 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


@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
Expand Down
13 changes: 9 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading