From 56d004ff93acb5e3528b145646e4da168922e349 Mon Sep 17 00:00:00 2001 From: Ben Burtenshaw Date: Fri, 14 Nov 2025 09:56:23 +0000 Subject: [PATCH 1/9] project matter --- src/envs/coding_env/README.md | 4 ++-- src/envs/coding_env/openenv.yaml | 5 +++++ src/envs/coding_env/pyproject.toml | 30 ++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 src/envs/coding_env/openenv.yaml create mode 100644 src/envs/coding_env/pyproject.toml diff --git a/src/envs/coding_env/README.md b/src/envs/coding_env/README.md index ab500002..b99921b8 100644 --- a/src/envs/coding_env/README.md +++ b/src/envs/coding_env/README.md @@ -1,8 +1,8 @@ --- title: Coding Environment Server emoji: 💻 -colorFrom: '#007ACC' -colorTo: '#1E1E1E' +colorFrom: blue +colorTo: blue sdk: docker pinned: false app_port: 8000 diff --git a/src/envs/coding_env/openenv.yaml b/src/envs/coding_env/openenv.yaml new file mode 100644 index 00000000..ba42db55 --- /dev/null +++ b/src/envs/coding_env/openenv.yaml @@ -0,0 +1,5 @@ +name: coding_env +version: "0.1.0" +description: "Coding environment for OpenEnv" +action: CodingAction +observation: CodingObservation diff --git a/src/envs/coding_env/pyproject.toml b/src/envs/coding_env/pyproject.toml new file mode 100644 index 00000000..3faa22d8 --- /dev/null +++ b/src/envs/coding_env/pyproject.toml @@ -0,0 +1,30 @@ +[build-system] +requires = ["setuptools>=45", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "openenv-coding_env" +version = "0.1.0" +description = "Coding Environment for OpenEnv" +requires-python = ">=3.10" +dependencies = [ + "openenv-core>=0.1.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=8.0.0", + "pytest-cov>=4.0.0", + "ipykernel>=6.29.5", +] + +[project.scripts] +server = "coding_env.server.app:main" + + +[tool.setuptools] +packages = ["coding_env", "coding_env.server"] +package-dir = { "coding_env" = ".", "coding_env.server" = "server" } + +[tool.setuptools.package-data] +coding_env = ["**/*.yaml", "**/*.yml"] From 354c943c2bf99ad7b7d61b298115ae923021005b Mon Sep 17 00:00:00 2001 From: Ben Burtenshaw Date: Fri, 14 Nov 2025 09:56:35 +0000 Subject: [PATCH 2/9] docker files for coding env --- src/envs/coding_env/server/Dockerfile | 41 ++++++++++---------- src/envs/coding_env/server/Dockerfile.backup | 25 ++++++++++++ 2 files changed, 46 insertions(+), 20 deletions(-) create mode 100644 src/envs/coding_env/server/Dockerfile.backup diff --git a/src/envs/coding_env/server/Dockerfile b/src/envs/coding_env/server/Dockerfile index 152f9e59..cef367db 100644 --- a/src/envs/coding_env/server/Dockerfile +++ b/src/envs/coding_env/server/Dockerfile @@ -1,25 +1,26 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the BSD-style license found in the -# LICENSE file in the root directory of this source tree. +# Base image +FROM python:3.11-slim -# Use the standard openenv base image -# Built from: docker build -t openenv-base:latest -f src/core/containers/images/Dockerfile . -# In GitHub Actions, this is overridden to use the GHCR base image -ARG BASE_IMAGE=openenv-base:latest -FROM ${BASE_IMAGE} +# Set working directory +WORKDIR /app/env -# Copy only what's needed for this environment -COPY src/core/ /app/src/core/ -COPY src/envs/coding_env/ /app/src/envs/coding_env/ +# Install system dependencies +RUN apt-get update && apt-get install -y \ + git \ + && rm -rf /var/lib/apt/lists/* -# Copy README for web interface documentation -COPY src/envs/coding_env/README.md /app/README.md +# Copy environment files +COPY . . -# Health check -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD curl -f http://localhost:8000/health || exit 1 +# Install Python dependencies +RUN pip install --no-cache-dir -e . -# Run the FastAPI server -CMD ["uvicorn", "envs.coding_env.server.app:app", "--host", "0.0.0.0", "--port", "8000"] +# Expose port +EXPOSE 8000 + +# Set environment variables +ENV PYTHONUNBUFFERED=1 +ENV ENABLE_WEB_INTERFACE=true + +# Run the server +CMD ["python", "-m", "uvicorn", "coding_env.server.app:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/src/envs/coding_env/server/Dockerfile.backup b/src/envs/coding_env/server/Dockerfile.backup new file mode 100644 index 00000000..152f9e59 --- /dev/null +++ b/src/envs/coding_env/server/Dockerfile.backup @@ -0,0 +1,25 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +# Use the standard openenv base image +# Built from: docker build -t openenv-base:latest -f src/core/containers/images/Dockerfile . +# In GitHub Actions, this is overridden to use the GHCR base image +ARG BASE_IMAGE=openenv-base:latest +FROM ${BASE_IMAGE} + +# Copy only what's needed for this environment +COPY src/core/ /app/src/core/ +COPY src/envs/coding_env/ /app/src/envs/coding_env/ + +# Copy README for web interface documentation +COPY src/envs/coding_env/README.md /app/README.md + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8000/health || exit 1 + +# Run the FastAPI server +CMD ["uvicorn", "envs.coding_env.server.app:app", "--host", "0.0.0.0", "--port", "8000"] From eff9369f3d0ff03033205325dfedcfc3b38ef057 Mon Sep 17 00:00:00 2001 From: Ben Burtenshaw Date: Fri, 14 Nov 2025 09:56:48 +0000 Subject: [PATCH 3/9] coding env server with integrated executor --- src/envs/coding_env/server/app.py | 17 +- .../coding_env/server/python_codeact_env.py | 6 +- src/envs/coding_env/server/python_executor.py | 149 ++++++++++++++++++ src/envs/coding_env/server/transforms.py | 8 +- 4 files changed, 170 insertions(+), 10 deletions(-) create mode 100644 src/envs/coding_env/server/python_executor.py diff --git a/src/envs/coding_env/server/app.py b/src/envs/coding_env/server/app.py index 3a895474..1a5edf7c 100644 --- a/src/envs/coding_env/server/app.py +++ b/src/envs/coding_env/server/app.py @@ -21,10 +21,10 @@ python -m envs.coding_env.server.app """ -from core.env_server import create_app +from openenv_core.env_server import create_app -from ..models import CodeAction, CodeObservation -from .python_codeact_env import PythonCodeActEnv +from coding_env.models import CodeAction, CodeObservation +from coding_env.server.python_codeact_env import PythonCodeActEnv # Create the environment instance env = PythonCodeActEnv() @@ -37,3 +37,14 @@ import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000) + + +def main(): + """Main entry point for running the server.""" + import uvicorn + + uvicorn.run(app, host="0.0.0.0", port=8000) + + +if __name__ == "__main__": + main() diff --git a/src/envs/coding_env/server/python_codeact_env.py b/src/envs/coding_env/server/python_codeact_env.py index 14daf2c9..ecc93d9f 100644 --- a/src/envs/coding_env/server/python_codeact_env.py +++ b/src/envs/coding_env/server/python_codeact_env.py @@ -13,10 +13,10 @@ import uuid -from core.env_server import Action, Environment, Observation -from core.tools import PyExecutor +from openenv_core.env_server.interfaces import Action, Environment, Observation +from coding_env.server.python_executor import PyExecutor -from ..models import CodeAction, CodeObservation, CodeState +from coding_env.models import CodeAction, CodeObservation, CodeState from .transforms import create_safe_coding_transform diff --git a/src/envs/coding_env/server/python_executor.py b/src/envs/coding_env/server/python_executor.py new file mode 100644 index 00000000..17b6ecc1 --- /dev/null +++ b/src/envs/coding_env/server/python_executor.py @@ -0,0 +1,149 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +"""Local Python Executor (enhanced). + +This module provides a safer wrapper around smolagents.LocalPythonExecutor +with improved exception handling and a few helpful tools registered with +the executor to make debugging executed code easier. + +Key improvements: +- Register a few helper utilities via send_tools so user code can use + them for reporting (e.g. `format_exc`). +- More robust extraction of stdout/stderr/exit codes from the executor + result object, tolerant to different versions of smolagents. +- Detailed stderr on unexpected exceptions including full traceback. +- Structured logging for operational visibility. +""" + +from __future__ import annotations + +import json +import logging +import traceback + +from smolagents import LocalPythonExecutor + +from openenv_core.env_server.types import CodeExecResult + +logger = logging.getLogger(__name__) +logger.addHandler(logging.NullHandler()) + + +class PyExecutor: + """Wrapper around smolagents LocalPythonExecutor. + + The wrapper registers a few non-privileged helper tools to the + LocalPythonExecutor that can be used by the executed code to + format exceptions and to safely stringify results for improved + error reporting. + """ + + def __init__(self, additional_imports: list[str] | None = None): + if additional_imports is None: + additional_imports = [] + + self._executor = LocalPythonExecutor(additional_authorized_imports=additional_imports) + + # Register helpful utilities exposed to the execution environment. + # These are intentionally small, read-only helpers. + tools = { + # Provide a small helper to format the current exception in the + # executed context. This is a *string formatting* helper only. + "format_exc": traceback.format_exc, + # Safe JSON dumps with a fallback for non-serializable objects. + "safe_json_dumps": lambda obj: json.dumps(obj, default=lambda o: repr(o)), + } + + # `send_tools` is the public API on LocalPythonExecutor to make + # helper callables available to the sandboxed runtime. We don't + # provide any builtins that could change the environment. + try: + self._executor.send_tools(tools) + except Exception: + # If the LocalPythonExecutor implementation doesn't support + # send_tools or fails, log and continue — the executor is still usable. + logger.debug("LocalPythonExecutor.send_tools failed; continuing without extra tools", exc_info=True) + + def run(self, code: str) -> CodeExecResult: + """Execute Python code and return a CodeExecResult. + + This method is intentionally defensive: it attempts to extract + meaningful stdout/stderr/exit_code information from a variety of + possible return shapes that different versions of smolagents + may provide. + """ + try: + exec_result = self._executor(code) + + # Default values + stdout_parts: list[str] = [] + stderr_parts: list[str] = [] + exit_code = 0 + + # Extract logs/prints + try: + logs = getattr(exec_result, "logs", None) + if logs: + stdout_parts.append(str(logs)) + except Exception: + logger.debug("Failed to read exec_result.logs", exc_info=True) + + # Extract the result / output value + try: + if hasattr(exec_result, "output"): + out_val = exec_result.output + # If the output is not None, stringify it in a safe way + if out_val is not None: + # Prefer JSON if possible, otherwise repr + try: + stdout_parts.append(json.dumps(out_val)) + except Exception: + stdout_parts.append(repr(out_val)) + except Exception: + logger.debug("Failed to read exec_result.output", exc_info=True) + + # Some runtime implementations may put errors on `error` or `exception` + try: + err = getattr(exec_result, "error", None) + if err: + stderr_parts.append(str(err)) + except Exception: + logger.debug("Failed to read exec_result.error", exc_info=True) + + try: + ex = getattr(exec_result, "exception", None) + if ex: + stderr_parts.append(str(ex)) + except Exception: + logger.debug("Failed to read exec_result.exception", exc_info=True) + + # Determine exit code if provided + try: + if hasattr(exec_result, "exit_code"): + exit_code = int(exec_result.exit_code) if exec_result.exit_code is not None else 0 + elif hasattr(exec_result, "success"): + # Some versions use `success` boolean + exit_code = 0 if exec_result.success else 1 + else: + # Fallback: if there were any stderr parts, treat as non-zero + exit_code = 1 if stderr_parts else 0 + except Exception: + logger.debug("Failed to determine exec_result exit code", exc_info=True) + exit_code = 1 if stderr_parts else 0 + + # Compose the final stdout/stderr strings + stdout = "\n".join(part for part in stdout_parts if part is not None) + stderr = "\n".join(part for part in stderr_parts if part is not None) + + return CodeExecResult(stdout=stdout, stderr=stderr, exit_code=exit_code) + + except Exception as e: + # Any unexpected exception from the LocalPythonExecutor is + # returned with a full traceback to make debugging easier. + tb = traceback.format_exc() + logger.exception("LocalPythonExecutor raised an exception during run") + return CodeExecResult(stdout="", stderr=tb, exit_code=1) diff --git a/src/envs/coding_env/server/transforms.py b/src/envs/coding_env/server/transforms.py index 38ce952b..ee5a1c4b 100644 --- a/src/envs/coding_env/server/transforms.py +++ b/src/envs/coding_env/server/transforms.py @@ -9,11 +9,11 @@ import ast import re -from core.env_server.base_transforms import CompositeTransform -from core.env_server.interfaces import Transform -from core.env_server.types import Observation +from openenv_core.env_server.base_transforms import CompositeTransform +from openenv_core.env_server.interfaces import Transform +from openenv_core.env_server.types import Observation -from ..models import CodeObservation +from coding_env.models import CodeObservation class CodeSafetyTransform(Transform): From e512d35b031afa7e649d0b9a98340a34c43659a9 Mon Sep 17 00:00:00 2001 From: Ben Burtenshaw Date: Fri, 14 Nov 2025 09:56:56 +0000 Subject: [PATCH 4/9] coding env client code --- src/envs/coding_env/coding_env_client.py | 11 +++-------- src/envs/coding_env/models.py | 3 +-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/envs/coding_env/coding_env_client.py b/src/envs/coding_env/coding_env_client.py index 6d06e782..d65c5152 100644 --- a/src/envs/coding_env/coding_env_client.py +++ b/src/envs/coding_env/coding_env_client.py @@ -13,16 +13,11 @@ from __future__ import annotations -from typing import Optional, TYPE_CHECKING +from openenv_core.client_types import StepResult -from core.client_types import StepResult +from openenv_core.http_env_client import HTTPEnvClient -from core.http_env_client import HTTPEnvClient - -from .models import CodeAction, CodeObservation, CodeState - -if TYPE_CHECKING: - from core.containers.runtime import ContainerProvider +from coding_env.models import CodeAction, CodeObservation, CodeState class CodingEnv(HTTPEnvClient[CodeAction, CodeObservation]): diff --git a/src/envs/coding_env/models.py b/src/envs/coding_env/models.py index 92436ba5..a92c2560 100644 --- a/src/envs/coding_env/models.py +++ b/src/envs/coding_env/models.py @@ -7,9 +7,8 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Any, Optional -from core.env_server import Action, Observation, State +from openenv_core.env_server.interfaces import Action, Observation, State @dataclass From 911f8d6e1663c8ccce8f05c003657aac0ea2645a Mon Sep 17 00:00:00 2001 From: Ben Burtenshaw Date: Fri, 14 Nov 2025 09:57:03 +0000 Subject: [PATCH 5/9] coding env example --- examples/coding_env_inference.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/coding_env_inference.py b/examples/coding_env_inference.py index 8dfb31f9..05384098 100644 --- a/examples/coding_env_inference.py +++ b/examples/coding_env_inference.py @@ -35,7 +35,7 @@ from openai import OpenAI -from envs.coding_env import CodeAction, CodingEnv +from coding_env import CodeAction, CodingEnv # --------------------------------------------------------------------------- @@ -68,6 +68,7 @@ # Helpers # --------------------------------------------------------------------------- + def extract_python_code(text: str) -> str: """Extract the first Python code block from the model output.""" @@ -115,6 +116,7 @@ def build_initial_prompt(task: str) -> str: # Gameplay # --------------------------------------------------------------------------- + def solve_coding_task( env: CodingEnv, client: OpenAI, @@ -152,9 +154,7 @@ def solve_coding_task( transcripts.append( ( - f"Step {step} | exit_code={obs.exit_code}\n" - f"stdout:\n{obs.stdout}\n" - f"stderr:\n{obs.stderr}\n" + f"Step {step} | exit_code={obs.exit_code}\nstdout:\n{obs.stdout}\nstderr:\n{obs.stderr}\n" ) ) @@ -192,6 +192,7 @@ def solve_coding_task( # Entrypoint # --------------------------------------------------------------------------- + def main() -> None: if not API_KEY: raise SystemExit( @@ -222,5 +223,3 @@ def main() -> None: if __name__ == "__main__": main() - - From abe4f6efb732c61b281eac748738904e8836cae3 Mon Sep 17 00:00:00 2001 From: Ben Burtenshaw Date: Fri, 14 Nov 2025 10:14:37 +0000 Subject: [PATCH 6/9] add init to server --- src/envs/coding_env/server/__init__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/envs/coding_env/server/__init__.py diff --git a/src/envs/coding_env/server/__init__.py b/src/envs/coding_env/server/__init__.py new file mode 100644 index 00000000..dab6b748 --- /dev/null +++ b/src/envs/coding_env/server/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +"""Coding environment server components.""" + +from .python_codeact_env import PythonCodeActEnv + +__all__ = ["PythonCodeActEnv"] From 9a17cd91689c90ed5f6dcb10ef42738816399b49 Mon Sep 17 00:00:00 2001 From: Ben Burtenshaw Date: Fri, 14 Nov 2025 10:14:45 +0000 Subject: [PATCH 7/9] rename client file --- src/envs/coding_env/{coding_env_client.py => client.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/envs/coding_env/{coding_env_client.py => client.py} (100%) diff --git a/src/envs/coding_env/coding_env_client.py b/src/envs/coding_env/client.py similarity index 100% rename from src/envs/coding_env/coding_env_client.py rename to src/envs/coding_env/client.py From 4ba082bb9a8e8fd07e819bdaeb48953c7111c30c Mon Sep 17 00:00:00 2001 From: Ben Burtenshaw Date: Fri, 14 Nov 2025 10:39:23 +0000 Subject: [PATCH 8/9] update init to match rename --- src/envs/coding_env/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/envs/coding_env/__init__.py b/src/envs/coding_env/__init__.py index 9fd16ead..1334d242 100644 --- a/src/envs/coding_env/__init__.py +++ b/src/envs/coding_env/__init__.py @@ -6,7 +6,7 @@ """Coding Environment - A Python code execution environment.""" -from .coding_env_client import CodingEnv +from .client import CodingEnv from .models import CodeAction, CodeObservation, CodeState -__all__ = ["CodeAction", "CodeObservation", "CodeState", "CodingEnv"] +__all__ = ["CodingEnv", "CodeAction", "CodeObservation", "CodeState"] From 136d5cbbc762addde8c138323bdeb629566ed733 Mon Sep 17 00:00:00 2001 From: Ben Burtenshaw Date: Fri, 14 Nov 2025 10:39:31 +0000 Subject: [PATCH 9/9] add dependencies --- src/envs/coding_env/pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/envs/coding_env/pyproject.toml b/src/envs/coding_env/pyproject.toml index 3faa22d8..f6ff45aa 100644 --- a/src/envs/coding_env/pyproject.toml +++ b/src/envs/coding_env/pyproject.toml @@ -9,6 +9,11 @@ description = "Coding Environment for OpenEnv" requires-python = ">=3.10" dependencies = [ "openenv-core>=0.1.0", + "fastapi>=0.115.0", + "pydantic>=2.0.0", + "uvicorn>=0.24.0", + "requests>=2.31.0", + "smolagents>=1.22.0,<2", ] [project.optional-dependencies]