Skip to content

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 sets info["truncated"] = True.
  • env.step_timeout: run/test commands are killed after this many seconds.
  • Paths are resolved inside the workspace; .., absolute paths and .git are 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 refuses sudo, curl, wget, ssh, scp, nc and rm -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:

obs = env.step(Action.model_validate(request_json))
return obs.model_dump()