OpenEnv documentation

Core API

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Core API

The openenv.core package provides the core abstractions for building and running environments. For an end-to-end tutorial on building environments with OpenEnv, see the building an environment guide.

If you are trying to understand when OpenEnv exposes the training loop versus direct MCP access, see the simulation vs production mode guide.

For a high-level explanation of how MCP-backed environments move through step(), step_async(), and convenience tool helpers, see the MCP environment lifecycle guide.

Server

Environment server primitives

class openenv.core.Message

< >

( )

A message in a conversation.

Compatible with Huggingface chat template format.

class openenv.core.ModelTokenizer

< >

( *args **kwargs )

Protocol for tokenizers that support chat templates.

This protocol defines the interface that tokenizers must implement to work with chat-based environments. It’s compatible with Huggingface transformers tokenizers.

apply_chat_template

< >

( conversation: list tokenize: bool = True return_tensors: str | None = None **kwargs: typing.Any )

Parameters

  • conversation — List of message dictionaries with ‘role’ and ‘content’
  • tokenize — Whether to tokenize the output
  • return_tensors — Format for returned tensors (‘pt’ for PyTorch)
  • **kwargs — Additional arguments

Apply a chat template to format and optionally tokenize a conversation.

decode

< >

( token_ids: typing.Any skip_special_tokens: bool = False **kwargs: typing.Any )

Parameters

  • token_ids — Token IDs to decode
  • skip_special_tokens — Whether to skip special tokens in output
  • **kwargs — Additional arguments

Decode token IDs back to text.

class openenv.core.Transform

< >

( )

Transform observations to add rewards, metrics, or other modifications.

Transforms follow the TorchRL pattern where they take an observation and return a (potentially modified) observation. This allows for flexible reward computation and observation augmentation.

class openenv.core.Environment

< >

( transform: typing.Optional[openenv.core.env_server.interfaces.Transform[~ObsT]] = None rubric: typing.Optional[ForwardRef('Rubric')] = None )

Parameters

  • transform — Optional transform to apply to observations
  • rubric — Optional rubric for reward computation. When provided, the rubric’s output can be used to set the observation’s reward in step().
  • rubric — Optional rubric for computing rewards. Environments can set this in init and use it in step() to compute observation rewards. Training infrastructure can access it for introspection: for name, r in env.rubric.named_rubrics(): print(f”{name}: {r.last_score}“)

Base class for all environment servers following Gym/Gymnasium API.

Class Attributes: SUPPORTS_CONCURRENT_SESSIONS: Whether this environment supports concurrent sessions. When True, multiple WebSocket connections can each have their own environment instance (up to max_concurrent_envs). When False (default), the environment should only be used with a single session at a time.

Set this to True in your Environment subclass if:

  • The environment uses proper session isolation (e.g., unique working dirs)
  • No shared mutable state exists between instances
  • External resources (databases, APIs) can handle concurrent access

See RFC 004 for rubric design: rfcs/004-rubrics.md

close

< >

( )

Clean up resources used by the environment.

Override this method to implement custom cleanup logic. Called when the environment is being destroyed or reset.

get_metadata

< >

( )

Get metadata about this environment.

Override this method to provide custom metadata for the environment. Default implementation returns basic metadata derived from class name.

reset

< >

( seed: typing.Optional[int] = None episode_id: typing.Optional[str] = None **kwargs: typing.Any )

Reset the environment and return initial observation.

reset_async

< >

( seed: typing.Optional[int] = None episode_id: typing.Optional[str] = None **kwargs: typing.Any )

Async version of reset. Default implementation calls sync reset.

Override to provide true async implementation.

step

< >

( action: ~ActT timeout_s: typing.Optional[float] = None **kwargs: typing.Any )

Take a step in the environment.

step_async

< >

( action: ~ActT timeout_s: typing.Optional[float] = None **kwargs: typing.Any )

Async version of step. Default implementation calls sync step.

Override to provide true async implementation.

Types

class openenv.core.ServerMode

< >

( value names = None module = None qualname = None type = None start = 1 )

Server operation mode.

class openenv.core.HealthStatus

< >

( value names = None module = None qualname = None type = None start = 1 )

Server health status values.

class openenv.core.WSErrorCode

< >

( value names = None module = None qualname = None type = None start = 1 )

WebSocket error codes for structured error handling.

class openenv.core.Action

< >

( metadata: typing.Dict[str, typing.Any] = <factory> )

Base class for all environment actions.

All action subclasses should inherit from this base class. Uses Pydantic for automatic validation and serialization.

class openenv.core.Observation

< >

( done: bool = False reward: bool | int | float | None = None metadata: typing.Dict[str, typing.Any] = <factory> )

Base class for all environment observations.

All observation subclasses should inherit from this base class. Uses Pydantic for automatic validation and serialization.

class openenv.core.env_server.types.ResetRequest

< >

( seed: typing.Annotated[typing.Optional[int], Ge(ge=0)] = None episode_id: typing.Annotated[typing.Optional[str], MaxLen(max_length=255)] = None **extra_data: typing.Any )

Request model for environment reset.

class openenv.core.env_server.types.ResetResponse

< >

( observation: typing.Dict[str, typing.Any] reward: typing.Optional[float] = None done: bool = False )

Response model for environment reset.

class openenv.core.env_server.types.StepRequest

< >

( action: typing.Dict[str, typing.Any] timeout_s: typing.Annotated[typing.Optional[float], Gt(gt=0)] = None request_id: typing.Annotated[typing.Optional[str], MaxLen(max_length=255)] = None **extra_data: typing.Any )

Request model for environment step.

class openenv.core.env_server.types.StepResponse

< >

( observation: typing.Dict[str, typing.Any] reward: typing.Optional[float] = None done: bool = False )

Response model for environment step.

class openenv.core.BaseMessage

< >

( )

Base class for WebSocket messages with shared configuration.

class openenv.core.State

< >

( episode_id: typing.Optional[str] = None step_count: typing.Annotated[int, Ge(ge=0)] = 0 **extra_data: typing.Any )

Base class for environment state.

Represents internal environment state, separate from observations.

class openenv.core.env_server.types.CodeExecResult

< >

( stdout: str stderr: str exit_code: int )

Result of code execution containing stdout, stderr, and exit code.

class openenv.core.env_server.types.EnvironmentMetadata

< >

( name: str description: str readme_content: typing.Optional[str] = None version: typing.Optional[str] = None author: typing.Optional[str] = None documentation_url: typing.Optional[str] = None )

Metadata about an environment for documentation and UI purposes.

class openenv.core.SchemaResponse

< >

( action: typing.Dict[str, typing.Any] observation: typing.Dict[str, typing.Any] state: typing.Dict[str, typing.Any] )

Response model for the combined schema endpoint.

class openenv.core.HealthResponse

< >

( status: HealthStatus = <HealthStatus.HEALTHY: 'healthy'> )

Response model for health check endpoint.

class openenv.core.WSResetMessage

< >

( type: typing.Literal['reset'] = 'reset' data: typing.Dict[str, typing.Any] = <factory> )

WebSocket message to reset the environment.

class openenv.core.WSStepMessage

< >

( type: typing.Literal['step'] = 'step' data: typing.Dict[str, typing.Any] )

WebSocket message to execute a step.

class openenv.core.WSStateMessage

< >

( type: typing.Literal['state'] = 'state' )

WebSocket message to request current state.

class openenv.core.WSCloseMessage

< >

( type: typing.Literal['close'] = 'close' )

WebSocket message to close the session.

class openenv.core.WSObservationResponse

< >

( type: typing.Literal['observation'] = 'observation' data: typing.Dict[str, typing.Any] )

WebSocket response containing an observation.

class openenv.core.WSStateResponse

< >

( type: typing.Literal['state'] = 'state' data: typing.Dict[str, typing.Any] )

WebSocket response containing environment state.

