Environment API¶
RepoEnv follows the Gymnasium / OpenEnv convention: reset() then repeated
step(action) until the observation says done.
from repogym import RepoEnv, Task, Read, Write, Edit, Run, Test, ListFiles, Submit
env = RepoEnv(
Task.load("tasks/bugfix-median"),
sandbox=None, # LocalSandbox by default; pass DockerSandbox(...)
agent_name="my-policy", # stored in trajectories
record_dir="trajectories", # write one JSON per episode
keep_workspace=False, # keep the temp dir for inspection
)
obs = env.reset()
Observation¶
| Field | Meaning |
|---|---|
text |
what the agent should read; middle-truncated to env.max_output_chars |
reward |
0.0 for most steps; the graded score on submit; shaped rewards if enabled |
done |
episode finished (submit or max_steps reached) |
step |
1-based step counter |
info |
action, duration, error, exit_code, and on submit passed, score, files_changed |
The initial observation is the problem statement, hints, scope, the test command, the step budget and the repository file list.
Actions¶
| Constructor | Dict form | Effect |
|---|---|---|
Read(path, start_line=None, end_line=None) |
{"kind": "read", "path": ...} |
numbered file content |
Write(path, content) |
{"kind": "write", ...} |
create/overwrite |
Edit(path, old, new) |
{"kind": "edit", ...} |
replace one unique occurrence; ambiguous or missing matches are reported, not applied |
Run(command) |
{"kind": "run", "command": ...} |
shell command; every ;/&&/\|-separated segment must start with an allowlisted executable |
Test() |
{"kind": "test"} |
run the visible test-suite and summarise target tests (hidden tests are never shown) |
ListFiles(pattern=None) |
{"kind": "list_files"} |
tracked + untracked files, optional glob |
Submit() |
{"kind": "submit"} |
grade and finish |
Errors (missing file, path escape, disallowed command, malformed action dict,
grader crash) are returned as observations with info["error"], never raised,
so a policy can recover. The only exceptions are programming errors on the
caller's side: step() before reset() or after done.
Limits and safety¶
env.max_steps: on the last step the environment auto-submits and setsinfo["truncated"] = True.env.step_timeout:run/testcommands are killed after this many seconds.- Paths are resolved inside the workspace;
.., absolute paths and.gitare rejected. - The command allowlist is per task (
env.allowed_commands). It is checked per shell segment (;,&&,||,|) with quotes respected, command substitution ($(...), backticks) is rejected, and a safety filter refusessudo,curl,wget,ssh,scp,ncandrm -rf /.
The allowlist is a guardrail, not a security boundary
An allowlisted interpreter (python, node) can run anything. The allowlist
stops accidental or lazy misuse; isolation comes from DockerSandbox. What the
agent cannot do, even with arbitrary code, is fool the graders: git metadata
lives outside the working tree and grading fails closed (score 0) if it is
destroyed, hidden tests are copied in fresh at grade time, protected test
files are restored, and benchmarks are timed externally.
Reward shaping¶
With env.reward_shaping: true, each Test() that improves the fraction of
visible fail_to_pass tests passing yields 0.1 × improvement (so at most 0.1 in
total, and 0 while any pass_to_pass test is broken). The submit reward is
unchanged.
State and trajectories¶
env.state() # EnvState(step, done, max_steps, files_changed, cumulative_reward)
env.trajectory # Trajectory(steps=[TrajectoryStep(action, observation, reward, ...)], grade, ...)
env.grade # GradeResult after submit
env.workspace # the Workspace (diff(), read(), ...) while the env is open
Trajectory files are named <task_id>__<agent>__<timestamp>.json.
Using it from another process¶
Because actions and observations are plain JSON-able models, wrapping RepoEnv in
an HTTP or WebSocket server is a few lines: