Skip to content

Commit 3d3f9e6

Browse files
authored
Merge pull request #6305 from loopx-project/codex/research-checkpoint-continuation-20261012
feat(benchmark): checkpoint research participants and continue native sessions
2 parents c185e3f + 5eb9baa commit 3d3f9e6

50 files changed

Lines changed: 9537 additions & 74 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎benchmark/README.md‎

Lines changed: 625 additions & 0 deletions
Large diffs are not rendered by default.

‎benchmark/edgebench/checkpoint_controller.py‎

Lines changed: 414 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
"""Opt-in read-only checkpoint participant over the native SForge judge.
2+
3+
Native admission, grading, history and ranking retain their owners. This
4+
adapter exports a quiescent registration's public history and opportunity
5+
clock; it cannot register, submit, restore or extend a trial.
6+
"""
7+
from __future__ import annotations
8+
9+
import argparse
10+
import hashlib
11+
import json
12+
import os
13+
import stat
14+
import time
15+
import uuid
16+
from dataclasses import replace
17+
from pathlib import Path
18+
from benchmark.runtime.sforge_checkpoint import ARCHIVE_ROUTE, ROUTE, SCHEMA
19+
20+
21+
def _read_archive(root, run, task, round_id):
22+
"""Read an opaque native archive, never extract or inspect its members."""
23+
root = root.resolve(strict=True)
24+
path = root / "runs" / run / task / "submissions" / round_id / "submission.tar.gz"
25+
if not path.is_relative_to(root) or path.resolve(strict=True) != path:
26+
raise ValueError("Native incumbent archive must be a physical file below the log owner")
27+
with os.fdopen(os.open(path, os.O_RDONLY | os.O_NOFOLLOW), "rb") as source:
28+
before = os.fstat(source.fileno())
29+
if not stat.S_ISREG(before.st_mode):
30+
raise ValueError("Native incumbent archive must be a regular file")
31+
raw = source.read()
32+
after = os.fstat(source.fileno())
33+
observed = path.stat(follow_symlinks=False)
34+
def signature(value):
35+
return value.st_dev, value.st_ino, value.st_size, value.st_mtime_ns, value.st_ctime_ns
36+
if (signature(before) != signature(after) or signature(after) != signature(observed) or
37+
path.resolve(strict=True) != path or len(raw) != before.st_size):
38+
raise ValueError("Native incumbent archive moved during capture")
39+
return raw
40+
41+
42+
def create_app(config, *, checkpoint_capture=False):
43+
if type(checkpoint_capture) is not bool:
44+
raise ValueError("checkpoint_capture must be an explicit boolean")
45+
from sforge.harness.judge_server import SubmissionStatus, create_app as native_app
46+
app = native_app(config)
47+
if not checkpoint_capture:
48+
return app # Preserve the native route/admission surface when disabled.
49+
from fastapi import HTTPException
50+
from fastapi.responses import Response
51+
from pydantic import BaseModel, ConfigDict
52+
import sforge
53+
54+
state = getattr(app.state, "judge", None)
55+
if state is None or not all(hasattr(state, key) for key in (
56+
"_admission_lock", "_outstanding_by_run", "_tokens_lock", "_history_lock")):
57+
raise ValueError("Checkpoint capture requires a pinned SForge with its native admission owner exposed")
58+
epoch = uuid.uuid4().hex
59+
source = hashlib.sha256()
60+
source_root = Path(sforge.__file__).parent
61+
for path in sorted(source_root.rglob("*.py")):
62+
source.update(path.relative_to(source_root).as_posix().encode() + b"\0")
63+
source.update(hashlib.sha256(path.read_bytes()).digest())
64+
tasks = {key: {
65+
"native_source_sha256": source.hexdigest(),
66+
"task_spec_sha256": hashlib.sha256((config.tasks_dir / f"{key}.json").read_bytes()).hexdigest(),
67+
"judge_image_key": task.judge_image_key,
68+
"selection": task.judge.selection, "score_direction": task.judge.score_direction,
69+
} for key, task in state.tasks.items()}
70+
71+
class ReadRequest(BaseModel):
72+
model_config = ConfigDict(extra="forbid")
73+
token: str
74+
75+
class ArchiveRequest(ReadRequest):
76+
capture_binding_sha256: str
77+
78+
# FastAPI resolves annotations in module globals; keep this factory's
79+
# private request type concrete rather than a forward reference.
80+
def snapshot_locked(token):
81+
from sforge.harness.selection import select_best
82+
83+
# Reuse the native lock order and outstanding admission counter. A
84+
# pending grade or unexplained consumed round is never a complete point.
85+
# Called only under the native admission -> token -> history locks.
86+
info = state.tokens.get(token)
87+
if info is None:
88+
raise HTTPException(403, "Unknown registration")
89+
run, task = info["run_id"], info["task_id"]
90+
if state._closed or state.tasks[task].game_mode:
91+
raise HTTPException(409, "Checkpoint requires an open non-game registration")
92+
if state._outstanding_by_run.get((run, task), 0):
93+
raise HTTPException(409, "Accepted evaluation is still outstanding")
94+
if info["next_auto"] != 1:
95+
raise HTTPException(409, "This checkpoint profile requires no automatic evaluations")
96+
history = state.run_history.get(state._run_key(run, task), [])
97+
rounds = [entry.get("round") for entry in history]
98+
expected = {f"agent-{index}" for index in range(1, info["next_agent"])}
99+
if (len(rounds) != len(expected) or set(rounds) != expected or
100+
any(entry.get("status") not in {SubmissionStatus.COMPLETED, SubmissionStatus.ERROR} for entry in history)):
101+
raise HTTPException(409, "Consumed rounds and released history are not reconciled")
102+
results = {}
103+
for entry in history:
104+
identifier = entry.get("submission_id")
105+
native = state.submissions.get(identifier)
106+
if (native is None or native.get("run_id") != run or native.get("task_id") != task or
107+
native.get("round") != entry["round"] or native.get("status") != entry["status"]):
108+
raise HTTPException(409, "Released result details are unavailable or differ")
109+
results[identifier] = {key: native.get(key) for key in ("status", "report", "error")}
110+
# Reuse native ranking, including validity, direction and tie rules.
111+
# The working candidate can differ from this older selected archive.
112+
best = select_best(history, state.tasks[task].judge.score_direction,
113+
state.tasks[task].judge.selection)
114+
incumbent = None
115+
raw = None
116+
if best["best_round"]:
117+
selected = next(entry for entry in history if entry["round"] == best["best_round"])
118+
try:
119+
raw = _read_archive(config.log_dir, run, task, selected["round"])
120+
except (OSError, ValueError) as error:
121+
raise HTTPException(409, "Native selected incumbent archive is unavailable or moved") from error
122+
incumbent = {"round": selected["round"], "submission_id": selected["submission_id"],
123+
"archive_sha256": hashlib.sha256(raw).hexdigest(), "archive_bytes": len(raw)}
124+
observed = time.time()
125+
last = info["last_agent_submit_at"]
126+
cooldown = info["submission_cooldown"]
127+
remaining = max(0, last + (cooldown or 0) - observed) if last else 0
128+
packet = {
129+
"schema_version": SCHEMA, "judge_epoch": epoch,
130+
"run_id": run, "task_id": task, "evaluator": tasks[task],
131+
"observed_at_epoch": observed, "last_agent_submit_at_epoch": last,
132+
"submission_cooldown_seconds": cooldown,
133+
"remaining_cooldown_seconds": remaining,
134+
"max_agent_submissions": info["max_agent_submissions"],
135+
"next_agent_round": info["next_agent"], "next_auto_round": info["next_auto"],
136+
"released_history": history, "released_results": results, "outstanding_evaluations": 0,
137+
"native_incumbent": incumbent,
138+
"execution_authority_granted": False,
139+
}
140+
# Bind both reads to the same released state, not a wall clock. A new
141+
# accepted round, source epoch or changed archive invalidates download.
142+
binding = {key: value for key, value in packet.items()
143+
if key not in {"observed_at_epoch", "remaining_cooldown_seconds"}}
144+
packet["capture_binding_sha256"] = hashlib.sha256(
145+
json.dumps(binding, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
146+
return json.loads(json.dumps(packet)), raw # No live references or tokens leave the lock.
147+
148+
def capture(request):
149+
with state._admission_lock, state._tokens_lock, state._history_lock:
150+
return snapshot_locked(request.token)[0]
151+
152+
def archive(request):
153+
with state._admission_lock, state._tokens_lock, state._history_lock:
154+
packet, raw = snapshot_locked(request.token)
155+
incumbent = packet["native_incumbent"]
156+
if packet["capture_binding_sha256"] != request.capture_binding_sha256 or incumbent is None:
157+
raise HTTPException(409, "Native checkpoint basis moved or has no selected incumbent")
158+
return Response(raw, media_type="application/octet-stream")
159+
capture.__annotations__["request"] = ReadRequest
160+
archive.__annotations__["request"] = ArchiveRequest
161+
app.post(ROUTE)(capture)
162+
app.post(ARCHIVE_ROUTE)(archive)
163+
return app
164+
165+
166+
def main(argv=None):
167+
import uvicorn
168+
from sforge.harness.config import load_config
169+
parser = argparse.ArgumentParser(description=__doc__)
170+
parser.add_argument("--checkpoint-capture", action="store_true")
171+
parser.add_argument("--host", default="127.0.0.1")
172+
parser.add_argument("--port", type=int, default=8080)
173+
parser.add_argument("--tasks-dir", type=Path)
174+
parser.add_argument("--log-dir", type=Path)
175+
args = parser.parse_args(argv)
176+
config = load_config()
177+
overrides = {key: getattr(args, key) for key in ("tasks_dir", "log_dir") if getattr(args, key) is not None}
178+
uvicorn.run(create_app(replace(config, **overrides), checkpoint_capture=args.checkpoint_capture),
179+
host=args.host, port=args.port, access_log=False)
180+
181+
182+
if __name__ == "__main__":
183+
main()
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
"""Host-only adapter for the worker's explicitly enabled checkpoint mailbox."""
2+
from __future__ import annotations
3+
4+
import hashlib
5+
import io
6+
import json
7+
import re
8+
import tarfile
9+
import tempfile
10+
import threading
11+
import time
12+
from pathlib import Path
13+
14+
from .checkpoint_controller import _controller
15+
from .checkpoint_worker import _archive, capture_worker
16+
17+
18+
class CheckpointSupervisor:
19+
def __init__(self, controller_file: Path, destination: Path):
20+
_controller(controller_file) # Fail before native registration.
21+
self.controller_file, self.destination = controller_file, destination
22+
self.token = None
23+
self.stop = threading.Event()
24+
self.thread = None
25+
self.failure = None
26+
27+
def bind(self, environment):
28+
if self.token is not None or not (environment or {}).get("SFORGE_TOKEN"):
29+
raise ValueError("Checkpoint controller requires one native worker registration")
30+
self.token = environment["SFORGE_TOKEN"]
31+
32+
def start(self, container):
33+
if self.thread is not None or self.token is None:
34+
raise ValueError("Checkpoint supervisor requires one bound worker execution")
35+
self.destination.mkdir(mode=0o700, parents=True, exist_ok=False)
36+
self.container = container
37+
self.thread = threading.Thread(target=self._run, args=(container,), daemon=True)
38+
self.thread.start()
39+
40+
def close(self):
41+
self.stop.set()
42+
if self.thread is not None:
43+
self.thread.join(60)
44+
if self.thread.is_alive():
45+
raise RuntimeError("Checkpoint writer barrier is still active; preserve the worker for recovery")
46+
if self.failure is not None:
47+
# A missed optional point does not cancel otherwise useful source
48+
# work. An uncertain/unreleased writer barrier still needs recovery.
49+
self.container.reload()
50+
if self.container.attrs["State"]["Paused"]:
51+
raise RuntimeError("Checkpoint writer barrier requires controller recovery") from self.failure
52+
53+
def _reply(self, container, directory, nonce, response):
54+
container.reload()
55+
if container.attrs["State"]["Paused"] or not container.attrs["State"]["Running"]:
56+
raise RuntimeError("Checkpoint reply requires the released, running source worker")
57+
raw = json.dumps(response).encode()
58+
pending, final = nonce + ".pending", nonce + ".json"
59+
stream = io.BytesIO()
60+
with tarfile.open(fileobj=stream, mode="w") as archive:
61+
entry = tarfile.TarInfo(pending)
62+
entry.size, entry.mode = len(raw), 0o444
63+
archive.addfile(entry, io.BytesIO(raw))
64+
if not container.put_archive(directory, stream.getvalue()):
65+
raise RuntimeError("Checkpoint reply transport was not accepted")
66+
result = container.exec_run(["/bin/mv", directory + "/" + pending, directory + "/" + final])
67+
if result.exit_code:
68+
raise RuntimeError("Checkpoint reply could not be published atomically")
69+
70+
def _run(self, container):
71+
from docker.errors import NotFound
72+
73+
mailbox = "/logs/agent/checkpoint-controller"
74+
remote = mailbox + "/ready.json"
75+
seen = set()
76+
try:
77+
while not self.stop.is_set():
78+
with tempfile.TemporaryDirectory(prefix=".controller-poll-", dir=self.destination) as temp:
79+
stage = Path(temp)
80+
try:
81+
raw = _archive(container, remote, stage / "ready").read_bytes()
82+
except NotFound:
83+
self.stop.wait(5)
84+
continue
85+
digest = hashlib.sha256(raw).hexdigest()
86+
if digest in seen:
87+
self.stop.wait(5)
88+
continue
89+
seen.add(digest) # Never retry a later state under an earlier point.
90+
value = json.loads(raw)
91+
nonce = value.get("capture_id")
92+
if not isinstance(nonce, str) or not re.fullmatch(r"[0-9a-f]{32}", nonce):
93+
raise ValueError("Malformed checkpoint request identity")
94+
response = {"request_sha256": digest, "ok": False}
95+
try:
96+
request = stage / "request.json"
97+
request.write_text(json.dumps({"container_id": container.id,
98+
"components": value["components"],
99+
"phase_deadline_path": "/opt/loopx-benchmark/control/phase-deadline",
100+
"handoff": {"source_path": remote, "sha256": digest}}))
101+
remaining = value["deadline_epoch"] - time.time()
102+
if remaining <= 0:
103+
raise TimeoutError("Checkpoint handoff deadline expired")
104+
captured = capture_worker(self.controller_file, token=self.token, capture_id=nonce,
105+
destination=self.destination / nonce, request_file=request, timeout=min(30, remaining))
106+
response.update(ok=True, capture=captured)
107+
except Exception as error:
108+
# Optional capture failure resumes the original source;
109+
# credentials and raw provider errors never enter it.
110+
response["error_kind"] = type(error).__name__
111+
diagnostic = self.destination / (nonce + ".error.txt")
112+
diagnostic.write_text(str(error).replace(self.token, "[redacted]")[:2000])
113+
diagnostic.chmod(0o600)
114+
(self.destination / (nonce + ".json")).write_text(json.dumps(response))
115+
self._reply(container, mailbox, nonce, response)
116+
except Exception as error:
117+
self.failure = error
118+
(self.destination / "supervisor-failure.json").write_text(json.dumps({"error_kind": type(error).__name__}))

0 commit comments

Comments
 (0)