class openenv.core.WSErrorResponse

< >

( type: typing.Literal['error'] = 'error' data: typing.Dict[str, typing.Any] )

WebSocket response for errors.

class openenv.core.ConcurrencyConfig

< >

( max_concurrent_envs: typing.Annotated[int, Ge(ge=1)] = 1 session_timeout: typing.Annotated[typing.Optional[float], Gt(gt=0)] = None )

Configuration for concurrent environment sessions.

class openenv.core.ServerCapacityStatus

< >

( active_sessions: typing.Annotated[int, Ge(ge=0)] max_sessions: typing.Annotated[int, Ge(ge=1)] )

Status of server capacity for concurrent sessions.

from_counts

< >

( active: int max_sessions: int )

Create status from active and max session counts.

class openenv.core.SessionInfo

< >

( session_id: str created_at: float last_activity_at: float step_count: typing.Annotated[int, Ge(ge=0)] = 0 environment_type: str )

Information about an active session.

Exceptions

class openenv.core.OpenEnvError

< >

( )

Base exception for all OpenEnv errors.

class openenv.core.ConcurrencyConfigurationError

< >

( environment_name: str max_concurrent_envs: int message: typing.Optional[str] = None )

Raised when an environment is misconfigured for concurrent sessions.

This error is raised during server startup when max_concurrent_envs > 1 is specified for an environment that is not marked as SUPPORTS_CONCURRENT_SESSIONS.

class openenv.core.SessionCapacityError

< >

( active_sessions: int max_sessions: int message: typing.Optional[str] = None )

Raised when the server cannot accept new sessions due to capacity limits.

This error is raised when a new WebSocket connection is attempted but the server has already reached max_concurrent_envs active sessions.

class openenv.core.SessionNotFoundError

< >

( session_id: str message: typing.Optional[str] = None )

Raised when attempting to access a session that does not exist.

class openenv.core.SessionCreationError

< >

( reason: str message: typing.Optional[str] = None )

Raised when a session cannot be created.

class openenv.core.EnvironmentFactoryError

< >

( factory_name: str message: typing.Optional[str] = None )

Raised when the environment factory fails to create an instance.

HTTP server utilities

class openenv.core.HTTPEnvServer

< >

( env: Callable[[], Environment] action_cls: Type[Action] observation_cls: Type[Observation] max_concurrent_envs: Optional[int] = None concurrency_config: Optional[ConcurrencyConfig] = None )

HTTP server wrapper for Environment instances.

This class wraps an Environment and exposes its reset(), step(), and state methods as HTTP and WebSocket endpoints compatible with EnvClient.

The server expects:

  • Action deserialization: Converts JSON dict to Action subclass
  • Observation serialization: Converts Observation subclass to JSON dict

Example:

from core.env_server import HTTPEnvServer from envs.coding_env.server import CodeExecutionEnvironment from envs.coding_env.models import CodeAction, CodeObservation

Pass environment class (factory pattern)

server = HTTPEnvServer( … env=CodeExecutionEnvironment, … action_cls=CodeAction, … observation_cls=CodeObservation, … max_concurrent_envs=4, … )

Register routes with FastAPI

from fastapi import FastAPI app = FastAPI() server.register_routes(app)

get_capacity_status

< >

( )

Get the current capacity status of the server.

get_session_info

< >

( session_id: str )

Parameters

  • session_id — The session ID to query

Get information about a specific session.

register_routes

< >

( app: FastAPI mode: ServerMode | str = <ServerMode.SIMULATION: 'simulation'> )

Parameters

  • app — FastAPI application instance
  • mode — Server mode - either SIMULATION or PRODUCTION (or string equivalents). In production mode, simulation control endpoints (/reset, /step, /state) are NOT registered. Only safe endpoints (/health, /schema, /metadata, /ws) are available. Defaults to SIMULATION for backwards compatibility.

Raises

ValueError

  • ValueError — If mode is not a valid ServerMode or string equivalent.

Register HTTP routes on a FastAPI application.

openenv.core.create_app

< >

( env: Callable[[], Environment] action_cls: Type[Action] observation_cls: Type[Observation] env_name: Optional[str] = None max_concurrent_envs: Optional[int] = None concurrency_config: Optional[ConcurrencyConfig] = None gradio_builder: Optional[Callable[..., Any]] = None custom_tab_name: str = 'Custom' custom_tab_primary: bool = False show_default_tab: bool = True title_override: Optional[str] = None )

Parameters

  • env — Environment factory (callable) that creates new instances
  • action_cls — The Action subclass this environment expects
  • observation_cls — The Observation subclass this environment returns
  • env_name — Optional environment name for README loading
  • max_concurrent_envs — Maximum concurrent WebSocket sessions. Mutually exclusive with concurrency_config.
  • concurrency_config — Optional ConcurrencyConfig for advanced concurrency settings. Mutually exclusive with max_concurrent_envs.
  • gradio_builder — Optional callable to build a custom Gradio UI at /web. Signature: (web_manager, action_fields, metadata, is_chat_env, title, quick_start_md) -> gr.Blocks. When None, the default Gradio app is used. See docs/customizing-web-ui.md.
  • custom_tab_name — Label for the env-specific tab when gradio_builder is provided. Defaults to "Custom".
  • custom_tab_primary — When True, the env-specific tab is active first; the auto-generated Playground becomes secondary. Use when the custom tab is the real interaction surface for the env.
  • show_default_tab — When False, mount the env’s gradio_builder output alone (no auto-generated Playground, no tab chrome). Only meaningful when gradio_builder is provided.
  • title_override — If set, used as the Gradio app title instead of the default "OpenEnv Agentic Environment: {name}".

Create a FastAPI application with or without web interface.

This function creates a FastAPI app with the web interface enabled by default, including README integration for better user experience.

openenv.core.create_fastapi_app

< >

( env: Callable[[], Environment] action_cls: Type[Action] observation_cls: Type[Observation] max_concurrent_envs: Optional[int] = None concurrency_config: Optional[ConcurrencyConfig] = None )

Parameters

  • env — Environment factory (callable) that creates new instances
  • action_cls — The Action subclass this environment expects
  • observation_cls — The Observation subclass this environment returns
  • max_concurrent_envs — Maximum concurrent WebSocket sessions. Mutually exclusive with concurrency_config.
  • concurrency_config — Optional ConcurrencyConfig for advanced concurrency settings. Mutually exclusive with max_concurrent_envs.

Create a FastAPI application with comprehensive documentation.

Web interface helpers

class openenv.core.env_server.web_interface.ActionLog

< >

( timestamp: str action: typing.Dict[str, typing.Any] observation: typing.Dict[str, typing.Any] reward: typing.Optional[float] = None done: bool step_count: int )

Log entry for an action taken.

class openenv.core.env_server.web_interface.EpisodeState

< >

( episode_id: typing.Optional[str] = None step_count: int current_observation: typing.Optional[typing.Dict[str, typing.Any]] = None action_logs: typing.List[openenv.core.env_server.web_interface.ActionLog] = <factory> is_reset: bool = True )

Current episode state for the web interface.

class openenv.core.WebInterfaceManager

< >

( env: Environment action_cls: Type[Action] observation_cls: Type[Observation] metadata: Optional[EnvironmentMetadata] = None )

Manages the web interface for an environment.

connect_websocket

< >

( websocket: WebSocket )

Connect a new WebSocket client.

disconnect_websocket

< >

( websocket: WebSocket )

Disconnect a WebSocket client.

get_state

< >

( )

Get current environment state.

reset_environment

< >

( reset_kwargs: Optional[Dict[str, Any]] = None )

Reset the environment and update state.

step_environment

< >

( action_data: Dict[str, Any] )

Execute a step in the environment and update state.

openenv.core.create_web_interface_app

< >

