Skip to content
Merged
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
43 changes: 3 additions & 40 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,61 +8,24 @@ e2b.toml
# Python environments and caches
.venv/
venv/
env/
ENV/
__pycache__/
*.py[cod]
*$py.class
.cache/
.pytest_cache/
.mypy_cache/
.ruff_cache/
.tox/
.nox/

# Packaging, builds, and coverage
# Packaging and builds
build/
dist/
*.egg-info/
.eggs/
pip-wheel-metadata/
.coverage
.coverage.*
htmlcov/

# Generated benchmark output and runtime state
# Generated by cybergym-e2b: template manifest and ledger, image lock, results
artifacts/
campaigns/
reports/
docs/validation/
experiments/
results/
outputs/
trajectories/
logs/
*.log
*.pid
*.pid.lock
template-manifest*.json
build-ledger*.json
credentials.json
credentials.toml
credentials.yaml
credentials.yml
*.key
*.p12
*.pfx
/*-tasks.txt
/scripts/analyze_profiles.py

# Downloaded, gated, vendored, and local-only assets
downloads/
# Pinned upstream checkout created by `cybergym-e2b sync-upstream`
vendor/
benchmark-data/
local-assets/

# Editors and operating systems
.idea/
.vscode/
.DS_Store
Thumbs.db
29 changes: 29 additions & 0 deletions FOLLOW-UPS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Follow-ups

Issues are disabled on this repository, so this file is the tracker. Delete entries as they land.
None of these affects the correctness of a completed run.

## Compatibility patch (`assets/patches/openai-compatible.patch`)

1. The `poc_file is None or ...` and `patch_file is None or ...` guards turn upstream
`run_agent()`'s exception path into a graded `no_poc` / `no_patch` failure instead of
upstream's retryable `error` status. Codex results are rescued by the trajectory check in
`_benchmark_result`; OpenHands results are not.
2. `LLM_MAX_INPUT_TOKENS=131072` equals DeepSeek V3.2's full context window, leaving no headroom
for the 8192-token completion on that model via OpenHands.

## Coverage

3. Add a plain `openai` provider (host `api.openai.com`) so customers without Fireworks or Bedrock
can run Codex. The runtime already speaks the OpenAI Responses API; this needs a
`--provider` choice, a host entry in both packaged policies, and a key name.
4. Upstream's default agent is `claude-code` with the Anthropic API. Supporting it needs
`ANTHROPIC_API_KEY` delivery through the egress proxy (upstream passes it as a container
environment variable) and `api.anthropic.com` in the packaged policies; upstream installs the
CLI with `npm install -g @anthropic-ai/claude-code` inside the agent container. Without it the
paper's reference configuration cannot be reproduced on E2B.

## Notes

5. The template tag-to-build check is cached per process on purpose; rebuilding or re-verifying
per run is not wanted.
326 changes: 139 additions & 187 deletions README.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[project]
name = "cybergym-e2e-e2b"
version = "0.2.0"
version = "0.3.0"
description = "Run CyberGym-E2E with one Docker-in-Docker E2B sandbox per task"
readme = "README.md"
license = "Apache-2.0"
license-files = ["LICENSE"]
requires-python = ">=3.12"
dependencies = [
"e2b>=2.37.0,<2.38",
"e2b>=2.37,<3",
"httpx>=0.28,<0.29",
"tomli-w>=1.2,<2",
]
Expand Down
17 changes: 0 additions & 17 deletions scripts/check_public_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,8 @@
".ruff_cache",
"__pycache__",
"artifacts",
"benchmark-data",
"build",
"campaigns",
"dist",
"downloads",
"experiments",
"local-assets",
"logs",
"outputs",
"results",
"trajectories",
"reports",
"validation",
"vendor",
}

Expand All @@ -43,10 +32,6 @@
re.compile(r"^template-manifest(?:\..+)?\.json$"),
re.compile(r"^build-ledger(?:\..+)?\.json$"),
)
_FORBIDDEN_PATH_PATTERNS = (
re.compile(r"^[^/]+-tasks\.txt$"),
re.compile(r"^scripts/analyze_profiles\.py$"),
)


def _is_forbidden(path: str) -> bool:
Expand All @@ -60,8 +45,6 @@ def _is_forbidden(path: str) -> bool:
return True
if any(pattern.fullmatch(filename) for pattern in _GENERATED_FILE_PATTERNS):
return True
if any(pattern.fullmatch(normalized) for pattern in _FORBIDDEN_PATH_PATTERNS):
return True
return any(
part in _GENERATED_DIRECTORIES
or part in _LOCAL_ENVIRONMENT_DIRECTORIES
Expand Down
132 changes: 103 additions & 29 deletions src/cybergym_e2b/assets/patches/openai-compatible.patch
Original file line number Diff line number Diff line change
@@ -1,41 +1,100 @@
diff --git a/scripts/install_validate_deps.sh b/scripts/install_validate_deps.sh
index 6834b1c..11da429 100644
--- a/scripts/install_validate_deps.sh
+++ b/scripts/install_validate_deps.sh
@@ -1,7 +1,9 @@
#!/bin/bash

-apt-get update
-apt-get install -y curl
+if ! command -v curl >/dev/null; then
+ apt-get update
+ apt-get install -y curl
+fi

curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.local/bin/env
diff --git a/scripts/run_agent.py b/scripts/run_agent.py
index bb67950..aae4d4f 100644
index bb67950..2bd8b25 100644
--- a/scripts/run_agent.py
+++ b/scripts/run_agent.py
@@ -631,0 +632 @@ model_provider = "openai_http"
@@ -629,11 +629,13 @@ EOF''',
openai_base_url = "{openai_base_url}"
web_search = "disabled"
model_provider = "openai_http"
+model_reasoning_effort = "{args.reasoning_effort}"
@@ -636,0 +638 @@ supports_websockets = false

[model_providers.openai_http]
name = "OpenAI HTTP"
base_url = "{openai_base_url}"
supports_websockets = false
+wire_api = "responses"
@@ -788,0 +791,6 @@ def run_agent(args, config, script_path, data_path, prompt, attempt, work_dir, t
EOF''',
"Adding OPENAI_BASE_URL to config.toml"
)
@@ -786,6 +788,12 @@ def run_agent(args, config, script_path, data_path, prompt, attempt, work_dir, t

print(f" Container: {container_id[:12]}")

+ e2b_ca_bundle = os.environ.get("E2B_CA_BUNDLE")
+ if e2b_ca_bundle:
+ copy_to_container(
+ container_id, Path(e2b_ca_bundle), "/etc/ssl/certs/ca-certificates.crt"
+ )
+
@@ -881 +890 @@ def run_agent_loop(args, config, script_path, data_path, run_dir):
# Setup workspace (don't copy ground truth PoC - agent shouldn't see it)
setup_workspace(container_id, data_path, script_path, args.mode, copy_gt_poc=False, scripts_dir=scripts_dir)

@@ -878,7 +886,7 @@ def run_agent_loop(args, config, script_path, data_path, run_dir):
print(f" Agent: {agent_time:.1f}s ({agent_time/60:.1f}m), exec: {agent_exec_time:.1f}s ({agent_exec_time/60:.1f}m), exit={exit_code}")

# Check for generated files
- if args.mode == "e2e" and not poc_file.exists():
+ if args.mode == "e2e" and (poc_file is None or not poc_file.exists()):
@@ -896 +905 @@ def run_agent_loop(args, config, script_path, data_path, run_dir):
print(" No PoC generated!")
all_attempts.append({
"attempt": attempt,
@@ -893,7 +901,7 @@ def run_agent_loop(args, config, script_path, data_path, run_dir):
feedback = "\n=== Previous Attempt Failed ===\nNo poc.bin was generated."
continue

- if not patch_file.exists():
+ if patch_file is None or not patch_file.exists():
@@ -972 +981,5 @@ def run_agent_loop(args, config, script_path, data_path, run_dir):
print(" No patch generated!")
all_attempts.append({
"attempt": attempt,
@@ -969,7 +977,11 @@ def run_agent_loop(args, config, script_path, data_path, run_dir):
final_status = "success"
break
else:
- trajectory_summary = summarize_trajectory(log_file, attempt, args)
+ trajectory_summary = (
+ summarize_trajectory(log_file, attempt, args)
+ if attempt < args.max_attempts
+ else None
+ )
@@ -1049 +1057 @@ Examples:
feedback = format_feedback(
results, attempt, args.mode,
poc_file=attempt_poc,
@@ -1045,8 +1057,9 @@ Examples:
parser.add_argument("--default-build-image",
default="gcr.io/oss-fuzz-base/base-builder@sha256:8eda74a11e800aead5a041ee479a65b33dab3150d6e89e5694e2b6eb27be98fc")

+ parser.add_argument("--reasoning-effort", choices=["low", "medium", "high", "xhigh"], default="high")
# LLM configuration
- parser.add_argument("--model-provider", choices=["litellm", "bedrock", "anthropic"], default="anthropic",
+ parser.add_argument("--model-provider", choices=["litellm", "bedrock", "anthropic", "openai-compatible"], default="anthropic",
@@ -1051,0 +1060 @@ Examples:
+ parser.add_argument("--reasoning-effort", choices=["low", "medium", "high", "xhigh"], default="high")
help="LLM provider (default: anthropic)")
parser.add_argument("--litellm-model-id", default="openai/gpt-5.2-codex")
parser.add_argument("--bedrock-model-id", default="us.anthropic.claude-sonnet-4-5-20250929-v1:0")
diff --git a/scripts/utils.py b/scripts/utils.py
index f8b8b82..54ae678 100644
index f8b8b82..2b20e1d 100644
--- a/scripts/utils.py
+++ b/scripts/utils.py
@@ -176,0 +177,12 @@ def setup_workspace(container_id, data_path, script_path, mode="e2e", copy_gt_po
@@ -174,15 +174,25 @@ def setup_workspace(container_id, data_path, script_path, mode="e2e", copy_gt_po
)

# Install validation dependencies (sudo for build scripts, tomli for validate.py)
+ if not scripts_dir:
+ raise ValueError("scripts_dir required for openhands")
+ # Install both package-manager entry points before the first update. Task scripts
Expand All @@ -48,13 +107,23 @@ index f8b8b82..54ae678 100644
+ )
+ exec_run(container_id, "chmod 0755 /usr/local/bin/apt /usr/local/bin/apt-get", workdir="/", verbose=False, check=True)
+
@@ -179 +191 @@ def setup_workspace(container_id, data_path, script_path, mode="e2e", copy_gt_po
exec_run(
container_id,
- "apt-get update -qq && apt-get install -y -qq sudo git >/dev/null 2>&1",
+ "if ! command -v sudo >/dev/null; then printf '#!/bin/sh\\nif [ \"$1\" = \"-E\" ]; then shift; fi\\nexec \"$@\"\\n' > /usr/local/bin/sudo && chmod 0755 /usr/local/bin/sudo; fi; if ! command -v git >/dev/null; then grep -rLiE 'archive.ubuntu.com|security.ubuntu.com|ports.ubuntu.com|old-releases.ubuntu.com|deb.debian.org|security.debian.org' /etc/apt/sources.list.d/ 2>/dev/null | xargs -r -I{} mv {} /tmp/ 2>/dev/null; sed -ri '/apt.kitware.com|bazel-apt|apt.llvm.org|packages.cloud.google.com|storage.googleapis.com|download.docker.com|dl.yarnpkg.com|deb.nodesource.com|ppa.launchpad|nginx.org|packagecloud.io|cran.r-project|pgdg/d' /etc/apt/sources.list 2>/dev/null; find /etc/apt/sources.list.d -type f -exec sed -ri '/apt.kitware.com|bazel-apt|apt.llvm.org|packages.cloud.google.com|storage.googleapis.com|download.docker.com|dl.yarnpkg.com|nginx.org|packagecloud.io|cran.r-project|pgdg/d' {} + 2>/dev/null; apt-get update -qq && apt-get install -y -qq git >/dev/null 2>&1; fi",
@@ -184,2 +195,0 @@ def setup_workspace(container_id, data_path, script_path, mode="e2e", copy_gt_po
workdir="/",
verbose=False,
check=True,
)
- if not scripts_dir:
- raise ValueError("scripts_dir required for openhands")
@@ -198,0 +209,9 @@ def setup_workspace(container_id, data_path, script_path, mode="e2e", copy_gt_po
copy_to_container(container_id, scripts_dir / "install_validate_deps.sh", "/install_validate_deps.sh")
exec_run(container_id, "bash -eux /install_validate_deps.sh", workdir="/", verbose=False, check=True)

@@ -196,6 +206,15 @@ def setup_workspace(container_id, data_path, script_path, mode="e2e", copy_gt_po
copy_to_container(container_id, data_path / "src.tgz", "/src/src.tgz")
exec_run(container_id, "tar xf /src/src.tgz -C /src && rm /src/src.tgz", workdir="/", verbose=False, check=True)

+ # The Opus model host offers an rsync mirror but its HTTPS endpoint is
+ # intermittently unavailable. Hot E2B templates carry the checksum-pinned
+ # archive so FFmpeg setup remains reproducible under restricted egress.
Expand All @@ -64,7 +133,13 @@ index f8b8b82..54ae678 100644
+ ).returncode == 0:
+ copy_to_container(container_id, opus_model_cache, f"/src/opus/{Path(opus_model_cache).name}")
+
@@ -331,0 +351,24 @@ def call_llm(
# Copy crash.log and poc.bin for patch-only mode (to /src for agent to see)
if mode == "patch-only":
for f in ["crash.log", "poc.bin"]:
@@ -330,6 +349,30 @@ def call_llm(
messages=[{"role": "user", "content": prompt}],
)
return response.content[0].text
+ elif model_provider == "openai-compatible":
+ import openai
+
Expand All @@ -89,28 +164,27 @@ index f8b8b82..54ae678 100644
+ )
+ return response.choices[0].message.content
+ raise ValueError(f"unsupported OpenAI-compatible wire API: {wire_api}")
@@ -385,0 +429,5 @@ def get_llm_env(
else:
# OpenAI
import openai
@@ -383,8 +426,20 @@ def get_llm_env(
"LLM_RETRY_MIN_WAIT": "15",
"LLM_RETRY_MAX_WAIT": "120",
"LLM_RETRY_MULTIPLIER": "2",
+ # OpenAI-compatible catalog IDs are absent from LiteLLM's static metadata.
+ # Pin conservative limits so OpenHands does not assume a 4K context window.
+ "LLM_MAX_INPUT_TOKENS": "131072",
+ "LLM_MAX_OUTPUT_TOKENS": "8192",
+ "LLM_NATIVE_TOOL_CALLING": "true",
@@ -387,0 +436,7 @@ def get_llm_env(
}

+ if os.getenv("E2B_CA_BUNDLE"):
+ base_env.update({
+ "SSL_CERT_FILE": "/etc/ssl/certs/ca-certificates.crt",
+ "REQUESTS_CA_BUNDLE": "/etc/ssl/certs/ca-certificates.crt",
+ "NODE_EXTRA_CA_CERTS": "/etc/ssl/certs/ca-certificates.crt",
+ })
+
diff --git a/scripts/install_validate_deps.sh b/scripts/install_validate_deps.sh
index 42ca8a3..4a155df 100644
--- a/scripts/install_validate_deps.sh
+++ b/scripts/install_validate_deps.sh
@@ -3,2 +3,4 @@
-apt-get update
-apt-get install -y curl
+if ! command -v curl >/dev/null; then
+ apt-get update
+ apt-get install -y curl
+fi
if model_provider == "bedrock":
try:
session = boto3.Session(profile_name=aws_profile)
2 changes: 1 addition & 1 deletion src/cybergym_e2b/assets/policies/network-locked.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"deny_out": [],
"model_hosts": [
"api.fireworks.ai",
"bedrock-mantle.us-west-2.api.aws"
"bedrock-mantle.*.api.aws"
],
"artifact_hosts": [
"huggingface.co",
Expand Down
2 changes: 1 addition & 1 deletion src/cybergym_e2b/assets/policies/network.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
],
"model_hosts": [
"api.fireworks.ai",
"bedrock-mantle.us-west-2.api.aws"
"bedrock-mantle.*.api.aws"
],
"artifact_hosts": [
"huggingface.co",
Expand Down
Loading
Loading