OpenEnv documentation

Pi Environment for OpenEnv

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v0.4.1).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Pi Environment for OpenEnv

pi_env runs the Pi coding agent inside an isolated Hugging Face sandbox against any OpenAI-compatible LLM endpoint, optionally capturing per-token logprobs for GRPO training.

It mirrors opencode_env: same two-layer design (an in-process harness primitive + a deployable HTTP env), same transparent-proxy logprob capture, same uniform (instruction, setup, verify) Task shape. The agent is Pi instead of OpenCode, and the default sandbox backend is Hugging Face instead of E2B.

The env is task-agnostic β€” every rollout is configured at call-time with a uniform Task shape:

  • instruction β€” prompt for the agent
  • setup β€” list of bash commands run before the agent (pip install, git clone, file downloads β€” anything you need staged in the sandbox)
  • verify β€” list of bash commands run after the agent (asserts, pytest invocations, score-file writes)

Reward = passed_verify / total_verify unless any verify command writes a float to /root/logs/verifier/reward.txt (override).

In-process primitive (no HTTP)

For trainers that drive a sandbox directly without an HTTP boundary β€” this is what loop-owning GRPO training uses. The primitive reuses the sandbox backend + proxy from opencode_env, so install it alongside pi_env:

pip install "openenv-opencode-env @ git+https://github.com/huggingface/OpenEnv.git#subdirectory=envs/opencode_env"
import os
from pi_env import PiConfig, PiSessionFactory, PiTask, HFSandboxBackend

factory = PiSessionFactory(
    config=PiConfig(
        base_url="https://api.openai.com/v1",
        api_key=os.environ["OPENAI_API_KEY"],
        model="gpt-4o-mini",
        sandbox_home="/root",                   # HF sandbox execs as root
    ),
    sandbox_backend=HFSandboxBackend(image="python:3.12"),
    mode="transparent_proxy",                   # captures per-token logprobs
)
session = factory.create(task=PiTask(instruction="..."))
session.wait_for_completion()
turns = session.fetch_proxy_trace()             # per-turn (tokens, logprobs)
session.close()

Pi is pointed at the endpoint via a models.json provider block written under PI_CODING_AGENT_DIR (api: openai-completions), then launched headless with pi --print --no-session --mode json. In transparent_proxy mode the in-sandbox proxy fronts base_url, injects logprobs=true, and writes each turn’s (messages, completion_token_ids, per_token_logps) to proxy_trace.jsonl.

Sandbox backend

HFSandboxBackend (from opencode_env.sandbox, shared with opencode_env) runs the agent in a Hugging Face sandbox. image="python:3.12" cold-installs Node 22 (bootstrapped) + the Pi CLI (npm install -g @mariozechner/pi-coding-agent)

  • the proxy’s Python deps on every rollout. For faster rollouts use the pre-baked image (Node + Pi + proxy deps already installed), built by CI from hf_image/Dockerfile:
sandbox_backend=HFSandboxBackend(image="ghcr.io/huggingface/openenv-pi-sandbox:latest")

Any backend satisfying the SandboxBackend / SandboxHandle / BgJob protocols in opencode_env.sandbox.base can be plugged in the same way.

The sandbox backend and interception proxy live in opencode_env for now; the plan is to consolidate both into openenv.core so pi_env and opencode_env share them without a cross-package import.

Deployed env (HTTP)

The deployed Space exposes:

  • Web UI at /web β€” pick endpoint, write task, hit Run, watch live phase log + reward + logprobs.
  • MCP tool API at /mcp β€” programmatic run_rollout calls.
  • OpenAPI docs at /docs, health at /health.
import os
from pi_env import PiEnv

with PiEnv(base_url="https://<user>-pi-env.hf.space") as env:
    env.reset()
    result = env.run_rollout(
        endpoint="openai",                          # vllm | openai | hf_router
        api_key=os.environ["OPENAI_API_KEY"],       # or set as a Space secret
        instruction=(
            "Create binary_search.py exposing def binary_search(arr, target) -> int "
            "that returns the index of target in arr, or -1 if absent."
        ),
        setup=[],
        verify=[
            "test -f /root/workdir/binary_search.py",
            "python -c \"import sys; sys.path.insert(0, '/root/workdir'); "
            "import binary_search; "
            "assert binary_search.binary_search([1,2,3], 2) == 1; print('OK')\"",
        ],
        task_id="binary_search_v1",
    )
    print("reward:", result.reward)
    print("turns:", len(result.proxy_turns))

The MCP Tool: run_rollout

Single tool, two ways to specify the LLM endpoint:

Option A β€” endpoint shorthand (recommended): pass endpoint="vllm" (or "openai" / "hf_router"). The server resolves base_url, api_key, and model from env vars + catalog defaults. Any explicit field overrides.

Option B β€” fully explicit: pass base_url + api_key + model directly.

ArgTypeDefaultNotes
endpointstr""One of "vllm" / "openai" / "hf_router".
base_url / api_key / modelstr""Override / supply explicitly.
instructionstrrequiredPrompt passed to pi.
setuplist[str][]Bash commands run before the agent.
verifylist[str][]Bash commands run after the agent.
task_idstr""Echoed back in result.
modestr"transparent_proxy"Or "black_box" (no logprobs).
disable_thinkingbool \| NoneNone (catalog default)Inject chat_template_kwargs.enable_thinking=false.
max_tokens_capint4096Per-turn max_tokens clamp.
top_logprobsint5HF Router cap is 5; OpenAI 0–20; vLLM unbounded.
agent_timeout_sfloat600.0Hard wall budget for one pi run.
imagestr""HF sandbox image; blank β†’ python:3.12 (cold-installs Node + Pi).

Returns RolloutResult JSON with: reward, setup_results[], verify_results[], proxy_turns[], files{}, agent_log_tail, proxy_log_tail, wall_s, agent_exit_code, sandbox_id, error.

Two Operating Modes

ModeWhat it doesBest for
transparent_proxy (default)In-sandbox proxy at localhost:7000 forwards Pi’s LLM calls to base_url, injects logprobs=true, captures per-turn (messages, completion_tokens, logprobs) to proxy_trace.jsonl.GRPO / RL training, observability, top-k distillation.
black_boxNo proxy. Pi talks straight to base_url.Smoke tests, eval, SFT data collection.

Building the Docker Image

cd envs/pi_env

openenv validate           # check pyproject.toml + openenv.yaml + server/app.py + uv.lock
openenv build -t pi-env    # builds the image (uses server/Dockerfile)

# run locally with an HF token (Sandbox + Jobs access)
docker run -p 8000:8000 -e HF_TOKEN=hf_... pi-env

Or build directly:

docker build -t pi-env -f envs/pi_env/server/Dockerfile envs/pi_env

Environment Variables

VariableRequiredPurpose
HF_TOKENyes for any rolloutHugging Face sandbox credentials.
MAX_CONCURRENT_ENVSnoEnv-instance pool size. Default 4.
ENABLE_WEB_INTERFACEnoSet false to disable the /web Gradio mount. Default true.
VLLM_URL / VLLM_API_KEY / VLLM_MODELfor endpoint="vllm"OAI-compatible base URL (key defaults to intercepted).
OPENAI_API_KEY / OPENAI_BASE_URL / OPENAI_MODELfor endpoint="openai"Standard OpenAI.
HF_ROUTER_API_KEY / HF_ROUTER_BASE_URL / HF_ROUTER_MODELfor endpoint="hf_router"HF Router.

Pick provider: suffixes that actually return logprobs: Together / Nscale / Scaleway / SambaNova / Cerebras. Avoid Novita / Hyperbolic / Featherless (silent drop) and Groq (HTTP 400).

Project Structure

pi_env/
β”œβ”€β”€ README.md                       # this file
β”œβ”€β”€ openenv.yaml                    # OpenEnv space spec
β”œβ”€β”€ pyproject.toml                  # deps + ``server`` entrypoint
β”œβ”€β”€ __init__.py                     # re-exports primitive + client + models
β”‚
β”œβ”€β”€ client.py                       # PiEnv(MCPToolClient)
β”œβ”€β”€ models.py                       # RolloutResult / RolloutTurn / PiState
β”‚
β”œβ”€β”€ config.py                       # PiConfig (primitive)
β”œβ”€β”€ harness.py                      # PiSession / PiSessionFactory (CLI-only)
β”œβ”€β”€ pi_runtime.py                   # models.json builder + install/run cmds
β”œβ”€β”€ task.py                         # PiTask
β”‚
└── server/
    β”œβ”€β”€ __init__.py
    β”œβ”€β”€ app.py                      # FastAPI factory; mounts Gradio at /web
    β”œβ”€β”€ pi_environment.py           # MCPEnvironment with single ``run_rollout`` tool
    β”œβ”€β”€ gradio_ui.py                # the /web Gradio Blocks UI
    β”œβ”€β”€ catalog.py                  # endpoint shorthand resolver
    └── Dockerfile                  # multi-stage uv build (used by ``openenv build``)

The sandbox backend + interception proxy are imported from opencode_env.sandbox; pi_env ships no sandbox/ of its own.

Update on GitHub