( env: Environment action_cls: Type[Action] observation_cls: Type[Observation] env_name: Optional[str] = None max_concurrent_envs: Optional[int] = None concurrency_config: Optional[Any] = None gradio_builder: Optional[Callable[..., Any]] = None custom_tab_name: str = 'Custom' custom_tab_primary: bool = False show_default_tab: bool = True title_override: Optional[str] = None )

Parameters

  • env — The Environment instance to serve
  • action_cls — The Action subclass this environment expects
  • observation_cls — The Observation subclass this environment returns
  • env_name — Optional environment name for README loading
  • max_concurrent_envs — Maximum concurrent WebSocket sessions
  • concurrency_config — Optional ConcurrencyConfig for advanced concurrency settings
  • gradio_builder — Optional callable (web_manager, action_fields, metadata, is_chat_env, title, quick_start_md) -> gr.Blocks to use instead of the default Gradio UI. Lets envs replace or customize the /web interface.
  • custom_tab_name — Label shown on the env-specific tab when gradio_builder is provided. Defaults to "Custom" for backwards compatibility; envs that ship a rich custom UI should pass a descriptive name (e.g. "REPL"). Ignored when show_default_tab=False (no tab chrome is rendered).
  • custom_tab_primary — When True, the env-specific tab is rendered first and selected by default; the auto-generated Playground becomes secondary. Use this for envs whose custom tab is the real interaction surface (so visitors don’t land on a less informative schema form). Ignored when show_default_tab=False.
  • show_default_tab — When False, the auto-generated Playground tab is not rendered and the env’s gradio_builder output is mounted directly (single-view UI, no tab chrome). Only meaningful when gradio_builder is provided.
  • title_override — If set, used verbatim as the Gradio app/browser-tab title instead of the default "OpenEnv Agentic Environment: {name}".

Create a FastAPI application with web interface for the given environment.

Serialization

openenv.core.deserialize_action

< >

( action_data: typing.Dict[str, typing.Any] action_cls: typing.Type[openenv.core.env_server.types.Action] )

Parameters

  • action_data — Dictionary containing action data
  • action_cls — The Action subclass to instantiate

Raises

ValidationError

  • ValidationError — If action_data is invalid for the action class

Convert JSON dict to Action instance using Pydantic validation.

MCP action types (list_tools, call_tool) are recognised automatically via the "type" discriminator field, regardless of the environment’s configured action_cls. All other payloads fall through to action_cls.model_validate().

For special cases (e.g., tensor fields, custom type conversions), use deserialize_action_with_preprocessing().

Note: This uses Pydantic’s model_validate() for automatic validation.

openenv.core.deserialize_action_with_preprocessing

< >

( action_data: typing.Dict[str, typing.Any] action_cls: typing.Type[openenv.core.env_server.types.Action] )

Parameters

  • action_data — Dictionary containing action data
  • action_cls — The Action subclass to instantiate

Raises

ValidationError

  • ValidationError — If action_data is invalid for the action class

Convert JSON dict to Action instance with preprocessing for special types.

This version handles common type conversions needed for web interfaces:

  • Converting lists/strings to tensors for ‘tokens’ field
  • Converting string action_id to int
  • Other custom preprocessing as needed

openenv.core.serialize_observation

< >

( observation: Observation )

Parameters

  • observation — Observation instance

Convert Observation instance to JSON-compatible dict using Pydantic.

The format matches what EnvClient expects: { “observation”: {…}, # Observation fields “reward”: float | None, “done”: bool, }

Transforms

class openenv.core.CompositeTransform

< >

( transforms: list )

Combines multiple transforms into a single transform.

class openenv.core.NullTransform

< >

( )

Default transform that passes through unchanged.

Route configuration

class openenv.core.GetEndpointConfig

< >

( path: str handler: typing.Callable[[], pydantic.main.BaseModel | dict] response_model: typing.Union[typing.Type[pydantic.main.BaseModel], type[dict]] tag: str summary: str description: str )

Configuration for a simple GET endpoint.

openenv.core.env_server.route_config.register_get_endpoints

< >

( app: FastAPI configs: typing.List[openenv.core.env_server.route_config.GetEndpointConfig] )

Parameters

  • app — FastAPI application instance
  • configs — List of GET endpoint configurations

Register multiple GET endpoints from configuration.

Clients

Base client

class openenv.core.EnvClient

< >

( base_url: str connect_timeout_s: float = 10.0 message_timeout_s: float = 60.0 max_message_size_mb: float = 100.0 provider: Optional['ContainerProvider | RuntimeProvider'] = None mode: Optional[str] = None )

Async environment client for persistent sessions.

This client maintains a persistent WebSocket connection to an environment server, enabling efficient multi-step interactions. Each client instance corresponds to a dedicated environment session on the server.

The client is async by default. For synchronous usage, use the .sync() method to get a SyncEnvClient wrapper.

Features:

  • Lower latency for sequential interactions
  • Session state is maintained server-side
  • Better suited for long-running episodes
  • Async by default for modern Python async/await patterns

Example (async):

from envs.coding_env.client import CodingEnv

Connect to a server using async context manager

async with CodingEnv(base_url=“ws://localhost:8000”) as env: … result = await env.reset(seed=42) … while not result.done: … action = agent.predict(result.observation) … result = await env.step(action)

Example (sync wrapper):

env = CodingEnv(base_url=“ws://localhost:8000”).sync() with env: … result = env.reset(seed=42) … result = env.step(action)

close

< >

( )

Close the WebSocket connection and clean up resources.

If this client was created via from_docker_image() or from_env(), this will also stop and remove the associated container/process.

connect

< >

( )

Raises

ConnectionError

  • ConnectionError — If connection cannot be established

Establish WebSocket connection to the server.

disconnect

< >

( )

Close the WebSocket connection.

from_docker_image

< >

( image: str provider: Optional['ContainerProvider'] = None **kwargs: Any )

Parameters

  • image — Docker image name to run (e.g., “coding-env:latest”)
  • provider — Container provider to use (defaults to LocalDockerProvider)
  • **kwargs — Additional arguments to pass to provider.start_container()

Create an environment client by spinning up a Docker container.

from_env

< >

( repo_id: str use_docker: bool = True provider: Optional['ContainerProvider | RuntimeProvider'] = None **provider_kwargs: Any )

Parameters

  • repo_id — Hugging Face space identifier {org}/{space}.
  • use_docker — When True (default) pull from the HF registry and launch via LocalDockerProvider. When False run the space locally with UVProvider.
  • provider — Optional provider instance to reuse. Must be a ContainerProvider when use_docker=True and a RuntimeProvider otherwise.
  • provider_kwargs — Additional keyword arguments forwarded to either the container provider’s start_container (docker) or to the UVProvider constructor/start (uv). When use_docker=False, the project_path argument can be used to override the default git URL (git+https://huggingface.co/spaces/{repo_id}).

Create a client from a Hugging Face Space.

Examples:

Pull and run from HF Docker registry

env = await MyEnv.from_env(“openenv/echo-env”)

Run locally with UV (clones the space)

env = await MyEnv.from_env(“openenv/echo-env”, use_docker=False)

Run from a local checkout

env = await MyEnv.from_env( … “openenv/echo-env”, … use_docker=False, … project_path=“/path/to/local/checkout” … )

reset

< >

( **kwargs: Any )

Parameters

  • **kwargs — Optional parameters passed to the environment’s reset method. Common parameters include:
    • seed: Random seed for reproducibility
    • episode_id: Custom episode identifier

Reset the environment with optional parameters.

state

< >

( )

Get the current environment state from the server.

step

< >

( action: ActT **kwargs: Any )

Parameters

  • action — The action to execute
  • **kwargs — Optional parameters (currently ignored)

Execute an action in the environment.

sync

< >

( )

Return a synchronous wrapper around this async client.

Use this method when you need synchronous access to the environment without async/await syntax. This is useful for:

  • Integration with synchronous codebases
  • Interactive/REPL usage
  • Stopping async from “infecting” the call stack

Example:

Create async client and get sync wrapper

async_client = GenericEnvClient(base_url=“http://localhost:8000”) sync_client = async_client.sync()

Use synchronous API

with sync_client: … result = sync_client.reset() … result = sync_client.step({“code”: “print(‘hello’)”})

Synchronous client

class openenv.SyncEnvClient

< >

( async_client: 'EnvClient[ActT, ObsT, StateT]' )

Parameters

  • _async — The wrapped async EnvClient instance

Synchronous wrapper around an async EnvClient.

This class provides a synchronous interface to an async EnvClient, making it easier to use in synchronous code or to stop async from “infecting” the entire call stack.

The wrapper executes async operations on a dedicated background event loop so connection state remains bound to a single loop.

Cleanup note: For guaranteed resource cleanup, use with SyncEnvClient(...) or call close() explicitly. __del__ is best-effort only and may not run reliably (for example, during interpreter shutdown).

Example:

From an async client

async_client = GenericEnvClient(base_url=“http://localhost:8000”) sync_client = async_client.sync()

Use synchronous context manager

with sync_client: … result = sync_client.reset() … result = sync_client.step({“action”: “test”})

close

< >

( )

Close the connection and clean up resources.

connect

< >

( )

Establish connection to the server.

disconnect

< >

( )

Close the connection.

reset

< >

( **kwargs: Any )

Parameters

  • **kwargs — Optional parameters passed to the environment’s reset method

Reset the environment.

state

< >

( )

Get the current environment state.

step

< >

( action: ActT **kwargs: Any )

Parameters

  • action — The action to execute
  • **kwargs — Optional parameters

Execute an action in the environment.

Generic client

class openenv.GenericEnvClient

< >

( base_url: str connect_timeout_s: float = 10.0 message_timeout_s: float = 60.0 max_message_size_mb: float = 100.0 provider: Optional['ContainerProvider | RuntimeProvider'] = None mode: Optional[str] = None )

Environment client that works with raw dictionaries instead of typed classes.

This client doesn’t require installing environment-specific packages, making it ideal for:

  • Connecting to remote servers without installing their packages
  • Quick prototyping and testing
  • Environments where type safety isn’t needed
  • Security-conscious scenarios where you don’t want to run remote code

The trade-off is that you lose type safety and IDE autocomplete for actions and observations. Instead of typed objects, you work with plain dictionaries.

Example:

Direct connection to a running server (no installation needed)

with GenericEnvClient(base_url=“http://localhost:8000”) as env: … result = env.reset() … result = env.step({“code”: “print(‘hello’)”}) … print(result.observation) # Dict[str, Any] … print(result.observation.get(“output”))

From local Docker image

env = GenericEnvClient.from_docker_image(“coding-env:latest”) result = env.reset() result = env.step({“code”: “x = 1 + 2”}) env.close()

From HuggingFace Hub (pulls Docker image, no pip install)

env = GenericEnvClient.from_env(“user/my-env”, use_docker=True) result = env.reset() env.close()

Note: GenericEnvClient inherits from_docker_image() and from_env() from EnvClient, so you can use it with Docker containers and HuggingFace Spaces without any package installation.

class openenv.GenericAction

< >

( **kwargs: typing.Any )

A dictionary subclass for creating actions when using GenericEnvClient.

This provides a semantic wrapper around dictionaries to make code more readable when working with GenericEnvClient. It behaves exactly like a dict but signals intent that this is an action for an environment.

Example:

Without GenericAction (works fine)

env.step({“code”: “print(‘hello’)”})

With GenericAction (more explicit)

action = GenericAction(code=“print(‘hello’)”) env.step(action)

With multiple fields

action = GenericAction(code=“x = 1”, timeout=30, metadata={“tag”: “test”}) env.step(action)

Note: GenericAction is just a dict with a constructor that accepts keyword arguments. It’s provided for symmetry with typed Action classes and to make code more readable.

LLM client

class openenv.core.ToolCall

< >

( id: str name: str args: dict[str, Any] )

A single tool/function call returned by the model.

class openenv.core.LLMResponse

< >

( content: str tool_calls: list[ToolCall] = <factory> )

Normalized response from an LLM, with optional tool calls.

to_message_dict

< >

( )

Convert to an OpenAI-format assistant message dict.

class openenv.core.LLMClient

< >

( endpoint: str port: int )

Parameters

  • endpoint — The base URL of the LLM service (e.g. “http://localhost”).
  • port — The port the service listens on.

Abstract base for LLM endpoint clients.

Subclass and implement complete() for your protocol.

complete

< >

( prompt: str **kwargs )

Parameters

  • prompt — The user prompt to send.
  • **kwargs — Override default parameters (temperature, max_tokens, etc.).

Send a prompt, return the text response.

complete_with_tools

< >

( messages: list[dict[str, Any]] tools: list[dict[str, Any]] **kwargs: Any )

Parameters

  • messages — Conversation history as OpenAI-format message dicts.
  • tools — MCP tool definitions.
  • **kwargs — Override default parameters (temperature, max_tokens, etc.).

Send messages with tool definitions, return a normalized response.

Messages use OpenAI-format dicts ({"role": "...", "content": "..."}). Tools use MCP tool definitions; they are converted internally.

class openenv.core.OpenAIClient

< >

( endpoint: str port: int model: str api_key: str | None = None system_prompt: str | None = None temperature: float = 0.0 max_tokens: int = 256 use_max_completion_tokens: bool = False )

Parameters

  • endpoint — The base URL (e.g. “http://localhost”).
  • port — The port number.
  • model — Model name to pass to the API.
  • api_key — API key. Defaults to “not-needed” for local endpoints.
  • system_prompt — Optional system message prepended to every request.
  • temperature — Default sampling temperature.
  • max_tokens — Default max tokens in the response.
  • use_max_completion_tokens — Use max_completion_tokens instead of max_tokens. Required for newer OpenAI models (gpt-5-mini, o1, o3). Not supported by self-hosted OpenAI-compatible endpoints.

Client for OpenAI-compatible APIs.

Works with: OpenAI, vLLM, TGI, Ollama, HuggingFace Inference API, or any endpoint that speaks the OpenAI chat completions format.

complete

< >

( prompt: str **kwargs )

Parameters

  • prompt — The user message.
  • **kwargs — Overrides for temperature, max_tokens.

Send a chat completion request.

class openenv.core.AnthropicClient

< >

( endpoint: str port: int model: str api_key: str | None = None system_prompt: str | None = None temperature: float = 0.0 max_tokens: int = 256 )

Parameters

  • endpoint — The base URL (e.g. ”https://api.anthropic.com”).
  • port — The port number.
  • model — Model name (e.g. “claude-sonnet-4-20250514”).
  • api_key — Anthropic API key.
  • system_prompt — Optional system message prepended to every request.
  • temperature — Default sampling temperature.
  • max_tokens — Default max tokens in the response.

Client for Anthropic’s Messages API.

Requires the anthropic package (lazy-imported at construction time).

openenv.core.create_llm_client

< >

( provider: str model: str api_key: str system_prompt: str | None = None temperature: float = 0.0 max_tokens: int = 4096 )

Parameters

  • provider — Provider name (“openai” or “anthropic”).
  • model — Model identifier.
  • api_key — API key for the provider.
  • system_prompt — Optional system message prepended to every request.
  • temperature — Sampling temperature.
  • max_tokens — Maximum tokens in the response.

Create an LLM client for a hosted provider.

Shared dataclasses

class openenv.core.client_types.StepResult

< >

( observation: ~ObsT reward: typing.Optional[float] = None done: bool = False )

Parameters

  • observation — The environment’s observation after the action.
  • reward — Scalar reward for this step (optional).
  • done — Whether the episode is finished.

Represents the result of one environment step.

MCP (Model Context Protocol)

MCP environment

class openenv.core.MCPEnvironment

< >

( mcp_server: typing.Any transform: typing.Optional[typing.Any] = None )

Parameters

  • mcp_server — A FastMCP server instance containing tool definitions. The server’s tools will be validated against reserved names.
  • transform — Optional transform to apply to observations (inherited from Environment).

Raises

ValueError

  • ValueError — If any tool in the MCP server uses a reserved name (reset, step, state, close).

Base class for environments that expose tools via MCP (Model Context Protocol).

MCPEnvironment bridges FastMCP servers with OpenEnv’s Gym-style API, allowing agents to discover and invoke MCP tools through the standard step() interface.

The class automatically handles:

  • ListToolsAction: Returns available tools from the MCP server
  • CallToolAction: Invokes a specific tool with arguments

All other actions are delegated to the abstract _step_impl() method, which subclasses must implement.

Example:

from fastmcp import FastMCP mcp = FastMCP(“calculator”) @mcp.tool() … def add(a: int, b: int) -> int: … return a + b env = MyMCPEnvironment(mcp) obs = env.step(ListToolsAction()) obs.tools[0].name ‘add’

close

< >

( )

Clean up resources used by the environment.

This method cleans up the MCP client and any other resources. Subclasses should call super().close() if they override this method.

execute_code

< >

( code: str )

Parameters

  • code — Python code to execute. Tools are available as functions in the execution namespace. Set a variable named ‘result’ to capture the return value.

Execute Python code with tools available as callables.

This enables the CodeAct pattern where agents write Python code that calls tools directly as functions, avoiding JSON-RPC overhead.

get_callables

< >

( )

Get callable functions for code mode.

Returns tool functions as direct Python callables, enabling code mode where agents write Python code that calls tools directly (no JSON-RPC overhead). Mode-specific tools are filtered by the current mode.

mcp_session

< >

( )

Context manager for MCP client sessions.

This wrapper serves two purposes:

  1. Null guard — raises a clear error if close() has already been called (mcp_client is None).

  2. AsyncExitStack adapter — FastMCP’s Client.__aenter__ creates a background asyncio.Task for session management. When entered directly via AsyncExitStack in the HTTP session path (_create_session), this task can be cancelled by ASGI harnesses (e.g. Starlette TestClient) between requests, corrupting session state. Wrapping in an asynccontextmanager generator isolates the task lifecycle: the generator frame keeps async with client: suspended at yield, so cleanup only runs when the stack explicitly closes the generator — not when the event loop cancels orphaned tasks.

Delegates to FastMCP’s Client context manager which is reentrant: the first entry opens the transport and subsequent (nested) entries simply increment an internal reference counter. The transport is closed only when the outermost context exits.

No external lock is needed because Client._connect / Client._disconnect already serialise connection state changes through their own anyio.Lock.

step

< >

( action: Action timeout_s: typing.Optional[float] = None **kwargs: typing.Any ) Observation appropriate to the action type

Parameters

  • action — The action to execute. Can be:
    • ListToolsAction: Returns available MCP tools
    • CallToolAction: Invokes a specific MCP tool
    • Any other Action: Delegated to _step_impl()
  • timeout_s — Optional timeout in seconds for the action. Defaults to MCP_TOOL_CALL_TIMEOUT (30s) for MCP actions.
  • **kwargs — Additional arguments passed to handlers.

Returns

Observation appropriate to the action type

  • ListToolsObservation for ListToolsAction
  • CallToolObservation for CallToolAction
  • Subclass-defined Observation for other actions

Execute an action in the environment.

This method routes MCP-specific actions (ListToolsAction, CallToolAction) to the appropriate handlers, while delegating all other actions to the subclass’s _step_impl() method.

step_async

< >

( action: Action timeout_s: typing.Optional[float] = None **kwargs: typing.Any )

Async step that routes MCP actions without going through run_async_safely.

The WebSocket handler calls this directly on the outer event loop, where the MCP session is already open, avoiding the thread/event-loop deadlock that occurs when the sync step() path is used via run_in_executor.

tool

< >

( mode: typing.Optional[str] = None )

Parameters

  • mode — Optional mode for the tool (“production” or “simulation”). If None, tool is available in all modes.

Raises

ValueError

  • ValueError — If mode is not None, “production”, or “simulation”.

Decorator for registering mode-aware tools.

MCP types

class openenv.core.JsonRpcErrorCode

< >

( value names = None module = None qualname = None type = None start = 1 )

Standard JSON-RPC 2.0 error codes.

See: https://www.jsonrpc.org/specification#error_object

class openenv.core.McpMethod

< >

( value names = None module = None qualname = None type = None start = 1 )

Supported MCP method names.

class openenv.core.JsonRpcError

< >

( code: int message: str data: typing.Optional[typing.Any] = None )

JSON-RPC 2.0 error object.

See: https://www.jsonrpc.org/specification#error_object

from_code

< >

( code: JsonRpcErrorCode message: typing.Optional[str] = None data: typing.Any = None )

Create an error from a standard error code.

class openenv.core.JsonRpcRequest

< >

( jsonrpc: typing.Literal['2.0'] method: str params: typing.Dict[str, typing.Any] = <factory> id: typing.Union[str, int, NoneType] = None )

JSON-RPC 2.0 request object.

See: https://www.jsonrpc.org/specification#request_object

class openenv.core.JsonRpcResponse

< >

( jsonrpc: typing.Literal['2.0'] = '2.0' result: typing.Optional[typing.Any] = None error: typing.Optional[openenv.core.env_server.mcp_types.JsonRpcError] = None id: typing.Union[str, int, NoneType] = None )

JSON-RPC 2.0 response object.

Per JSON-RPC 2.0 spec, a response has either ‘result’ or ‘error’, not both. This model excludes None values during serialization to comply with the spec.

See: https://www.jsonrpc.org/specification#response_object

error_response

< >

( code: JsonRpcErrorCode message: typing.Optional[str] = None data: typing.Any = None request_id: typing.Union[str, int, NoneType] = None )

Create an error response from a standard error code.

model_dump

< >

( **kwargs )

Serialize to dict, excluding result or error when None (JSON-RPC compliance).

model_dump_json

< >

( **kwargs )

Serialize to JSON string, excluding result or error when None (JSON-RPC compliance).

success

< >

( result: typing.Any request_id: typing.Union[str, int, NoneType] = None )

Create a success response.

class openenv.core.Tool

< >

( name: str description: str input_schema: typing.Dict[str, typing.Any] )

Strongly typed MCP tool specification.

Follows the MCP ToolSpec format for tool discovery. See: https://modelcontextprotocol.io/specification/2025-06-18/server/tools

class openenv.core.ToolErrorType

< >

( value names = None module = None qualname = None type = None start = 1 )

Types of errors that can occur during tool execution.

class openenv.core.ToolError

< >

( error_type: ToolErrorType message: str )

Structured error for tool execution failures.

This is used for transport/framework errors, NOT for errors returned by the tool itself (those go in the result field).

class openenv.core.ListToolsAction

< >

( metadata: typing.Dict[str, typing.Any] = <factory> type: typing.Literal['list_tools'] = 'list_tools' )

Request list of available tools from the environment.

This action triggers MCP’s tools/list operation and returns all available tools with their schemas.

Note: Does NOT require reset() to be called first.

class openenv.core.CallToolAction

< >

( metadata: typing.Dict[str, typing.Any] = <factory> type: typing.Literal['call_tool'] = 'call_tool' tool_name: str arguments: typing.Dict[str, typing.Any] = <factory> )

Call a specific tool via MCP.

This action triggers MCP’s tools/call operation with the specified tool name and arguments.

class openenv.core.ListToolsObservation

< >

( done: bool = False reward: bool | int | float | None = None metadata: typing.Dict[str, typing.Any] = <factory> tools: typing.List[openenv.core.env_server.mcp_types.Tool] )

Response containing available tools.

Returned when processing a ListToolsAction.

class openenv.core.CallToolObservation

< >

( done: bool = False reward: bool | int | float | None = None metadata: typing.Dict[str, typing.Any] = <factory> tool_name: str result: typing.Any = None error: typing.Optional[openenv.core.env_server.mcp_types.ToolError] = None )

Response from tool execution.

Contains the tool’s result or an error if the call failed. Tool-specific errors (from the tool itself) are included in the result. Transport/framework errors use the error field.

class openenv.core.WSMCPMessage

< >

( type: typing.Literal['mcp'] = 'mcp' data: typing.Dict[str, typing.Any] )

WebSocket message for MCP JSON-RPC requests.

Allows direct MCP access via WebSocket for production inference, bypassing the step() API.

class openenv.core.WSMCPResponse

< >

( type: str = 'mcp' data: typing.Dict[str, typing.Any] )

WebSocket response for MCP JSON-RPC.

Contains the JSON-RPC response from the MCP server.

MCP client

class openenv.core.MCPClientBase

< >

( base_url: str connect_timeout_s: float = 10.0 message_timeout_s: float = 60.0 provider: typing.Optional[typing.Any] = None mode: typing.Optional[str] = None )

Parameters

  • _tools_cache — Cached list of tools (populated on first list_tools() call)

Base class for MCP clients with tool discovery.

This class provides the common list_tools() method for discovering available tools from an MCP-enabled environment. Subclasses implement specific interaction patterns (tool-calling or CodeAct).

close

< >

( )

Close client resources.

In production MCP mode, this also closes the server-side persistent MCP session (best effort) before closing websocket/provider resources.

list_tools

< >

( use_cache: bool = True )

Parameters

  • use_cache — If True, return cached tools if available. Set to False to force a fresh request.

Discover available tools from the environment.

Example:

tools = await env.list_tools() for tool in tools: … print(f”{tool.name}: {tool.description}“)

class openenv.core.MCPToolClient

< >

( base_url: str connect_timeout_s: float = 10.0 message_timeout_s: float = 60.0 provider: typing.Optional[typing.Any] = None mode: typing.Optional[str] = None )

Async client for tool-calling style MCP interactions.

Each step invokes a single tool. Use this for traditional function-calling agent patterns where the agent decides which tool to call next.

This client provides convenience methods for tool discovery and invocation:

  • list_tools(): Get all available tools with their schemas
  • call_tool(name, **kwargs): Invoke a tool by name with arguments

Example (async):

async with MCPToolClient(base_url=“http://localhost:8000”) as env: … # Reset the environment … await env.reset() … … # Discover available tools … tools = await env.list_tools() … print([t.name for t in tools]) # [‘echo_message’, ‘echo_with_length’] … … # Call a tool directly … result = await env.call_tool(“echo_message”, message=“Hello!”) … print(result) # “Hello!” … … # Or use the full action interface … from openenv.core.env_server.mcp_types import CallToolAction … step_result = await env.step(CallToolAction( … tool_name=“echo_with_length”, … arguments={“message”: “Test”} … )) … print(step_result.observation.result)

Example (sync wrapper):

env = MCPToolClient(base_url=“http://localhost:8000”).sync() with env: … tools = env.list_tools() … result = env.call_tool(“echo_message”, message=“Hello!“)

call_tool

< >

( name: str **kwargs: typing.Any )

Parameters

  • name — Name of the tool to invoke (must match a tool from list_tools()).
  • **kwargs — Arguments to pass to the tool. Must match the tool’s input_schema.

Raises

RuntimeError

  • RuntimeError — If the server returns an error response.

Call a tool by name.

This is a convenience method that creates a CallToolAction, executes it, and returns the result directly. For more control, use step() with a CallToolAction directly.

Example:

result = await env.call_tool(“add”, a=5, b=3) print(result) # 8

result = await env.call_tool(“greet”, name=“Claude”) print(result) # “Hello, Claude!”

get_tool

< >

( name: str )

Parameters

  • name — Name of the tool to find.

Get a specific tool by name.

Example:

tool = await env.get_tool(“echo_message”) if tool: … print(tool.description) … print(tool.input_schema)

has_tool

< >

( name: str )

Parameters

  • name — Name of the tool to check.

Check if a tool exists.

Rubrics

class openenv.core.rubrics.Rubric

< >

( )

Abstract base class for reward computation.

A Rubric computes a reward signal from an action and observation. Subclasses implement forward() to define the reward logic.

Usage: class MyRubric(Rubric): def forward(self, action, observation) -> float: return 1.0 if action.valid else 0.0

rubric = MyRubric() reward = rubric(action, observation)

Child rubrics are auto-registered when assigned as attributes, enabling hierarchical composition and introspection.

children

< >

( )

Iterate over immediate child rubrics.

forward

< >

( action: typing.Any observation: typing.Any )

Parameters

  • action — The action taken by the agent.
  • observation — The resulting observation.

Compute the reward. Implement this in subclasses.

get_rubric

< >

( path: str )

Parameters

  • path — Dot-separated path (e.g., “code.syntax”).

Raises

KeyError

  • KeyError — If the path does not exist.

Access a nested rubric by dot-separated path.

load_state_dict

< >

( state: typing.Dict[str, typing.Any] )

Load rubric configuration from checkpoint.

named_children

< >

( )

Iterate over immediate child rubrics with names.

named_rubrics

< >

( prefix: str = '' )

Iterate over all descendant rubrics with dot-separated names.

register_forward_hook

< >

( hook: typing.Callable[[ForwardRef('Rubric'), typing.Any, typing.Any, float], NoneType] )

Parameters

  • hook — Callable with signature (rubric, action, observation, result).

Register a hook called after forward().

register_forward_pre_hook

< >

( hook: typing.Callable[[ForwardRef('Rubric'), typing.Any, typing.Any], NoneType] )

Parameters

  • hook — Callable with signature (rubric, action, observation).

Register a hook called before forward().

reset

< >

( )

Reset any internal state. Override in subclasses if needed.

rubrics

< >

( )

Iterate over all descendant rubrics (depth-first).

state_dict

< >

( )

Serialize rubric configuration for checkpointing.

class openenv.core.rubrics.Sequential

< >

( *rubrics: Rubric )

Run rubrics in order, fail-fast on zero.

Runs child rubrics in order. If any returns 0, stops immediately and returns 0. This implements hierarchical gating patterns where syntax checks run before execution checks.

Usage: rubric = Sequential( Gate(Compiles()), Gate(PassesTests(), threshold=0.5), WeightedSum([PassesTests(), StyleRubric()], weights=[0.7, 0.3]) )

forward

< >

( action: typing.Any observation: typing.Any )

Run rubrics in order, return 0 if any returns 0. Sync version.

class openenv.core.rubrics.Gate

< >

( rubric: Rubric threshold: float = 1.0 )

Threshold wrapper - returns 0 if child score is below threshold.

Useful for hard constraints like “must pass 50% of tests”.

Usage: rubric = Gate(PassesTests(), threshold=0.5)

Returns PassesTests() score if >= 0.5, else 0.0

forward

< >

( action: typing.Any observation: typing.Any )

Return child score if >= threshold, else 0. Sync version.

class openenv.core.rubrics.WeightedSum

< >

( rubrics: typing.List[openenv.core.rubrics.base.Rubric] weights: typing.List[float] )

Weighted combination of child rubrics.

Standard aggregation pattern for multi-criteria evaluation.

Usage: rubric = WeightedSum( [PassesTests(), StyleRubric()], weights=[0.7, 0.3] )

forward

< >

( action: typing.Any observation: typing.Any )

Return weighted sum of child scores. Sync version.

class openenv.core.rubrics.RubricList

< >

( rubrics: typing.List[openenv.core.rubrics.base.Rubric] = None )

Container for dynamic lists of rubrics.

Analogous to nn.ModuleList. Does not define aggregation - use within a parent rubric that implements custom logic.

Usage: class MultiGameRubric(Rubric): def init(self, games: List[str]): super().init() self.games = RubricList([GameRubric(g) for g in games])

def forward(self, action, obs) -> float: return self.games[obs.game_index](action, obs)

append

< >

( rubric: Rubric )

Add a rubric to the list.

extend

< >

( rubrics: typing.List[openenv.core.rubrics.base.Rubric] )

Add multiple rubrics to the list.

forward

< >

( action: typing.Any observation: typing.Any )

RubricList does not define aggregation - override in parent.

class openenv.core.rubrics.RubricDict

< >

( rubrics: typing.Dict[str, openenv.core.rubrics.base.Rubric] = None )

Container for named rubrics with keyed access.

Analogous to nn.ModuleDict. Enables keyed access for multi-task environments where different tasks require different rubrics.

Usage: class AtariRubric(Rubric): def init(self): super().init() self.games = RubricDict({ “pong”: PongRubric(), “breakout”: BreakoutRubric(), “space_invaders”: SpaceInvadersRubric(), })

def forward(self, action, obs) -> float: return self.games[obs.game_id](action, obs)

Access: env.rubric.games “pong”

forward

< >

( action: typing.Any observation: typing.Any )

RubricDict does not define aggregation - override in parent.

items

< >

( )

Iterate over (key, rubric) pairs.

keys

< >

( )

Iterate over keys.

update

< >

( rubrics: typing.Union[typing.Dict[str, openenv.core.rubrics.base.Rubric], typing.Mapping[str, openenv.core.rubrics.base.Rubric]] )

Update with rubrics from a dictionary.

values

< >

( )

Iterate over rubrics.

class openenv.core.rubrics.TrajectoryRubric

< >

( intermediate_reward: float = 0.0 )

Abstract base for rubrics that score based on full trajectories.

Subclasses implement:

  • score_trajectory(): Compute final score from trajectory
  • compute_step_rewards(): Define credit assignment strategy

The call method accumulates steps and returns rewards according to the subclass’s implementation.

IMPORTANT: Trajectories are stored in CPU memory to avoid GPU pressure. Environments with GPU tensors in observations must move them to CPU before returning from step().

Known limitation: Very long episodes (thousands of steps) may consume significant CPU memory. For such cases, consider streaming rubrics.

Usage: class WinLossRubric(TrajectoryRubric): def scoretrajectory(self, trajectory): , final_obs = trajectory[-1] return 1.0 if final_obs.metadata.get(‘won’) else 0.0

def compute_step_rewards(self):

Equal credit to all steps

score = self.score_trajectory(self._trajectory) return [score] * len(self._trajectory)

rubric = WinLossRubric() for action, obs in episode: reward = rubric(action, obs) # 0.0 until done step_rewards = rubric.compute_step_rewards() # Credit assignment

compute_step_rewards

< >

( )

Compute per-step rewards from the accumulated trajectory.

Define your credit assignment strategy here (e.g., discounting, assigning all credit to specific steps, etc.).

forward

< >

( action: typing.Any observation: typing.Any )

Parameters

  • action — The action taken.
  • observation — The resulting observation. Must have a ‘done’ attribute.

Accumulate step and return reward.

Returns intermediate_reward until done, then computes trajectory score.

load_state_dict

< >

( state: typing.Dict[str, typing.Any] )

Load configuration from checkpoint.

reset

< >

( )

Clear accumulated trajectory. Call on env.reset().

score_trajectory

< >

( trajectory: typing.List[typing.Tuple[typing.Any, typing.Any]] )

Parameters

  • trajectory — List of (action, observation) tuples.

Score the complete trajectory. Return 0.0-1.0.

Called when observation.done=True.

state_dict

< >

( )

Serialize configuration (not trajectory data).

class openenv.core.rubrics.ExponentialDiscountingTrajectoryRubric

< >

( gamma: float = 0.99 intermediate_reward: float = 0.0 )

TrajectoryRubric with exponential discounting for credit assignment.

Per-step reward: r_t = gamma^(T-1-t) * R_final

With gamma=0.99, later steps get higher reward (they’re “closer” to the outcome). With gamma=1.0, all steps get equal reward. With gamma=0.0, only the final step gets reward.

This is the standard temporal discounting used in reinforcement learning, applied retroactively once the episode outcome is known.

Usage: class ChessRubric(ExponentialDiscountingTrajectoryRubric): def scoretrajectory(self, trajectory): , final_obs = trajectory[-1] outcome = final_obs.metadata.get(‘winner’) if outcome == ‘agent’: return 1.0 elif outcome == ‘opponent’: return 0.0 else: return 0.5 # Draw

rubric = ChessRubric(gamma=0.99) reward = rubric(action, obs) # 0.0 until done, then final score step_rewards = rubric.compute_step_rewards() # Discounted per-step rewards

compute_step_rewards

< >

( )

Apply exponential discounting from final reward.

state_dict

< >

( )

Serialize configuration.

class openenv.core.rubrics.LLMJudge

< >

( prompt_template: str client: LLMClient score_pattern: str | None = None default_score: float = 0.0 normalize: bool = True )

Parameters

  • prompt_template — Template string with {action} and {observation} placeholders.
  • client — An LLMClient instance for making LLM calls.
  • score_pattern — Regex to extract the score from the LLM response. Defaults to matching the first decimal number.
  • default_score — Score returned when parsing fails.
  • normalize — If True, clamp extracted score to [0, 1].

Rubric that uses an LLM to evaluate agent actions/observations.

The prompt template is formatted with {action} and {observation} placeholders. The LLM response is parsed for a numeric score.

forward

< >

( action: typing.Any observation: typing.Any )

Parameters

  • action — The action taken by the agent.
  • observation — The resulting observation.

Evaluate by sending a prompt to the LLM and parsing the score.

state_dict

< >

( )

Serialize rubric configuration.

Tools

class openenv.core.tools.RepoInfo

< >

( name: str url: str commit: str clone_url: str )

Information about a repository.

class openenv.core.tools.GitServerClient

< >

( gitea_url: str username: str password: str workspace_dir: str = '/workspace' )

Parameters

  • gitea_url — URL of the Gitea server (e.g., “http://gitea:3000”)
  • username — Gitea username for authentication
  • password — Gitea password for authentication
  • workspace_dir — Local workspace directory for cloning repos

Client for connecting to an external Gitea server.

This client is optimized for task-based isolation where:

  • Multiple tasks share the same Gitea instance
  • Each task has its own isolated workspace
  • Fast reset() via git operations (no server restart)
  • Repos are pre-migrated to Gitea once

Example:

Connect to shared Gitea (credentials from environment)

import os client = GitServerClient( … gitea_url=os.getenv(“GITEA_URL”), … username=os.getenv(“GITEA_USERNAME”), … password=os.getenv(“GITEA_PASSWORD”) … ) client.wait_for_ready()

Clone repo to workspace

path = client.clone_to_workspace(“my-repo”, commit=“abc123”)

Fast reset to base state

client.reset_workspace(“my-repo”, commit=“abc123”)

clone_to_workspace

< >

( repo_name: str target_dir: str | None = None commit: str = 'main' )

Parameters

  • repo_name — Name of repository to clone
  • target_dir — Target directory name (defaults to repo_name)
  • commit — Commit hash or branch to check out

Raises

RuntimeError

  • RuntimeError — If clone fails

Clone a repository to the workspace at a specific commit.

This creates a fresh clone optimized for task isolation.

execute_git_command

< >

( command: str working_dir: str = '' )

Parameters

  • command — Git command to execute (without ‘git’ prefix)
  • working_dir — Working directory relative to workspace

Execute a git command in the workspace.

get_current_commit

< >

( repo_name: str )

Parameters

  • repo_name — Name of repository in workspace

Get current commit hash of a workspace repository.

list_repositories

< >

( )

List all repositories in Gitea.

reset_workspace

< >

( repo_name: str commit: str = 'main' )

Parameters

  • repo_name — Name of repository (directory in workspace)
  • commit — Commit hash or branch to reset to

Raises

RuntimeError

  • RuntimeError — If reset fails

Fast reset of workspace to base state (optimized for task resets).

This is much faster than re-cloning. It:

  1. Checks out the target commit
  2. Resets to that commit (hard)
  3. Cleans untracked files

wait_for_ready

< >

( timeout: int = 30 )

Parameters

  • timeout — Maximum seconds to wait

Wait for Gitea server to be ready.

workspace_exists

< >

( repo_name: str )

Check if a repository exists in workspace.

Container providers

class openenv.core.containers.runtime.ContainerProvider

< >

( )

Abstract base class for container providers.

Providers implement this interface to support different container platforms:

  • LocalDockerProvider: Runs containers on local Docker daemon
  • KubernetesProvider: Runs containers in Kubernetes cluster
  • FargateProvider: Runs containers on AWS Fargate
  • CloudRunProvider: Runs containers on Google Cloud Run

The provider manages a single container lifecycle and provides the base URL for connecting to it.

Example:

provider = LocalDockerProvider() base_url = provider.start_container(“echo-env:latest”) print(base_url) # http://localhost:8000

Use the environment via base_url

provider.stop_container()

start_container

< >

( image: str port: Optional[int] = None env_vars: Optional[Dict[str, str]] = None **kwargs: Any ) Base URL to connect to the container (e.g., “http

Parameters

  • image — Container image name (e.g., “echo-env:latest”)
  • port — Port to expose (if None, provider chooses)
  • env_vars — Environment variables to pass to container
  • **kwargs — Provider-specific options

Returns

Base URL to connect to the container (e.g., “http

//localhost:8000”)

Raises

RuntimeError

  • RuntimeError — If container fails to start

Start a container from the specified image.

stop_container

< >

( )

Stop and remove the running container.

This cleans up the container that was started by start_container().

wait_for_ready

< >

( base_url: str timeout_s: float = 30.0 )

Parameters

  • base_url — Base URL of the container
  • timeout_s — Maximum time to wait

Raises

TimeoutError

  • TimeoutError — If container doesn’t become ready in time

Wait for the container to be ready to accept requests.

This typically polls the /health endpoint until it returns 200.

class openenv.core.containers.runtime.LocalDockerProvider

< >

( )

Container provider for local Docker daemon.

This provider runs containers on the local machine using Docker. Useful for development and testing.

Example:

provider = LocalDockerProvider() base_url = provider.start_container(“echo-env:latest”)

Container running on http://localhost: <random-port>

provider.stop_container()

start_container

< >

( image: str port: Optional[int] = None env_vars: Optional[Dict[str, str]] = None **kwargs: Any )

Parameters

  • image — Docker image name
  • port — Port to expose (if None, finds available port)
  • env_vars — Environment variables for the container
  • **kwargs — Additional Docker run options

Start a Docker container locally.

stop_container

< >

( )

Stop and remove the Docker container.

wait_for_ready

< >

( base_url: str timeout_s: float = 30.0 )

Parameters

  • base_url — Base URL of the container
  • timeout_s — Maximum time to wait

Raises

TimeoutError

  • TimeoutError — If container doesn’t become ready

Wait for container to be ready by polling /health endpoint.

class openenv.core.containers.runtime.DockerSwarmProvider

< >

( auto_init_swarm: bool = True overlay_network: Optional[str] = None )

Container provider that uses Docker Swarm services for local concurrency.

This provider creates a replicated Swarm service backed by the local Docker engine. The built-in load-balancer fans requests across the replicas, allowing multiple container instances to run concurrently on the developer workstation (mirroring the workflow described in the Docker stack docs).

start_container

< >

( image: str port: Optional[int] = None env_vars: Optional[Dict[str, str]] = None **kwargs: Any )

Start (or scale) a Swarm service for the given image.

Supported kwargs: replicas (int): Number of container replicas (default: 2). cpu_limit (float | str): CPU limit passed to --limit-cpu. memory_limit (str): Memory limit passed to --limit-memory. constraints (Sequence[str]): Placement constraints. labels (Dict[str, str]): Service labels. command (Sequence[str] | str): Override container command.

stop_container

< >

( )

Remove the Swarm service (and keep the Swarm manager running).

wait_for_ready

< >

( base_url: str timeout_s: float = 30.0 )

Wait for at least one replica to become healthy by polling /health.

Note: With Swarm’s load balancer, requests round-robin across replicas, so this only verifies that at least one replica is responding. Some replicas may still be starting when this returns.

class openenv.core.containers.runtime.KubernetesProvider

< >

( )

Container provider for Kubernetes clusters.

This provider creates pods in a Kubernetes cluster and exposes them via services or port-forwarding.

Example:

provider = KubernetesProvider(namespace=“envtorch-dev”) base_url = provider.start_container(“echo-env:latest”)

Pod running in k8s, accessible via service or port-forward

provider.stop_container()

class openenv.core.containers.runtime.RuntimeProvider

< >

( )

Abstract base class for runtime providers that are not container providers. Providers implement this interface to support different runtime platforms:

  • UVProvider: Runs environments via uv run

The provider manages a single runtime lifecycle and provides the base URL for connecting to it.

Example:

provider = UVProvider(project_path=“/path/to/env”) base_url = provider.start() print(base_url) # http://localhost:8000 provider.stop()

start

< >

( port: Optional[int] = None env_vars: Optional[Dict[str, str]] = None **kwargs: Any )

Parameters

  • image — Runtime image name
  • port — Port to expose (if None, provider chooses)
  • env_vars — Environment variables for the runtime
  • **kwargs — Additional runtime options

Start a runtime from the specified image.

stop

< >

( )

Stop the runtime.

wait_for_ready

< >

( timeout_s: float = 30.0 )

Wait for the runtime to be ready to accept requests.

class openenv.core.containers.runtime.UVProvider

< >

( project_path: str app: str = 'server.app:app' host: str = '0.0.0.0' reload: bool = False env_vars: Optional[Dict[str, str]] = None context_timeout_s: float = 60.0 )

Parameters

  • project_path — Local path to a uv project (passed to uv run --project)
  • app — ASGI application path for uvicorn (defaults to server.app:app)
  • host — Host interface to bind to (defaults to 0.0.0.0)
  • reload — Whether to enable uvicorn’s reload mode
  • env_vars — Environment variables to pass through to the spawned process
  • context_timeout_s — How long to wait for the environment to become ready

RuntimeProvider implementation backed by uv run.

Example:

provider = UVProvider(project_path=“/path/to/env”) base_url = provider.start() print(base_url) # http://localhost:8000

Use the environment via base_url

provider.stop()

start

< >

( port: Optional[int] = None env_vars: Optional[Dict[str, str]] = None workers: int = 1 **_: Dict[str, str] )

Parameters

  • port — The port to bind the environment to
  • env_vars — Environment variables to pass to the environment
  • workers — The number of workers to use

Raises

RuntimeError

  • RuntimeError — If the environment is already running

Start the environment via uv run.

stop

< >

( )

Raises

RuntimeError

  • RuntimeError — If the environment is not running

Stop the environment.

wait_for_ready

< >

( timeout_s: float = 60.0 )

Parameters

  • timeout_s — The timeout to wait for the environment to become ready

Raises

RuntimeError or TimeoutError

  • RuntimeError — If the environment is not running
  • TimeoutError — If the environment does not become ready within the timeout

Wait for the environment to become ready.

Update on GitHub