Skip to content

feat(edgebench): package ClawBench V2 as an EdgeBench/SForge benchmark#253

Open
reacher-z wants to merge 7 commits into
mainfrom
feat/edgebench-adapter
Open

feat(edgebench): package ClawBench V2 as an EdgeBench/SForge benchmark#253
reacher-z wants to merge 7 commits into
mainfrom
feat/edgebench-adapter

Conversation

@reacher-z

Copy link
Copy Markdown
Collaborator

Package ClawBench V2 as an EdgeBench/SForge benchmark

Adds clawbench.eval.edgebench_{adapter,judge} so ClawBench tasks run through ByteDance-Seed/EdgeBench (harness: SForge), the two-container code-agent harness.

Design — Mapping A ("structured_json score task"), zero SForge source edits

EdgeBench's judge is offline/archive-only; ClawBench's verify.py already scores a captured interception.json — so it fits cleanly:

  • Work container: ClawBench browser runtime + agent + interceptor → writes evidence/interception.json; agent runs sforge-submit once.
  • Judge eval_cmd = clawbench-edgebench-judge → re-scores the evidence two-stage (Stage-1 interception ∧ Stage-2 LLM judge) → prints the SForge structured_json block. parser: structured_json is built-in (no harness edit). Judge key via SFORGE_JUDGE_EXTRA_ENVCLAWBENCH_JUDGE_*.
  • One-shot: --max-submissions 1 --disable-auto-eval --disable-stop-hook (SForge's long-horizon best-of-N machinery is off for a short browser task).

What's here

  • edgebench_judge.py (clawbench-edgebench-judge) — host-importable, unit-tested; reuses runner.judge.judge_request; fail-closed when judge unconfigured; --no-judge = Stage-1 only.
  • edgebench_adapter.py (clawbench-edgebench-adapt) — emits tasks/BENCHMARK.yaml + tasks/<id>.json + per-task specs/, reusing harbor_adapter discovery.
  • docs/edgebench.md.

Verified

  • 11 tests (judge Stage-1/2, fail-closed, structured_json markers; adapter schema + layout); full suite green; ruff + pyright clean.
  • Live: adapter emits schema-correct SForge task JSON for a real v2 task; judge emits a valid >>>>> Start Structured Result {…score…} block on a real evidence fixture.

Two open ends (documented, not blockers to the ClawBench-side code)

  1. A browser Agent for SForge (its built-ins are code CLIs) — one Agent subclass + factory line in the EdgeBench repo, or a Work-image entrypoint that drives the harness then submits.
  2. Container build + a live sforge run — needs Docker + the EdgeBench harness on a build box (as with the ProRL image, validated separately from the offline contract tests).

Adds clawbench.eval.edgebench_{adapter,judge}: run ClawBench tasks through
ByteDance-Seed/EdgeBench (SForge), the two-container code-agent harness.

Mapping A ('structured_json score task', zero SForge source edits): the Work
container runs the browser agent + interceptor writing evidence/interception.json;
the agent submits evidence/ once; the Judge eval_cmd (clawbench-edgebench-judge)
re-scores it two-stage (Stage-1 interception + Stage-2 LLM judge) and prints the
SForge structured_json block. Fits SForge's offline/archive-only judge because
ClawBench's verify already scores captured interception.

- edgebench_judge.py (clawbench-edgebench-judge): host-importable, unit-tested;
  reuses runner.judge.judge_request; fail-closed when judge unconfigured;
  --no-judge for Stage-1 only; judge key via CLAWBENCH_JUDGE_* (SForge injects
  through SFORGE_JUDGE_EXTRA_ENV).
- edgebench_adapter.py (clawbench-edgebench-adapt): emits tasks/BENCHMARK.yaml +
  tasks/<id>.json (base_image/submit_paths/parser=structured_json/selection) +
  per-task specs, reusing harbor_adapter discovery + build_instruction.
- docs/edgebench.md: mapping, run command, and the two open ends (a browser
  Agent subclass for SForge; container build + live sforge run on a build box).

Tests: 11 (judge Stage-1/2 + fail-closed + structured_json markers; adapter
schema + layout). Full suite green; ruff + pyright clean. Verified: live
adapter emits schema-correct task JSON; judge emits a valid structured_json
block on a real evidence fixture.
reacher-z added 6 commits July 9, 2026 13:26
…dge, my-info staging)

- edgebench_judge: validate interception evidence — malformed/non-object JSON is
  valid:false score 0; require intercepted IS True (a truthy string 'false' no
  longer passes Stage-1); require a request object. Wrap judge_request in
  try/except so a transport exception can't suppress the structured_json block;
  emit sanitized error categories (never raw provider/exception text).
