diff --git a/.claude/skills/generate-openenv-env/SKILL.md b/.claude/skills/generate-openenv-env/SKILL.md index fbfc899a8..ca3c49ced 100644 --- a/.claude/skills/generate-openenv-env/SKILL.md +++ b/.claude/skills/generate-openenv-env/SKILL.md @@ -120,6 +120,8 @@ Use these standards: - For MCP/tool-call UIs that send stringified JSON arguments, add action validators/parsers in `server/app.py`. - Export public client/models symbols in `__init__.py`. - Keep `openenv.yaml` aligned with current scaffold format (`spec_version: 1`, `name`, `type`, `runtime`, `app`, `port`). +- Add a Harbor schema 1.1 `task.toml` and configure truthful resource, + networking, timeout, verifier, and artifact requirements for the environment. - Avoid training/evaluation code paths in this skill. ### 8. Validate before handoff @@ -134,6 +136,7 @@ PYTHONPATH=src:envs uv run python -c "from envs._env.server._environ cd envs/_env openenv build openenv validate --verbose +openenv validate --profile publish --remote PYTHONPATH=src:envs uv run pytest envs/_env -q ``` diff --git a/.claude/skills/generate-openenv-env/assets/openenv_env_template/task.toml b/.claude/skills/generate-openenv-env/assets/openenv_env_template/task.toml new file mode 100644 index 000000000..960624a20 --- /dev/null +++ b/.claude/skills/generate-openenv-env/assets/openenv_env_template/task.toml @@ -0,0 +1,13 @@ +schema_version = "1.1" + +[task] +name = "openenv/__ENV_NAME__" +description = "__ENV_TITLE_NAME__ OpenEnv environment" + +[environment] +cpus = 1 +memory_mb = 2048 +storage_mb = 10240 +gpus = 0 +# Set this to false when the environment does not need outbound internet. +allow_internet = true diff --git a/.claude/skills/generate-openenv-env/references/env-generation-checklist.md b/.claude/skills/generate-openenv-env/references/env-generation-checklist.md index ec5b01a68..ab5d85429 100644 --- a/.claude/skills/generate-openenv-env/references/env-generation-checklist.md +++ b/.claude/skills/generate-openenv-env/references/env-generation-checklist.md @@ -79,11 +79,14 @@ Ask only what changes architecture or contracts. Mark complete only when all are true: - `envs/_env/openenv.yaml` exists, uses `spec_version: 1`, and points to `server.app:app`. +- `envs/_env/task.toml` uses Harbor schema 1.1 and truthfully declares + publish-time resource and network requirements. - `models.py` defines typed action/observation/state. - `server/_environment.py` implements `reset`, `step`, and `state`. - `server/app.py` calls `create_app` with action/observation classes. - `client.py` matches the selected archetype and correctly serializes/parses data. - `__init__.py` exports the public API. - `README.md` includes quickstart and configuration. -- `openenv build` and `openenv validate --verbose` pass, or failures are documented. +- `openenv build` and `openenv validate --profile publish --remote` pass, or + failures are documented. - Runtime smoke check is executed (`/health`; optionally `openenv validate --url`). diff --git a/envs/echo_env/task.toml b/envs/echo_env/task.toml new file mode 100644 index 000000000..304a32bd5 --- /dev/null +++ b/envs/echo_env/task.toml @@ -0,0 +1,12 @@ +schema_version = "1.1" + +[task] +name = "openenv/echo-env" +description = "Canonical OpenEnv echo environment used for validation and examples." + +[environment] +cpus = 1 +memory_mb = 2048 +storage_mb = 10240 +gpus = 0 +allow_internet = false diff --git a/src/openenv/cli/templates/openenv_env/task.toml b/src/openenv/cli/templates/openenv_env/task.toml new file mode 100644 index 000000000..960624a20 --- /dev/null +++ b/src/openenv/cli/templates/openenv_env/task.toml @@ -0,0 +1,13 @@ +schema_version = "1.1" + +[task] +name = "openenv/__ENV_NAME__" +description = "__ENV_TITLE_NAME__ OpenEnv environment" + +[environment] +cpus = 1 +memory_mb = 2048 +storage_mb = 10240 +gpus = 0 +# Set this to false when the environment does not need outbound internet. +allow_internet = true diff --git a/tests/test_cli/test_init.py b/tests/test_cli/test_init.py index 5f03bfb69..430771798 100644 --- a/tests/test_cli/test_init.py +++ b/tests/test_cli/test_init.py @@ -39,6 +39,7 @@ def test_init_creates_directory_structure(tmp_path: Path) -> None: assert (env_dir / "client.py").exists() assert (env_dir / "README.md").exists() assert (env_dir / "openenv.yaml").exists() + assert (env_dir / "task.toml").exists() assert (env_dir / "server").exists() assert (env_dir / "server" / "__init__.py").exists() assert (env_dir / "server" / "app.py").exists() @@ -116,6 +117,22 @@ def test_init_generates_openenv_yaml(tmp_path: Path) -> None: assert "__ENV_NAME__" not in yaml_content +def test_init_generates_publish_requirements(tmp_path: Path) -> None: + env_name = "test_env" + result = runner.invoke( + app, + ["init", env_name, "--output-dir", str(tmp_path)], + input="\n", + ) + + assert result.exit_code == 0 + task_content = (tmp_path / env_name / "task.toml").read_text() + assert 'schema_version = "1.1"' in task_content + assert 'name = "openenv/test_env"' in task_content + assert "[environment]" in task_content + assert "__ENV_NAME__" not in task_content + + def test_init_readme_has_hf_frontmatter(tmp_path: Path) -> None: """Test that README has Hugging Face Space compatible frontmatter.""" env_name = "test_env"