-
Notifications
You must be signed in to change notification settings - Fork 417
Add pi_env: Pi coding-agent environment (HF sandbox + transparent-proxy logprobs)
#999
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
sergiopaniego
wants to merge
20
commits into
huggingface:main
Choose a base branch
from
sergiopaniego:pi-hf-sandbox-backend
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+6,800
−0
Open
Changes from 3 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
e680120
Add Pi coding-agent harness (pi_env) reusing opencode_env sandbox + p…
sergiopaniego 38876d2
Add deployable HTTP env layer to pi_env (parity with opencode_env)
sergiopaniego 01168b4
Add pi_env to the docs (env stub + catalog card + toctree nav)
sergiopaniego 2501c2c
Format test_pi_runtime.py (usort import order + ruff line wrap)
sergiopaniego 271f94a
Address review: fix docs links, node version gate, black-box model id…
sergiopaniego 7798e3c
Keep server/client separation in pi_env server; correct setup timing …
sergiopaniego 82d3c19
Address review: cover cold-bootstrap in timeouts, doc peer-dep, drop …
sergiopaniego dca01db
Add pre-baked HF sandbox image for pi_env; fix mode docstring
sergiopaniego e1e3c27
Address review: skip verify/reward on failed setup; pipefail proxy-de…
sergiopaniego 8bb0c3b
Give setup commands a longer timeout than verify (pip install / downl…
sergiopaniego 95d7d4f
Use get_token() for the HF credential check; gate proxy deps vs sourc…
sergiopaniego 68eb2b8
Drop unused os import in pi_environment (token check now uses get_token)
sergiopaniego a22b02e
Merge branch 'main' into pi-hf-sandbox-backend
sergiopaniego 599718e
Default PiEnv client message timeout to cover a full rollout (server …
sergiopaniego 9bbec18
Merge branch 'main' into pi-hf-sandbox-backend
sergiopaniego daa57f2
Retry PiSessionFactory.create() with backoff
sergiopaniego 75d354e
Merge remote-tracking branch 'origin/main' into pi-hf-sandbox-backend
sergiopaniego bec6fe5
Merge branch 'main' into pi-hf-sandbox-backend
sergiopaniego 01f9dca
Run setup before the agent and skip verify on a non-zero agent exit
sergiopaniego e96444c
Merge branch 'main' into pi-hf-sandbox-backend
sergiopaniego File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,210 @@ | ||
| <!-- openenv-source: pi_env --> | ||
| # Pi Environment for OpenEnv | ||
|
|
||
| `pi_env` runs the [Pi](https://github.com/badlogic/pi-mono) coding agent | ||
| inside an isolated [Hugging Face sandbox](https://huggingface.co/docs/huggingface_hub/guides/sandboxes) | ||
| against any OpenAI-compatible LLM endpoint, optionally capturing per-token | ||
| logprobs for GRPO training. | ||
|
|
||
| It mirrors [`opencode_env`](../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: | ||
|
|
||
| ```python | ||
| 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. 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`. | ||
|
|
||
| ```python | ||
| 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. | ||
|
|
||
| | Arg | Type | Default | Notes | | ||
| |---|---|---|---| | ||
| | `endpoint` | `str` | `""` | One of `"vllm"` / `"openai"` / `"hf_router"`. | | ||
| | `base_url` / `api_key` / `model` | `str` | `""` | Override / supply explicitly. | | ||
| | `instruction` | `str` | required | Prompt passed to `pi`. | | ||
| | `setup` | `list[str]` | `[]` | Bash commands run **before** the agent. | | ||
| | `verify` | `list[str]` | `[]` | Bash commands run **after** the agent. | | ||
| | `task_id` | `str` | `""` | Echoed back in result. | | ||
| | `mode` | `str` | `"transparent_proxy"` | Or `"black_box"` (no logprobs). | | ||
| | `disable_thinking` | `bool \| None` | `None` (catalog default) | Inject `chat_template_kwargs.enable_thinking=false`. | | ||
| | `max_tokens_cap` | `int` | `4096` | Per-turn `max_tokens` clamp. | | ||
| | `top_logprobs` | `int` | `5` | HF Router cap is 5; OpenAI 0–20; vLLM unbounded. | | ||
| | `agent_timeout_s` | `float` | `600.0` | Hard wall budget for one `pi` run. | | ||
| | `image` | `str` | `""` | 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 | ||
|
|
||
| | Mode | What it does | Best 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_box`** | No proxy. Pi talks straight to `base_url`. | Smoke tests, eval, SFT data collection. | | ||
|
|
||
| ## Building the Docker Image | ||
|
|
||
| ```bash | ||
| 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: | ||
|
|
||
| ```bash | ||
| docker build -t pi-env -f envs/pi_env/server/Dockerfile envs/pi_env | ||
| ``` | ||
|
|
||
| ## Environment Variables | ||
|
|
||
| | Variable | Required | Purpose | | ||
| |---|---|---| | ||
| | `HF_TOKEN` | **yes** for any rollout | Hugging Face sandbox credentials. | | ||
| | `MAX_CONCURRENT_ENVS` | no | Env-instance pool size. Default `4`. | | ||
| | `ENABLE_WEB_INTERFACE` | no | Set `false` to disable the `/web` Gradio mount. Default `true`. | | ||
| | `VLLM_URL` / `VLLM_API_KEY` / `VLLM_MODEL` | for `endpoint="vllm"` | OAI-compatible base URL (key defaults to `intercepted`). | | ||
| | `OPENAI_API_KEY` / `OPENAI_BASE_URL` / `OPENAI_MODEL` | for `endpoint="openai"` | Standard OpenAI. | | ||
| | `HF_ROUTER_API_KEY` / `HF_ROUTER_BASE_URL` / `HF_ROUTER_MODEL` | for `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`](../opencode_env/sandbox); `pi_env` ships no `sandbox/` | ||
| of its own. | ||
|
|
||
| ## References | ||
|
|
||
| - [OpenEnv docs](https://huggingface.co/docs/openenv) | ||
| - [Pi coding agent](https://github.com/badlogic/pi-mono) | ||
| - [Hugging Face sandboxes](https://huggingface.co/docs/huggingface_hub/guides/sandboxes) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| # Don't bloat the build context. Copy only what the runtime needs. | ||
| .venv | ||
| __pycache__ | ||
| **/__pycache__ | ||
| *.pyc | ||
| *.pyo | ||
| .pytest_cache | ||
| .mypy_cache | ||
| .ruff_cache | ||
|
|
||
| # NEVER ship secrets into the image. The container reads E2B_API_KEY etc. | ||
| # from runtime env vars (HF Space secrets, ``docker run -e``). | ||
| .env | ||
| .env.* | ||
|
|
||
| # Test artifacts and local debug output. | ||
| tests/_artifacts/ | ||
| *.log | ||
| .coverage | ||
| htmlcov/ | ||
|
|
||
| # Editor / IDE noise. | ||
| .idea | ||
| .vscode | ||
| .DS_Store | ||
|
|
||
| # Git metadata is unused at runtime. | ||
| .git | ||
| .gitignore | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # Tests are dev-only — never ship into the deployable env. | ||
| tests/ | ||
|
|
||
| # Local debug artifacts (covered above, kept for clarity). | ||
| *.log | ||
|
|
||
| # Local secrets / venv (also covered at repo root, kept here for safety). | ||
| .env | ||
| .env.* | ||
| .venv | ||
| __pycache__/ | ||
| *.pyc |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in
271f94a6— the comment now referencesHF_TOKEN.