- edgebench_adapter: sforge_task_id() maps every non-[a-z0-9_] char to '_'
  (dots/mixed punctuation) + collapses; raise on task_id collisions instead of
  silently dropping; catch OSError/ValueError in main. Stage the my-info bundle
  (persona + creds + resume) + extra_info into specs so the reused instruction's
  ./my-info promise holds; work.setup_cmds copy it in.
- docs: structured_json example includes pass_rate.
- tests: +10 (malformed/non-object/string-false/no-request evidence, judge
  exception + error sanitization, id sanitization params, dup collision, my-info
  staged, no-secret-in-task-json). ruff + pyright clean.
…reason leak

Applies Codex round-2 findings on feat/edgebench-adapter:
- HIGH runtime arming: Work setup now also copies eval-schema to /eval-schema.json
  (the runtime-server reads it there to arm the interceptor); docs make explicit
  that the browser Agent/entrypoint must start start-runtime.sh + wait for
  eval_interceptor_ready at container-run time (setup_cmds are build-time), with
  the exact bootstrap commands.
- MEDIUM extra_info path-traversal: _guard_extra_info rejects absolute/'..'-escaping
  paths before the (unguarded) copy_extra_info stages them into the shareable
  benchmark.
- MEDIUM reason leak: the judge's free-text reason quotes the intercepted request
  body (may hold creds/PII); emit generic match/mismatch summaries instead of
  echoing it to SForge output.
- tests: +3 (reason not echoed, extra_info traversal rejected, eval-schema staged).
Applies Codex round-3 HIGH findings on feat/edgebench-adapter:
- evidence path mismatch: the runtime writes interceptions to $CLAWBENCH_DATA_DIR
  (default /data), but SForge submits + the judge reads /app/evidence. Docs
  bootstrap now exports CLAWBENCH_DATA_DIR=/app/evidence before start-runtime.sh
  so interception.json lands where submit_paths/--evidence-dir point.
- judge executable missing: the default image lacks clawbench-edgebench-judge.
  judge.setup_cmds now 'pip install clawbench-eval' so the console script exists
  in the Judge container (a base image bundling it can drop the line).
- tests: +3 (judge installs clawbench, submit_paths<->eval_cmd evidence-dir
  consistency, eval-schema staged).
…usted evidence)

Codex round-4 HIGH: the agent controls the submitted evidence archive, so the
judge must not trust interception.json's 'intercepted' flag. score_evidence now
RE-VERIFIES Stage-1 by matching the submitted request against task['eval_schema']
(url_pattern regex + method + const body/params), mirroring the runtime-server
interceptor (_stage1_match/_const_fields_match). Forged evidence (intercepted:true
with a non-matching request) now FAILS; a fabricated flag over a matching request
still requires the request to actually hit the target. Fail-closed when the task
has no eval_schema url_pattern.

Verified: forged wrong-URL -> 0.0/FAILED; real matching request -> 1.0/PASSED.
tests rewritten around a real eval_schema (+ forged/wrong-method/body-match/
missing-schema cases). 29 edgebench tests; ruff + pyright clean.
Codex round-5:
- params must be derived from the request URL (like the runtime), not a
  forgeable submitted request['params'] field. _stage1_match now always parses
  query params from the URL.
- tamper-resistance for agent-controlled evidence: opt-in HMAC. When
  CLAWBENCH_EVIDENCE_SECRET is set (shared runtime+judge, not the agent), the
  judge requires signature = HMAC-SHA256(secret, canonical-json(request)) and
  rejects unsigned/forged evidence; without the secret it runs in attestable
  mode (recomputed Stage-1 only). Documented the trust model.
- tests: +4 (signature enforced/optional, params-from-url-not-field).
'/etc/passwd' is absolute on POSIX (rejected via the is_absolute 'relative'
branch) but not on Windows (no drive letter), where _guard_extra_info rejects it
as an 'escapes' containment failure instead. Relax the match to
'relative|escapes' — rejected either way — so pytest (windows-latest) passes.

@Perry2004 Perry2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't see the point of doing this integration, and I'm not familiar with this framework.

@reacher-z, if you think this is reasonable, feel free to approve & merge it.

@reacher-z

Copy link
Copy Markdown
Collaborator Author

Validated against the real SForge harness code (not just the documented contract). Imported SForge's actual sforge.harness.task_spec + sforge.harness.grading from [ByteDance-Seed/EdgeBench] and ran this adapter's output through them:

  • make_task_spec() accepts the generated task.json → loads a valid TaskSpec with base_image=browser, parser=structured_json, selection=score_first, submit_paths=['evidence/'].
  • grade_output() parses clawbench-edgebench-judge's structured_json block (wrapped in the >>>>> Start/End Test Output markers SForge extracts) → EvalReport(score=1.0, valid=True, pass_rate=1.0).

So the ClawBench-side contract is confirmed correct against SForge's own loader + grader — the remaining open ends (a browser Agent for SForge, a live container sforge run) are unchanged and documented.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants