Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion responses_api_agents/swe_agents/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1716,6 +1716,45 @@ def _openhands_setup_target(self, commit: str) -> Path:
return legacy_dir
return self.setup_root / "swe_openhands_setup" / _repo_slug(self.config.agent_framework_repo) / commit

def _probe_openhands_venv(self, openhands_dir: Path) -> Optional[str]:
"""Probe an existing OpenHands venv's deps; return None if healthy, else the failure."""
venv_python = openhands_dir / ".venv" / "bin" / "python"
probe = subprocess_run(
[
str(venv_python),
"-c",
(
"from importlib.metadata import version; "
"from packaging.version import Version; "
"assert Version(version('jinja2')) >= Version('3.1.3'); "
"assert Version(version('pyjwt')) >= Version('2.9'); "
"assert Version(version('sqlalchemy')) >= Version('2.0.40'); "
"assert Version(version('flask')) >= Version('2.2'); "
"import datasets, wandb"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE: the probe's version floors (jinja2>=3.1.3, pyjwt>=2.9, sqlalchemy>=2.0.40, flask>=2.2) are decoupled from what a rebuild actually installs. openhands.sh never pins these — they come from whatever OpenHands' poetry lock resolves. If the lockfile ever resolves any of them below a floor, _existing_setup_is_reusable returns False on every server startup, so the reuse fast-path is silently disabled and each process pays a full rebuild (which resolves the same sub-floor versions and would fail the probe again next time). No infinite loop (setup() doesn't re-probe after rebuild) and no data corruption — the cost is a permanent, silent loss of the reuse optimization. If these floors encode a known-bad OpenHands version, consider pinning them in openhands.sh alongside the wandb/datasets install so the rebuild actually satisfies what the probe demands.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE: the probe's version floors (jinja2>=3.1.3, pyjwt>=2.9, sqlalchemy>=2.0.40, flask>=2.2) are decoupled from what a rebuild actually installs. openhands.sh never pins these — they come from whatever OpenHands' poetry lock resolves. If the lockfile ever resolves any of them below a floor, _existing_setup_is_reusable returns False on every server startup, so the reuse fast-path is silently disabled and each process pays a full rebuild (which then resolves the same sub-floor versions and would fail the probe again). No infinite loop (setup() doesn't re-probe after rebuild) and no data corruption — the cost is a permanent, silent loss of the reuse optimization. If these floors encode a known-bad OpenHands version, consider pinning them in openhands.sh alongside the wandb/datasets install so the rebuild actually satisfies what the probe demands.

),
],
capture_output=True,
text=True,
env={**os.environ, "PYTHONNOUSERSITE": "1"},
)
if probe.returncode == 0:
return None
return " ".join(probe.stderr.strip().splitlines()[-1:]) or f"exit code {probe.returncode}"

def _existing_setup_is_reusable(self, openhands_dir: Path) -> bool:
"""Whether a pre-existing OpenHands checkout can be reused as-is."""
if not (openhands_dir.exists() and Path(openhands_dir / ".venv" / "bin" / "python").exists()):
return False
probe_failure = self._probe_openhands_venv(openhands_dir)
if probe_failure is not None:
print(
f"OpenHands venv at {openhands_dir} failed its runtime probe ({probe_failure}); "
"rebuilding the setup instead of reusing it",
flush=True,
)
return False
return True

def setup(self) -> Path:
repo = self.config.agent_framework_repo
ref = self.config.agent_framework_commit
Expand All @@ -1739,7 +1778,7 @@ def setup(self) -> Path:
openhands_dir = setup_dir / "OpenHands"
miniforge_dir = setup_dir / "miniforge3"

if self._openhands_tree_valid(setup_dir):
if self._existing_setup_is_reusable(openhands_dir):
print(f"OpenHands already set up at {setup_dir}", flush=True)
self._sync_openhands_to_commit(openhands_dir, commit)
return setup_dir
Expand Down
4 changes: 2 additions & 2 deletions responses_api_agents/swe_agents/setup_scripts/openhands.sh
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,10 @@ done
echo "Installing Python dependencies (creating .venv in OpenHands directory)..."
poetry install --no-interaction --no-root

# Install datasets package
# Install datasets package.
echo "Installing datasets package..."

poetry run python -m pip install datasets huggingface_hub packaging==26.0
poetry run python -m pip install datasets huggingface_hub packaging==26.0 wandb

mkdir -p evaluation/oh
mkdir -p logs
Expand Down
35 changes: 35 additions & 0 deletions responses_api_agents/swe_agents/tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,41 @@ def test_get_run_command_no_replay_messages_omits_replay_file(self) -> None:
assert not (config.persistent_dir / "replay_messages.json").exists()
assert "replay_messages.json" not in self._read_agent_script(config)

def _reuse_processor(self) -> OpenHandsHarnessProcessor:
return OpenHandsHarnessProcessor(config=_minimal_server_config())

def _fake_openhands_dir(self, tmp_path: Path, probe_script: str) -> Path:
openhands_dir = tmp_path / "OpenHands"
bin_dir = openhands_dir / ".venv" / "bin"
bin_dir.mkdir(parents=True)
python = bin_dir / "python"
python.write_text(probe_script)
python.chmod(0o755)
return openhands_dir

@pytest.mark.parametrize(
("probe_script", "reusable"),
[
(None, False), # no setup at all
("#!/bin/sh\nexit 0\n", True), # healthy venv
("#!/bin/sh\nexit 1\n", False), # interpreter exists but the probe fails -> rebuild
],
)
def test_setup_reuse_gate(self, tmp_path, probe_script, reusable) -> None:
"""A pre-existing setup is reused only when its venv passes the
runtime probe: publishing a corpse makes every subsequent episode
fail after the agent has done its real work."""
openhands_dir = self._fake_openhands_dir(tmp_path, probe_script) if probe_script else tmp_path / "OpenHands"

assert self._reuse_processor()._existing_setup_is_reusable(openhands_dir) is reusable

def test_probe_reports_the_last_stderr_line(self, tmp_path) -> None:
openhands_dir = self._fake_openhands_dir(
tmp_path, "#!/bin/sh\necho 'ModuleNotFoundError: No module named wandb' >&2\nexit 1\n"
)
failure = self._reuse_processor()._probe_openhands_venv(openhands_dir)
assert failure is not None and "ModuleNotFoundError" in failure


########################################
# Workspace path + user-message resolver tests
Expand Down
Loading