mirror of
https://github.com/priyanshujain/messageboardbench.git
synced 2026-10-04 20:17:06 +00:00
Checkpoint experiments through SWE pilot v2
This commit is contained in:
1 parent
abacd5c5e1
commit
72d77018d8
845 files changed
+431756
-41
No files matched your search
@@ -0,0 +1,239 @@
|
||||
"""Host-side shared-team infrastructure. Nothing here calls a model provider."""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import errno
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import stat
|
||||
import subprocess
|
||||
import uuid
|
||||
|
||||
TEAM_COMPOSE = Path(__file__).resolve().parents[2] / "compose.team.yaml"
|
||||
IMAGE = "aisiuk/inspect-tool-support"
|
||||
|
||||
|
||||
def render_team_compose(team_dir: Path, config_path: Path) -> Path:
|
||||
"""Render a literal compose file usable during Inspect's pre-sample task init.
|
||||
|
||||
Inspect enumerates services before sample metadata interpolation, so a required
|
||||
metadata variable cannot remain in the configuration passed to Task. The runner
|
||||
must pass a directory created by prepare_team_directory (or validated against
|
||||
its run root with validate_team_directory).
|
||||
"""
|
||||
import yaml
|
||||
|
||||
directory = Path(team_dir)
|
||||
if directory.is_symlink() or not directory.is_dir():
|
||||
raise ValueError("Team mount source must be an existing nonsymlink directory")
|
||||
directory = directory.resolve(strict=True)
|
||||
if directory.parent.name != "shared":
|
||||
raise ValueError("Team mount source must be directly inside a shared directory")
|
||||
_identifier(directory.name)
|
||||
config = yaml.safe_load(TEAM_COMPOSE.read_text())
|
||||
config["services"]["default"]["volumes"][0]["source"] = str(directory)
|
||||
destination = Path(config_path).absolute()
|
||||
if destination.resolve().is_relative_to(directory):
|
||||
raise ValueError("Compose configuration must stay outside the agent mount")
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
contents = json.dumps(config, indent=2) + "\n"
|
||||
if destination.exists():
|
||||
if destination.read_text() != contents:
|
||||
raise ValueError("Existing compose configuration differs; use a new path")
|
||||
else:
|
||||
destination.write_text(contents)
|
||||
return destination
|
||||
|
||||
|
||||
def _identifier(value: str) -> str:
|
||||
if not re.fullmatch(r"[a-zA-Z0-9][a-zA-Z0-9_-]{0,79}", value):
|
||||
raise ValueError("Team and agent identifiers must be simple names")
|
||||
return value
|
||||
|
||||
|
||||
def validate_team_directory(path: Path, run_dir: Path) -> Path:
|
||||
"""Accept only an existing, nonsymlink team directly below run/shared."""
|
||||
root = run_dir.resolve() / "shared"
|
||||
candidate = Path(path).absolute()
|
||||
if root.is_symlink() or candidate.is_symlink():
|
||||
raise ValueError("Shared directories cannot be symbolic links")
|
||||
resolved = candidate.resolve(strict=True)
|
||||
if resolved.parent != root or not resolved.is_dir():
|
||||
raise ValueError("Team directory must be directly within run_dir/shared")
|
||||
_identifier(resolved.name)
|
||||
return resolved
|
||||
|
||||
|
||||
def prepare_team_directory(run_dir: Path, team_id: str, agent_ids: list[str]) -> Path:
|
||||
"""Make a fresh team namespace. Reuse fails to prevent accidental contamination."""
|
||||
_identifier(team_id)
|
||||
if not agent_ids or len(set(agent_ids)) != len(agent_ids):
|
||||
raise ValueError("Provide distinct agent identifiers")
|
||||
for agent_id in agent_ids:
|
||||
_identifier(agent_id)
|
||||
root = run_dir.resolve() / "shared"
|
||||
if root.is_symlink():
|
||||
raise ValueError("Shared directory cannot be a symbolic link")
|
||||
root.mkdir(parents=True, exist_ok=True)
|
||||
team = root / team_id
|
||||
team.mkdir() # deliberately fail if an earlier experiment used this directory
|
||||
(team / "board").mkdir()
|
||||
(team / "agents").mkdir()
|
||||
for agent_id in agent_ids:
|
||||
(team / "agents" / agent_id).mkdir()
|
||||
return validate_team_directory(team, run_dir)
|
||||
|
||||
|
||||
def snapshot_team_directory(path: Path, max_files: int = 1000,
|
||||
max_bytes: int = 64000) -> dict:
|
||||
"""Bounded snapshot without following agent-created symlinks or reading devices.
|
||||
|
||||
This is team state, not proof that a particular agent wrote/read a file. During
|
||||
concurrent execution it is not an atomic snapshot; archive again between waves.
|
||||
sha256 covers the captured bytes (explicitly a prefix for truncated files).
|
||||
Entries deleted or replaced while scanning receive explicit transient records.
|
||||
Other file/directory errors propagate as infrastructure failures.
|
||||
"""
|
||||
if max_files < 1 or max_bytes < 1:
|
||||
raise ValueError("Snapshot limits must be positive")
|
||||
if path.is_symlink():
|
||||
raise ValueError("Snapshot root cannot be a symbolic link")
|
||||
root_fd = os.open(path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
|
||||
records: dict = {}
|
||||
|
||||
def walk(directory_fd: int, prefix: str = "") -> None:
|
||||
for name in sorted(os.listdir(directory_fd)):
|
||||
if len(records) >= max_files:
|
||||
raise ValueError("Shared snapshot exceeded its entry limit")
|
||||
relative = f"{prefix}/{name}" if prefix else name
|
||||
try:
|
||||
capture(directory_fd, name, relative)
|
||||
except OSError as error:
|
||||
# Atomic saves commonly unlink/rename temporary files while another
|
||||
# agent is scanning. O_NOFOLLOW also rejects a symlink substituted
|
||||
# between stat and open; record that race without following it.
|
||||
if error.errno not in (errno.ENOENT, errno.ENOTDIR, errno.ELOOP):
|
||||
raise
|
||||
records[relative] = {
|
||||
"kind": "transient", "skipped": True,
|
||||
"reason": "entry disappeared or changed type during snapshot",
|
||||
"errno": error.errno,
|
||||
}
|
||||
|
||||
def capture(directory_fd: int, name: str, relative: str) -> None:
|
||||
info = os.stat(name, dir_fd=directory_fd, follow_symlinks=False)
|
||||
if stat.S_ISLNK(info.st_mode):
|
||||
records[relative] = {"kind": "symlink", "skipped": True}
|
||||
elif stat.S_ISDIR(info.st_mode):
|
||||
records[relative] = {"kind": "directory"}
|
||||
fd = os.open(name, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW,
|
||||
dir_fd=directory_fd)
|
||||
try:
|
||||
walk(fd, relative)
|
||||
finally:
|
||||
os.close(fd)
|
||||
elif stat.S_ISREG(info.st_mode):
|
||||
fd = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK,
|
||||
dir_fd=directory_fd)
|
||||
try:
|
||||
if not stat.S_ISREG(os.fstat(fd).st_mode):
|
||||
records[relative] = {"kind": "transient", "skipped": True,
|
||||
"reason": "file changed type during snapshot"}
|
||||
return
|
||||
with os.fdopen(fd, "rb", closefd=False) as stream:
|
||||
data = stream.read(max_bytes + 1)
|
||||
finally:
|
||||
os.close(fd)
|
||||
truncated = len(data) > max_bytes
|
||||
data = data[:max_bytes]
|
||||
records[relative] = {
|
||||
"kind": "file", "content": data.decode("utf-8", errors="replace"),
|
||||
"sha256": hashlib.sha256(data).hexdigest(),
|
||||
"captured_bytes": len(data), "truncated": truncated,
|
||||
}
|
||||
else:
|
||||
records[relative] = {"kind": "special", "skipped": True}
|
||||
|
||||
try:
|
||||
walk(root_fd)
|
||||
finally:
|
||||
os.close(root_fd)
|
||||
return records
|
||||
|
||||
|
||||
def validate_shared_docker(run_dir: Path) -> dict:
|
||||
"""Explicit no-model integration check using two disposable Docker services.
|
||||
|
||||
Uses the production image, mount target, workdir and network restriction.
|
||||
Fails if Docker is unavailable. Leaves the compose file, shared evidence and
|
||||
result on the host; removes only the containers created by this check.
|
||||
"""
|
||||
subprocess.run(["docker", "info", "--format", "{{.ServerVersion}}"],
|
||||
check=True, capture_output=True, text=True, timeout=15)
|
||||
check_dir = run_dir.resolve() / f"infrastructure-check-{uuid.uuid4().hex[:10]}"
|
||||
team = prepare_team_directory(check_dir, "validation", ["agent-1", "agent-2"])
|
||||
sentinel = check_dir / "host-only-log.txt"
|
||||
sentinel.write_text("This researcher log must never be mounted.\n")
|
||||
service = {
|
||||
"image": IMAGE, "command": ["tail", "-f", "/dev/null"],
|
||||
"working_dir": "/workspace", "init": True, "network_mode": "none",
|
||||
"stop_grace_period": "1s", "mem_limit": "1gb",
|
||||
"volumes": [{"type": "bind", "source": str(team),
|
||||
"target": "/workspace/scratch", "bind": {"create_host_path": False}}],
|
||||
}
|
||||
compose = check_dir / "compose.validation.json"
|
||||
compose.write_text(json.dumps({"services": {"agent1": service, "agent2": service}}, indent=2))
|
||||
project = "mbb-check-" + uuid.uuid4().hex[:10]
|
||||
command = ["docker", "compose", "-p", project, "-f", str(compose)]
|
||||
|
||||
def run(*args: str, timeout: int = 60) -> str:
|
||||
return subprocess.run([*command, *args], check=True, capture_output=True,
|
||||
text=True, timeout=timeout).stdout
|
||||
|
||||
try:
|
||||
run("up", "-d", timeout=300)
|
||||
run("exec", "-T", "agent1", "python", "-c",
|
||||
"from pathlib import Path; Path('/workspace/func.py').write_text('agent-one'); "
|
||||
"Path('/workspace/scratch/agents/agent-1/note.txt').write_text('hello-team')")
|
||||
run("exec", "-T", "agent2", "python", "-c",
|
||||
"from pathlib import Path; "
|
||||
"assert Path('/workspace/scratch/agents/agent-1/note.txt').read_text() == 'hello-team'; "
|
||||
"assert not Path('/workspace/func.py').exists(); "
|
||||
"Path('/workspace/func.py').write_text('agent-two'); "
|
||||
"Path('/workspace/scratch/board/reply.txt').write_text('received')")
|
||||
run("exec", "-T", "agent1", "python", "-c",
|
||||
"from pathlib import Path; "
|
||||
"assert Path('/workspace/func.py').read_text() == 'agent-one'; "
|
||||
"assert Path('/workspace/scratch/board/reply.txt').read_text() == 'received'")
|
||||
image_evidence = {}
|
||||
for agent in ("agent1", "agent2"):
|
||||
container = run("ps", "-q", agent).strip()
|
||||
inspected = subprocess.run(["docker", "inspect", container], check=True,
|
||||
capture_output=True, text=True, timeout=30)
|
||||
container_info = json.loads(inspected.stdout)[0]
|
||||
mounts = container_info["Mounts"]
|
||||
image_id = container_info["Image"]
|
||||
image_inspected = subprocess.run(["docker", "image", "inspect", image_id],
|
||||
check=True, capture_output=True,
|
||||
text=True, timeout=30)
|
||||
image_info = json.loads(image_inspected.stdout)[0]
|
||||
image_evidence[agent] = {
|
||||
"requested_image": IMAGE, "image_id": image_id,
|
||||
"repo_digests": image_info.get("RepoDigests") or [],
|
||||
}
|
||||
assert len(mounts) == 1, f"Unexpected additional mounts: {mounts}"
|
||||
assert mounts[0]["Source"] == str(team)
|
||||
assert mounts[0]["Destination"] == "/workspace/scratch"
|
||||
run("exec", "-T", agent, "python", "-c",
|
||||
f"from pathlib import Path; assert not Path({str(sentinel)!r}).exists(); "
|
||||
"assert not Path('/workspace/host-only-log.txt').exists()")
|
||||
result = {"success": True, "cross_container_visibility": True,
|
||||
"grading_files_isolated": True, "only_team_directory_mounted": True,
|
||||
"images": image_evidence,
|
||||
"snapshot": snapshot_team_directory(team), "compose": str(compose)}
|
||||
(check_dir / "result.json").write_text(json.dumps(result, indent=2) + "\n")
|
||||
return result
|
||||
finally:
|
||||
run("down", "--timeout", "1", timeout=60)
|
||||
Reference in new issue
Block a user