{"data":{"kind":"file","path":"README.md","version_id":"k5p0523qyhqrb40zug55zkoi","entry":{"name":"README.md","path":"README.md","is_directory":false,"size":4019,"modified_at":"2026-09-12T00:06:26.571000","content_hash":"8fe4c39ecb667796b677afac3175f3f59e2b496c2564d828cebbc01ef3f3f6bc"},"entries":[],"content":"# repaint-env\n\nA verifiers/prime-rl environment. One rollout: the agent sees a painting,\nlaunches its own headless Chromium render workers, iterates\nwrite → render → read scores, and its last valid render is its submission.\n\n## Env shape\n\n`--env.taskset.id repaint-env` imports the installed `repaint_env` package and\ntakes `RepaintTaskset` from its `__all__` — the verifiers v1 plugin contract —\nso the env works installed as a wheel (Environments Hub), not just from a\ncheckout. Nothing is repo-relative: data streams from the HF Hub and the render\ngate ships inside the package.\n\n- `taskset.py` — tasks streamed from the HF dataset (lazy generator), system\n  prompt built from the gate's `--api` spec, reward/metrics read off rollout\n  state.\n- `toolset.py` — the agent-facing tools (`launch_browser`, `render`,\n  `close_browser`) as a taskset-scoped shared MCP server. Browsers stay warm\n  across rollouts in a free pool; leases are token-checked per rollout.\n- `gate.py` — async pool over `node gate_server.js --serve --pool 1` processes,\n  one Chromium per worker. Global cap + idle-lease reaper. `ensure_gate()`\n  makes the gate runnable anywhere: the packaged copy if its `node_modules`\n  exist, else staged into `~/.cache/repaint/gate` with a one-time `npm ci`\n  (wheel installs, where site-packages may be read-only).\n- `repaint_env/gate/` — gate JS + its own `package.json`; ships inside the\n  wheel. Host needs `node` (>=24) and `npm`; Chromium comes from puppeteer.\n- `metrics.py` / `reward.py` — ported from divinci. `WEIGHTS` includes direct\n  euclidean pixel distance (`rmse: 0.2`) and EMD is `0.05`; HOD stays 0.3\n  (anti-mosaic guardrail). MDL penalty unchanged.\n\n## Data\n\nArtBench-10 (60k paintings, 256x256, 10 styles), pulled straight from the HF\nHub at taskset load — no local corpus, no build step. `--env.taskset.dataset`\n(default `zguo0525/ArtBench`) names an ArtBench-shaped HF dataset (`image` +\nstyle `ClassLabel`); `split` `train`/`eval` maps to ArtBench's train/test\n(50k / 10k). Parquet shards land in the HF cache; each task's target image is\nmaterialized once under `~/.cache/repaint/targets/` so scoring can read it by\npath.\n\nPin `--env.taskset.revision` for reproducible runs. The default repo is\nthird-party: to own the hosting, duplicate it on the Hub and change `dataset`.\n\nAnchors (the mean-fill baseline each score is normalized against) are computed\nper task at rollout setup; pass `--env.taskset.anchors` with a precomputed\njsonl (`{\"key\": ..., \"anchor\": {...}}` per line) to skip that work.\n\n## Setup (checkout)\n\n```sh\nuv venv && uv pip install -e .\n(cd repaint_env/gate && npm ci)     # Chromium: npx puppeteer browsers install chrome\n.venv/bin/python test_env.py        # smoke: pulls the eval split, renders, scores\n```\n\n## Resource management is the agent's job\n\nPer rollout the agent holds at most `tools.max_browsers` (4) workers; the machine\nholds at most `tools.global_browsers` (8). One browser renders one program at a\ntime; parallel candidate evaluation requires launching more browsers, and a cold\nlaunch costs ~1.5s while a warm checkout is instant. `BROWSER_BUSY`, `CAPACITY`\nand `BROWSER_LIMIT` come back as tool results, not errors — scheduling around\nthem is part of the task.\n\nLeases idle for `reap_idle_s` (180s) are reclaimed to the warm pool, so crashed\nor lazy rollouts cannot strand capacity.\n\n## Scoring\n\n`render` returns the exact training signal every call (reward, fidelity,\nnormalized per-metric scores, MDL penalty, size) so the agent can hill-climb.\nThe `@reward` on the task reads the last valid render from rollout state; no\nrender, or no valid render, scores `INVALID_REWARD` (−1.5).\n\nKnown ceilings: the gate needs host-local node/npm (no docker runtime yet);\n`taskset.shuffle()` materializes every task including its base64 prompt image,\nso prefer `--seed`-style sampling upstream for very large sweeps; parallel tool\ncalls in one turn sync state last-write-wins, so `renders` can undercount under\nheavy parallelism.\n","encoding":"utf-8","truncated":false,"total_bytes":4019},"status":null}