GitNexus/eval/workflow_bench/runner.py

1920 lines
83 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Benchmark the gitnexus-plan/work workflow against a baseline agent.
Usage:
uv run --locked --extra dev python -m workflow_bench.runner \
--tasks workflow_bench/tasks.scenarios.yaml --runs 3 \
--model claude-sonnet-4-20250514
Each task runs in a fresh self-contained clone of the target repo, once per
arm per run:
* ``workflow`` — two headless Claude Code sessions: gitnexus-plan, then
gitnexus-work on the produced plan.
* ``candidate_workflow`` / ``candidate_workflow_direct`` — the matching
workflow arm with a prompt-only candidate overlay committed in its clone.
* ``baseline`` — one headless session with the same task text and the Skill
tool disallowed (so it cannot borrow the workflow), everything else equal.
Token usage, cost, duration, and turn counts come from the CLI's own
``--output-format json`` report — nothing is estimated. Caveat: the report's
top-level ``usage`` counts ONLY the main-loop session; ``total_cost_usd`` is
the only reported number that includes subagent spend. A task's model-visible
``verify`` command is retained as an authored-test quality signal; ``resolved``
also requires its harness-owned hidden behavioral oracle. Token savings on
unresolved runs are reported but flagged, because saving tokens by failing is
not a saving.
Trust model: task files and candidate prompts are executable input. Every
setup, verifier, and model session runs inside a preflighted Linux Bubblewrap
boundary with an allowlisted environment, isolated home, PID namespace,
self-contained clone, and task-declared read-only dependencies. Unsupported
or unavailable containment fails before model invocation (README § Trust
model).
"""
from __future__ import annotations
import argparse
from concurrent.futures import ThreadPoolExecutor, wait
import hashlib
import json
import os
import re
import secrets
import stat
import statistics
import sys
import tempfile
import threading
import time
from collections.abc import Callable, Mapping, Sequence
from functools import partial
from contextvars import copy_context
from dataclasses import dataclass, replace
from datetime import UTC, datetime, timedelta
from pathlib import Path
from typing import Any
import yaml
from .evolution import (
CANDIDATE_ARMS,
EVIDENCE_MAX_AGE_DAYS,
EVALUATED_ARM_SKILLS,
PROMOTION_METRICS,
apply_candidate_overlay,
candidate_overlay_digest,
promotion_policy,
promotion_evidence,
required_candidate_arms,
seed_evaluated_skills,
skill_fingerprint,
)
from .model_gateway import (
attach_openai_gateway,
anthropic_api_key_from_environ,
credential_secrets,
model_session_environment,
openai_api_key_from_environ,
)
from .oracle_assets import (
ORACLE_ENV_VAR,
TaskOracleSnapshot,
capture_task_oracles,
require_hidden_harness_absent,
sanitize_clone_for_hidden_oracles,
staged_task_oracle,
with_hidden_harness_apply_exclude,
)
from .process_control import cancellation_scope, ManagedProcessError
from .promotion_apply import committed_destination_base_digests
from .review_scoring import REVIEW_OUTPUT, expected_findings, parse_review_output, score_review
from .proposer_sandbox import (
SANDBOX_GITNEXUS as SANDBOX_GITNEXUS,
SANDBOX_GITNEXUS_REGISTRY,
SANDBOX_GITNEXUS_SHARED as SANDBOX_GITNEXUS_SHARED,
SANDBOX_NODE as SANDBOX_NODE,
SANDBOX_WORKSPACE,
ReadOnlyMount,
SandboxError,
SandboxSession,
build_sandbox_environment,
preflight_bubblewrap,
preflight_unsafe_host,
prepare_sandbox,
prepare_review_workspace,
redact_text,
require_claude_sandbox_helpers,
sandbox_workspace_write_boundary,
)
from .runner_artifacts import (
IMPLEMENTATION_ARMS,
MAX_PATCH_BYTES as MAX_PATCH_BYTES,
MAX_WORKSPACE_SNAPSHOT_ENTRIES as MAX_WORKSPACE_SNAPSHOT_ENTRIES,
MAX_WORKSPACE_SNAPSHOT_FILE_BYTES as MAX_WORKSPACE_SNAPSHOT_FILE_BYTES,
MAX_WORKSPACE_SNAPSHOT_PATH_BYTES as MAX_WORKSPACE_SNAPSHOT_PATH_BYTES,
_bounded_regular_bytes,
_prepare_untracked_for_diff,
_sandbox_git,
capture_patch,
diff_churn,
enforce_phase_workspace,
enforce_work_evidence,
implementation_diff_digest,
make_worktree,
new_plan_doc,
parse_shortstat as parse_shortstat,
remove_clone,
require_skill_fingerprint,
run_verify,
snapshot_plan_docs,
VerificationResult,
workspace_snapshot,
)
from .runner_sessions import (
BUILTIN_AGENT_TOOLS as BUILTIN_AGENT_TOOLS,
GITNEXUS_MUTATING_TOOLS as GITNEXUS_MUTATING_TOOLS,
GITNEXUS_READ_ONLY_TOOLS as GITNEXUS_READ_ONLY_TOOLS,
MAX_TRANSCRIPT_BYTES as MAX_TRANSCRIPT_BYTES,
SANDBOX_GITNEXUS_ENTRYPOINT as SANDBOX_GITNEXUS_ENTRYPOINT,
SESSION_TIMEOUT_SECONDS,
USAGE_FIELDS,
_na,
allowed_agent_tools,
run_claude,
sandbox_mcp_config,
sum_sessions,
)
from .runner_tasks import (
normalized_model_identifier,
resolve_task_bindings,
select_tasks,
selected_task_bindings as selected_task_bindings,
)
from .sanitized_graph import (
SanitizedGraphSnapshot,
prepare_sanitized_graph,
validate_no_prebuilt_graph_assets,
)
from .runtime_mounts import (
CE_ARMS,
CePluginSnapshot,
HARNESS_ROOT as HARNESS_ROOT,
ce_plugin_dir_for_arm,
ce_plugin_mounts_for_arm,
staged_ce_plugin_snapshot,
trusted_gitnexus_runtime_mounts,
validate_ce_plugin_inputs,
)
from .task_assets import TaskAssetCache, TaskAssetSnapshot, stage_task_assets
PLAN_PROMPT = (
"Use the gitnexus-plan skill for: {task}\n"
"Headless run: make reasonable choices without asking; the plan document "
"is the deliverable."
)
# Appended to every work-arm prompt. In a headless `claude -p` session there
# is no later turn: backgrounded test runs and scheduled wakeups never come
# back, so a session that "waits" for verification ends unverified (observed:
# a work arm backgrounded its slow tests, scheduled three wakeups that never
# fired, and reported done while two tests failed).
HEADLESS_VERIFY = (
" Verification must be observed inside this session: run the typecheck "
"and test commands in the foreground to completion and report their "
"actual output — never background them or wait on scheduled wakeups."
)
WORK_PROMPT = (
"Use the gitnexus-work skill to execute the plan at {plan}.\n"
"Headless run: proceed without asking; report Definition of Done status "
"at the end." + HEADLESS_VERIFY
)
WORK_DIRECT_PROMPT = (
"Use the gitnexus-work skill for: {task}\n"
"Headless run: proceed without asking. The user explicitly declines a "
"separate planning pass — execute in direct mode with the skill's "
"execution discipline." + HEADLESS_VERIFY
)
BASELINE_PROMPT = (
"{task}\n\n"
"Implement the change in this repository and verify it by running the "
"relevant tests. Work autonomously without asking questions."
)
# External-comparator arms: the compound-engineering plugin's plan/work family,
# prompted with the same structure as the gitnexus arms so only the skill
# family differs. The plugin ships user-level, so clones need no repo files.
CE_PLAN_PROMPT = (
"Use the ce-plan skill (compound-engineering plugin) for: {task}\n"
"Headless run: make reasonable choices without asking; the plan document "
"is the deliverable."
)
CE_WORK_PROMPT = (
"Use the ce-work skill (compound-engineering plugin) to execute the plan "
"at {plan}.\n"
"Headless run: proceed without asking; report completion status at the "
"end." + HEADLESS_VERIFY
)
CE_WORK_DIRECT_PROMPT = (
"Use the ce-work skill (compound-engineering plugin) for: {task}\n"
"Headless run: proceed without asking. The user explicitly declines a "
"separate planning pass — execute directly with the skill's execution "
"discipline." + HEADLESS_VERIFY
)
# Review cell: setup applies a historical PR diff, then the model sees a
# read-only checkout. Both arms emit the same strict artifact so quality can be
# scored deterministically against labels that remain hidden until it exits.
REVIEW_OUTPUT_CONTRACT = """
Write /workspace/review-output.json as UTF-8 JSON with exactly this shape:
{{"schema_version":1,"verdict":"approve|comment|request_changes","findings":[{{
"id":"unique stable id","severity":"critical|high|medium|low",
"path":"repository-relative changed file","line":1,"end_line":1,
"category":"correctness|security|compatibility|performance|tests|other",
"scenario":"specific failure scenario","evidence":"concrete code/graph evidence",
"recommendation":"bounded fix","blocking":true}}]}}
Use an empty findings list with verdict approve when there are no actionable
defects. Do not emit Markdown and do not edit any other file.
"""
REVIEW_PROMPT = (
"Use the gitnexus-review skill to review the local uncommitted changes "
"in this repository. {task}\n"
"Headless run: proceed without asking; do not post to GitHub or anywhere "
"external." + REVIEW_OUTPUT_CONTRACT
)
CE_REVIEW_PROMPT = (
"Use the ce-code-review skill (compound-engineering plugin) to review "
"the local uncommitted changes in this repository. {task}\n"
"Headless run: proceed without asking; do not post to GitHub or anywhere "
"external." + REVIEW_OUTPUT_CONTRACT
)
# Skill each arm's session(s) must actually invoke; a session that never ran
# its skill is a silent no-op arm, not a data point (checked via transcript).
ARM_EXPECTED_SKILLS: dict[str, tuple[str, ...]] = {
"workflow": ("gitnexus-plan", "gitnexus-work"),
"ce_workflow": ("ce-plan", "ce-work"),
"workflow_direct": ("gitnexus-work",),
"ce_workflow_direct": ("ce-work",),
"review": ("gitnexus-review",),
"ce_review": ("ce-code-review",),
}
def _require_implementation_fingerprint(
session: dict[str, Any],
worktree: Path,
arm: str,
expected: str | None,
) -> None:
"""Bind a just-finished implementation session to its original skill bytes."""
try:
require_skill_fingerprint(
worktree,
arm,
expected,
phase="implementation",
)
except ValueError as exc:
if session.get("error_kind") is None:
session["ok"] = False
session["error_kind"] = "implementation-evidence-invalid"
session["error_detail"] = str(exc)
else:
session.setdefault("evidence_diagnostics", []).append(str(exc))
def _verification_outcome(result: VerificationResult | tuple[bool, str]) -> tuple[bool, str]:
if isinstance(result, VerificationResult):
if result.process.state != "exited":
# Hidden-oracle output can contain mounted test bytes. Preserve
# terminal-state evidence without letting candidate-controlled
# stdout/stderr enter results.jsonl through the exception string.
safe_process = replace(
result.process,
stdout_tail="",
stderr_tail="",
detail=result.process.detail or "verifier infrastructure failed",
)
raise ManagedProcessError(result.command, safe_process)
return result.passed, result.output
return result
def _run_hidden_oracle(
snapshot: TaskOracleSnapshot,
worktree: Path,
args: argparse.Namespace,
sandbox: SandboxSession,
) -> tuple[bool, str]:
"""Stage a captured oracle after the model exits, execute it, then erase it."""
if worktree.expanduser().absolute() != sandbox.clone.expanduser().absolute():
raise SandboxError("hidden oracle sandbox does not bind the credited worktree")
mount_name = f".wfbench-oracle-{secrets.token_hex(16)}"
mount_point = worktree / mount_name
mount_point.mkdir(mode=0o700)
primary: BaseException | None = None
try:
host_unsafe = getattr(sandbox, "backend", "bwrap") == "host-unsafe"
# Host mode has no bind mounts. Keep relative candidate imports valid
# by staging beside the candidate, still only after the model exits.
stage_parent = worktree if host_unsafe else sandbox.private_root
with staged_task_oracle(stage_parent, snapshot) as stage_root:
oracle_env = build_sandbox_environment()
# A private RO bind at a random workspace sibling preserves each
# oracle's ../gitnexus import as the candidate implementation. The
# empty mountpoint exists only post-model and is removed before the
# credited patch is captured.
oracle_mount = f"{SANDBOX_WORKSPACE}/{mount_name}"
oracle_env[ORACLE_ENV_VAR] = str(stage_root) if host_unsafe else oracle_mount
passed, _output = _verification_outcome(
run_verify(
snapshot.command,
sandbox.clone,
args.timeout,
command_prefix=sandbox.command_prefix_for(
read_only_workspace=True,
unshare_network=True,
extra_read_only_mounts=()
if host_unsafe
else (ReadOnlyMount(source=stage_root, target=oracle_mount),),
),
env=oracle_env,
require_pid_namespace=getattr(sandbox, "require_pid_namespace", True),
)
)
# Candidate code executes in this process. Never persist its stdout
# or stderr: it can read the mounted hidden test bytes and print them.
return passed, "hidden oracle passed" if passed else "hidden oracle failed"
except BaseException as exc:
primary = exc
raise
finally:
try:
metadata = mount_point.lstat()
if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode):
raise SandboxError("hidden oracle mountpoint changed type during verification")
mount_point.rmdir()
except (OSError, SandboxError) as cleanup:
if primary is None:
raise
primary.add_note(f"hidden oracle mountpoint cleanup also failed: {cleanup}")
def _evaluated_skill_roots(worktree: Path, arm: str) -> tuple[Path, ...]:
"""Repo-local prompt roots that must remain immutable during a session."""
return tuple(worktree / ".claude" / "skills" / name for name in EVALUATED_ARM_SKILLS.get(arm, ()))
def isolated_gitnexus_registry_mount(worktree: Path, parent: Path) -> ReadOnlyMount:
"""Create a one-clone registry that cannot route MCP to any host repo."""
metadata_path = worktree / ".gitnexus" / "gitnexus.json"
if not metadata_path.exists():
metadata_path = worktree / ".gitnexus" / "meta.json"
mode = metadata_path.lstat().st_mode
if stat.S_ISLNK(mode) or not stat.S_ISREG(mode):
raise SandboxError(f"benchmark index metadata must be regular and non-symlink: {metadata_path}")
raw = _bounded_regular_bytes(metadata_path, limit=2 * 1024 * 1024)
try:
metadata = json.loads(raw)
except json.JSONDecodeError as exc:
raise SandboxError(f"benchmark index metadata is malformed: {metadata_path}") from exc
if not isinstance(metadata, dict):
raise SandboxError(f"benchmark index metadata must be an object: {metadata_path}")
indexed_at = metadata.get("indexedAt")
last_commit = metadata.get("lastCommit")
if not isinstance(indexed_at, str) or not indexed_at or not isinstance(last_commit, str) or not last_commit:
raise SandboxError("benchmark index metadata is missing indexedAt or lastCommit")
parent = parent.expanduser().absolute()
registry = Path(tempfile.mkdtemp(prefix="wfbench-registry-", dir=parent))
registry.chmod(0o700)
entry: dict[str, Any] = {
"name": "benchmark-target",
"path": SANDBOX_WORKSPACE,
"storagePath": f"{SANDBOX_WORKSPACE}/.gitnexus",
"indexedAt": indexed_at,
"lastCommit": last_commit,
}
for field in ("remoteUrl", "stats", "branch"):
if field in metadata:
entry[field] = metadata[field]
registry_file = registry / "registry.json"
descriptor = os.open(
registry_file,
os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0),
0o600,
)
try:
os.fchmod(descriptor, 0o600)
payload = (json.dumps([entry], sort_keys=True, separators=(",", ":")) + "\n").encode()
view = memoryview(payload)
while view:
written = os.write(descriptor, view)
view = view[written:]
finally:
os.close(descriptor)
return ReadOnlyMount(source=registry, target=SANDBOX_GITNEXUS_REGISTRY)
def _unchanged(value: Any) -> Any:
return value
def run_arm(
arm: str,
task: dict[str, Any],
worktree: Path,
args: argparse.Namespace,
*,
sandbox: SandboxSession,
transcript_output_dir: Path | None = None,
transcript_output_prefix: str | None = None,
expected_skill_digest: str | None = None,
enforce_phase_boundary: bool = False,
ce_plugin_dir: str | None = None,
oracle_snapshot: TaskOracleSnapshot | None = None,
) -> dict[str, Any]:
sessions: list[dict[str, Any]] = []
environment_builder = getattr(sandbox, "environment", build_sandbox_environment)
host_text = getattr(sandbox, "host_text", _unchanged)
host_path = getattr(sandbox, "host_path", lambda value: str(value))
backend = getattr(sandbox, "backend", "bwrap")
env = model_session_environment(
auth_token=args.auth_token,
base_url=args.base_url,
model=args.model,
build_sandbox_environment=environment_builder,
)
# --bare hard-disables the Skill tool and every mcp__* tool — by Claude
# Code design, not a bug (--allowedTools can't restore what --bare
# removes). Every arm except baseline_nomcp needs Skill and/or MCP tools,
# so only baseline_nomcp can keep --bare's tighter isolation; the rest
# rely on ANTHROPIC_API_KEY alone (the sandboxed HOME has no OAuth/
# keychain state to conflict with it).
bare = arm == "baseline_nomcp"
progress_label = transcript_output_prefix or f"{task.get('id', 'task')}-{arm}"
common = {
"progress_label": progress_label,
"claude_bin": sandbox.claude_bin,
"timeout": args.timeout,
"model": args.model,
"effort": args.effort,
"env": env,
"permission_mode": (
"bypassPermissions" if backend == "host-unsafe" else "dontAsk"
),
"command_prefix": sandbox.command_prefix_for(
read_only_paths=_evaluated_skill_roots(worktree, arm),
),
"require_pid_namespace": getattr(sandbox, "require_pid_namespace", True),
"bare": bare,
"settings_json": sandbox.settings_json,
"strict_mcp_config": True,
"mcp_config_json": host_text(sandbox_mcp_config()),
"transcript_projects": sandbox.transcript_projects,
"transcript_cwd": Path(SANDBOX_WORKSPACE),
"transcript_wait_seconds": 5,
"transcript_output_dir": transcript_output_dir,
"transcript_output_prefix": transcript_output_prefix,
"transcript_secrets": tuple(credential_secrets(args)),
}
if ce_plugin_dir is not None:
common["plugin_dirs"] = (host_path(ce_plugin_dir),)
expected_skills = ARM_EXPECTED_SKILLS.get(arm, ())
plan_doc: Path | None = None
if arm in ("workflow", "ce_workflow"):
plan_prompt = PLAN_PROMPT if arm == "workflow" else CE_PLAN_PROMPT
work_prompt = WORK_PROMPT if arm == "workflow" else CE_WORK_PROMPT
pre = snapshot_plan_docs(worktree)
phase_before = workspace_snapshot(worktree) if enforce_phase_boundary else None
plan_session = run_claude(
plan_prompt.format(task=task["prompt"]),
worktree,
expected_skill=expected_skills[0],
**{
**common,
"progress_label": f"{progress_label} plan",
"allowed_tools": allowed_agent_tools(implementation=False),
},
)
sessions.append(plan_session)
if plan_session["ok"]:
try:
plan_doc = new_plan_doc(worktree, pre)
if phase_before is not None:
enforce_phase_workspace(
worktree,
phase_before,
allowed_artifact=plan_doc,
)
require_skill_fingerprint(
worktree,
arm,
expected_skill_digest,
phase="planning",
)
except ValueError as exc:
plan_session["ok"] = False
plan_session["error_kind"] = "plan-evidence-invalid"
plan_session["error_detail"] = str(exc)
else:
work_session = run_claude(
work_prompt.format(plan=plan_doc.relative_to(worktree)),
worktree,
expected_skill=expected_skills[1],
**{
**common,
"progress_label": f"{progress_label} work",
"allowed_tools": allowed_agent_tools(implementation=True),
},
)
_require_implementation_fingerprint(
work_session,
worktree,
arm,
expected_skill_digest,
)
sessions.append(work_session)
elif arm == "ce_workflow_direct":
work_session = run_claude(
CE_WORK_DIRECT_PROMPT.format(task=task["prompt"]),
worktree,
expected_skill=expected_skills[0],
**{**common, "allowed_tools": allowed_agent_tools(implementation=True)},
)
_require_implementation_fingerprint(
work_session,
worktree,
arm,
expected_skill_digest,
)
sessions.append(work_session)
elif arm in ("review", "ce_review"):
review_prompt = REVIEW_PROMPT if arm == "review" else CE_REVIEW_PROMPT
review_output = prepare_review_workspace(sandbox, REVIEW_OUTPUT)
phase_before = workspace_snapshot(worktree) if enforce_phase_boundary else None
review_common = {
**common,
"allowed_tools": allowed_agent_tools(implementation=False, allow_edit=False),
"command_prefix": sandbox.command_prefix_for(
read_only_workspace=True,
read_only_paths=_evaluated_skill_roots(worktree, arm),
extra_writable_mounts=(
ReadOnlyMount(
source=review_output,
target=f"{SANDBOX_WORKSPACE}/{REVIEW_OUTPUT}",
),
),
),
}
with sandbox_workspace_write_boundary(
sandbox,
read_only_workspace=True,
writable=(review_output,),
):
review_session = run_claude(
host_text(review_prompt.format(task=task["prompt"])),
worktree,
expected_skill=expected_skills[0],
**review_common,
)
sessions.append(review_session)
if review_session["ok"] and phase_before is not None:
try:
enforce_phase_workspace(
worktree,
phase_before,
allowed_artifact=worktree / REVIEW_OUTPUT,
)
require_skill_fingerprint(
worktree,
arm,
expected_skill_digest,
phase="review",
)
except ValueError as exc:
review_session["ok"] = False
review_session["error_kind"] = "review-evidence-invalid"
review_session["error_detail"] = str(exc)
elif arm == "workflow_direct":
work_session = run_claude(
WORK_DIRECT_PROMPT.format(task=task["prompt"]),
worktree,
expected_skill=expected_skills[0],
**{**common, "allowed_tools": allowed_agent_tools(implementation=True)},
)
_require_implementation_fingerprint(
work_session,
worktree,
arm,
expected_skill_digest,
)
sessions.append(work_session)
elif arm == "baseline_nomcp":
# Isolates the workflow-discipline question from the GitNexus-tools
# question: no skills AND no graph tools.
sessions.append(
run_claude(
BASELINE_PROMPT.format(task=task["prompt"]),
worktree,
disallowed_tools=["Skill", "mcp__gitnexus"],
**{
**common,
"mcp_config_json": '{"mcpServers":{}}',
"allowed_tools": allowed_agent_tools(
implementation=True,
include_mcp=False,
),
},
)
)
else:
sessions.append(
run_claude(
BASELINE_PROMPT.format(task=task["prompt"]),
worktree,
disallowed_tools=["Skill"],
**{**common, "allowed_tools": allowed_agent_tools(implementation=True)},
)
)
record = sum_sessions(sessions)
record["arm"] = arm
record["plan_produced"] = arm not in ("workflow", "ce_workflow") or plan_doc is not None
authored_tests_passed, authored_test_output = _verification_outcome(
run_verify(
task["verify"],
worktree,
args.timeout,
command_prefix=sandbox.command_prefix_for(
read_only_workspace=True,
unshare_network=True,
),
env=environment_builder(),
require_pid_namespace=getattr(sandbox, "require_pid_namespace", True),
)
)
review_score: dict[str, Any] | None = None
if arm in ("review", "ce_review"):
try:
verdict, findings = parse_review_output(worktree / REVIEW_OUTPUT)
labels = expected_findings(oracle_snapshot) if oracle_snapshot is not None else ()
review_score = score_review(verdict, findings, labels)
except (OSError, ValueError) as exc:
record["ok"] = False
record["error_kind"] = record["error_kind"] or "review-evidence-invalid"
record["error_detail"] = str(exc)
record["review_score"] = review_score
record["review_evidence_valid"] = review_score is not None
if review_score is not None:
record.update({f"review_{key}": value for key, value in review_score.items()})
if oracle_snapshot is None:
oracle_passed, oracle_output = False, "hidden oracle snapshot unavailable"
elif arm in ("review", "ce_review"):
oracle_passed = bool(
review_score
and review_score["false_positives"] == 0
and review_score["false_negatives"] == 0
and review_score["verdict_correct"]
)
oracle_output = "hidden review labels matched" if oracle_passed else "hidden review labels not fully matched"
else:
oracle_passed, oracle_output = _run_hidden_oracle(
oracle_snapshot,
worktree,
args,
sandbox,
)
record["authored_tests_passed"] = authored_tests_passed
record["authored_test_output"] = authored_test_output
record["oracle_passed"] = oracle_passed
record["oracle_output"] = oracle_output
record["resolved"] = record["ok"] and authored_tests_passed and oracle_passed
# Compatibility alias for existing report consumers. The authored tests are
# now an explicit signal and can never self-certify resolution.
record["verify_output"] = authored_test_output
if oracle_snapshot is not None:
record.update(
{
"oracle_digest": oracle_snapshot.digest,
"oracle_command_digest": oracle_snapshot.command_digest,
"oracle_manifest_digest": oracle_snapshot.manifest_digest,
}
)
if record["error_kind"] is None and not authored_tests_passed:
# The sessions completed — the produced change just failed the task's
# verify command. Kept distinct from session-error so aggregates can
# exclude infrastructure deaths without hiding real failures.
record["error_kind"] = "verify-failed"
elif record["error_kind"] is None and not oracle_passed:
record["error_kind"] = "oracle-failed" if oracle_snapshot is not None else "oracle-unavailable"
return record
# ─── Pure aggregation/report helpers (unit-tested) ──────────────────────────
CHURN_FIELDS = ("diff_files", "diff_insertions", "diff_deletions")
# Rows where the session (or the harness) died carry no measured evidence and
# must not skew efficiency medians or resolve denominators. verify-failed and
# skill-not-invoked rows DO count: those sessions ran and spent real tokens.
EXCLUDED_ERROR_KINDS = frozenset(
{"session-error", "infra-error", "evidence-unverified", "cleanup-failure", "review-evidence-invalid", "cancelled"}
)
# A sustained upstream outage shows up as a run of session/infra/cleanup
# failures. (cleanup-failure overwrites the primary error_kind, so a
# session-error whose worktree cleanup also failed still counts.) A task's own
# resolved=False is real signal, not an outage, so it never trips the breaker.
SYSTEMIC_ERROR_KINDS = frozenset({"session-error", "infra-error", "cleanup-failure", "review-evidence-invalid"})
DEFAULT_OUTAGE_STREAK = 5
# A cell is a full clone plus a sandboxed agent session, so the ceiling is the
# machine, not the flag. Past a handful of siblings the cells lose CPU to each
# other, sessions reach their timeout, and a timed-out session is an excluded
# run the promotion gate refuses to work with — a mistyped --workers must fail
# at the command line rather than a quarter-day later as unusable evidence.
MAX_WORKERS = 8
def systemic_outage_streak(error_kind: str | None, prior_streak: int) -> int:
"""Consecutive systemic-failure count: +1 on a systemic kind, else reset to 0."""
return prior_streak + 1 if error_kind in SYSTEMIC_ERROR_KINDS else 0
def _run_wave(
wave: Sequence[tuple[int, str]],
*,
workers: int,
run: Callable[[int, str], dict[str, Any]],
cancel_event: threading.Event,
) -> tuple[list[dict[str, Any] | BaseException], BaseException | None]:
"""Cancel active subprocesses, join workers, and retain settled outcomes."""
pool = ThreadPoolExecutor(max_workers=workers)
futures = []
interruption = None
try:
for run_idx, arm in wave:
futures.append(pool.submit(copy_context().run, run, run_idx, arm))
wait(futures)
except (Exception, KeyboardInterrupt, SystemExit) as exc:
interruption = exc
cancel_event.set()
finally:
pool.shutdown(wait=True, cancel_futures=interruption is not None)
outcomes: list[dict[str, Any] | BaseException] = []
for future in futures:
if future.cancelled():
outcomes.append({"resolved": False, "error_kind": "cancelled"})
else:
error = future.exception()
outcomes.append(error if error is not None else future.result())
# Submission itself can be interrupted. Preserve positional evidence for
# cells that never started without masking the original interruption.
outcomes.extend({"resolved": False, "error_kind": "cancelled"} for _ in wave[len(futures) :])
return outcomes, interruption
def sweep_task_cells(
cells: Sequence[tuple[int, str]],
*,
workers: int,
run: Callable[[int, str], dict[str, Any]],
on_start: Callable[[int, str], None],
on_record: Callable[[int, str, dict[str, Any]], None],
outage_streak: int,
outage_limit: int,
cancel_event: threading.Event | None = None,
) -> tuple[int, bool]:
"""Run one task's cells in waves of ``workers``; return (streak, tripped).
Waves rather than one fan-out, because the outage breaker counts
CONSECUTIVE systemic failures and "consecutive" only means anything in a
fixed order — completion order under concurrency is not one. Each wave is
folded in submission order once it has fully completed, and the next wave
starts only if the breaker held, so the breaker overruns its limit by at
most ``workers - 1`` cells: the ones already in flight when it tripped.
The serial default executes directly; parallel workers copy the run
context so every owned subprocess observes the same cancellation signal.
"""
with cancellation_scope(cancel_event) as cancel_event:
if workers < 1:
raise ValueError("workers must be positive")
for wave_start in range(0, len(cells), workers):
if cancel_event.is_set():
return outage_streak, True
wave = list(cells[wave_start : wave_start + workers])
for run_idx, arm in wave:
on_start(run_idx, arm)
if workers == 1:
records = [run(run_idx, arm) for run_idx, arm in wave]
else:
outcomes, interruption = _run_wave(wave, workers=workers, run=run, cancel_event=cancel_event)
failure = interruption or next(
(outcome for outcome in outcomes if isinstance(outcome, BaseException)), None
)
if failure is not None:
# The siblings of the failing cell have already completed and
# spent their budget. Persist their rows, in submission order,
# before the harness bug takes the process down — otherwise a
# crash in one cell silently erases the evidence of the others.
for (run_idx, arm), outcome in zip(wave, outcomes, strict=True):
if not isinstance(outcome, BaseException):
on_record(run_idx, arm, outcome)
raise failure
records = [outcome for outcome in outcomes if not isinstance(outcome, BaseException)]
for (run_idx, arm), record in zip(wave, records, strict=True):
# Every future in this wave has already completed and incurred its
# cost. Persist all of them in canonical submission order even if
# an earlier row trips the breaker; only later waves are skipped.
on_record(run_idx, arm, record)
if cancel_event.is_set():
return outage_streak, True
for record in records:
kind = (
"review-evidence-invalid"
if record.get("review_evidence_valid") is False
else record.get("error_kind")
)
outage_streak = systemic_outage_streak(kind, outage_streak)
if outage_limit and outage_streak >= outage_limit:
print(
f"[systemic-outage] {outage_streak} consecutive unusable-evidence "
"failures — aborting the remaining sweep; report and promotion are written "
"from partial evidence and the run exits non-zero."
)
return outage_streak, True
return outage_streak, False
@dataclass(frozen=True)
class TaskCellContext:
"""Everything one benchmark cell needs from its task, prepared once.
A cell is one (run, arm) pair: a private clone, a sandboxed session set, and
the row it produces. Cells of the same task share this context read-only, so
it is what makes them independent of each other — every per-cell mutable is
local to ``run_cell``. Holding the fields explicitly, rather than closing
over ``main``'s scope, is what lets a cell run off the main thread without
dragging the whole sweep's state along with it.
``args`` is treated as immutable: ``main`` finishes mutating it during
setup, well before any cell starts. ``argparse.Namespace`` cannot enforce
that, so it is stated here.
"""
task: dict[str, Any]
oracle_snapshot: TaskOracleSnapshot
repo: Path
task_sha: str
graph_snapshot: SanitizedGraphSnapshot | None
graph_snapshot_error: BaseException | None
asset_snapshot: TaskAssetSnapshot | None
asset_snapshot_error: BaseException | None
args: argparse.Namespace
out_dir: Path
ce_plugin_snapshot: CePluginSnapshot | None
trees_dir: Path
bwrap_bin: Path
runtime_mounts: tuple[ReadOnlyMount, ...]
candidate_overlay: Path | None
overlay_digest: str | None
sandbox_backend: str = "bwrap"
def run_cell(ctx: TaskCellContext, run_idx: int, arm: str) -> dict[str, Any]:
"""Run one (run, arm) cell end to end and return its result row.
Owns its clone for the whole call, including teardown: the ``finally``
removes the worktree whatever happens, and an exception outside the five
expected kinds is deliberately left to propagate — a harness bug must not be
recorded as an ordinary infra-error and averaged into the evidence.
"""
args = ctx.args
task = ctx.task
worktree: Path | None = None
record: dict[str, Any] | None = None
cleanup_error: OSError | None = None
try:
if ctx.asset_snapshot_error is not None:
raise RuntimeError(f"task asset snapshot preparation failed: {ctx.asset_snapshot_error}")
if ctx.graph_snapshot_error is not None:
raise RuntimeError(f"sanitized graph snapshot preparation failed: {ctx.graph_snapshot_error}")
if ctx.graph_snapshot is None:
raise RuntimeError("sanitized graph snapshot is unavailable")
if ctx.asset_snapshot is None:
raise RuntimeError("task asset snapshot is unavailable")
worktree = make_worktree(ctx.repo, ctx.task_sha, ctx.trees_dir)
sanitized_head = sanitize_clone_for_hidden_oracles(worktree)
ctx.graph_snapshot.materialize(worktree, sanitized_head=sanitized_head)
dependency_mounts = stage_task_assets(
task,
repo=ctx.repo,
clone=worktree,
snapshot=ctx.asset_snapshot,
)
registry_mount = isolated_gitnexus_registry_mount(worktree, ctx.trees_dir)
execution_arm = CANDIDATE_ARMS.get(arm, arm)
ce_mounts = ce_plugin_mounts_for_arm(execution_arm, ctx.ce_plugin_snapshot)
with prepare_sandbox(
clone=worktree,
claude_bin=args.claude_bin,
bwrap_bin=ctx.bwrap_bin,
read_only_mounts=[
*dependency_mounts,
*ctx.runtime_mounts,
registry_mount,
*ce_mounts,
],
preflight=False,
backend=ctx.sandbox_backend,
) as sandbox:
# Capture the BASE (pre-overlay) skill digest — identical
# for the incumbent and candidate arms — then run the
# task's untrusted setup against those base skills. The
# candidate overlay is applied only afterwards, so setup
# can never observe candidate prose and both arms share
# byte-identical pre-overlay state.
# Historical review SHAs may predate gitnexus-review. Seed
# the current evaluated skill first so fingerprinting and
# the model see the same incumbent prose on every case.
if execution_arm == "review":
seed_evaluated_skills(
HARNESS_ROOT,
worktree,
sandbox=sandbox,
arm=execution_arm,
)
base_skill_digest = skill_fingerprint(worktree, execution_arm)
if task.get("setup"):
# Sanitization already removed eval/workflow_bench. Review
# cells copy the historical PR patch back under that path so
# `git apply` can read it. Do not overlay an empty mask on
# the same tree first — that hides the patch file and every
# cell dies with `can't open patch`. A historical patch that
# still edits the harness (gitignored learnings.jsonl) must
# skip those hunks or apply fails closed.
setup_command = ["/bin/sh", "-lc", with_hidden_harness_apply_exclude(str(task["setup"]))]
setup = sandbox.run(
setup_command,
timeout=600,
env=build_sandbox_environment(),
)
if not setup.ok:
raise ManagedProcessError(setup_command, setup)
# Setup (or its absence) must leave the staged harness copy gone
# before the model session starts. Fail closed rather than hide
# the tree with a mask that would also hide the patch from apply.
require_hidden_harness_absent(worktree)
# Tamper-evidence: setup must not have rewritten the base
# skills, verified before any candidate overlay lands.
require_skill_fingerprint(
worktree,
execution_arm,
base_skill_digest,
phase="task setup",
)
if arm in CANDIDATE_ARMS:
if ctx.candidate_overlay is None:
raise RuntimeError("candidate overlay is unavailable")
applied_digest = apply_candidate_overlay(
ctx.candidate_overlay,
worktree,
sandbox=sandbox,
)
if applied_digest != ctx.overlay_digest:
raise RuntimeError("candidate overlay changed during the benchmark run")
# The digest the model must preserve during its run is the
# post-overlay skill surface (candidate skills for
# candidate arms; unchanged base skills otherwise).
expected_skill_digest = (
skill_fingerprint(worktree, execution_arm) if arm in CANDIDATE_ARMS else base_skill_digest
)
orig_sha = _sandbox_git(sandbox, ["rev-parse", "HEAD"]).strip()
if not re.fullmatch(r"[0-9a-fA-F]{40,64}", orig_sha):
raise RuntimeError("sandboxed candidate setup did not produce an immutable commit")
before_work_digest = (
implementation_diff_digest(sandbox, orig_sha) if execution_arm in IMPLEMENTATION_ARMS else ""
)
record = run_arm(
execution_arm,
task,
worktree,
args,
sandbox=sandbox,
transcript_output_dir=ctx.out_dir,
transcript_output_prefix=f"{task['id']}-{arm}-run{run_idx}",
expected_skill_digest=expected_skill_digest,
enforce_phase_boundary=True,
ce_plugin_dir=ce_plugin_dir_for_arm(execution_arm, ctx.ce_plugin_snapshot),
oracle_snapshot=ctx.oracle_snapshot,
)
if execution_arm in ("review", "ce_review"):
review_source = worktree / REVIEW_OUTPUT
if review_source.is_file() and not review_source.is_symlink():
review_artifact = ctx.out_dir / f"{task['id']}-{arm}-run{run_idx}.review.json"
review_artifact.write_bytes(_bounded_regular_bytes(review_source, limit=256 * 1024))
record["review_artifact"] = review_artifact.name
_prepare_untracked_for_diff(sandbox)
after_work_digest = (
implementation_diff_digest(
sandbox,
orig_sha,
prepare_untracked=False,
)
if execution_arm in IMPLEMENTATION_ARMS
else ""
)
record.update(
diff_churn(
sandbox,
orig_sha,
prepare_untracked=False,
)
)
enforce_work_evidence(
record,
arm=execution_arm,
before_digest=before_work_digest,
after_digest=after_work_digest,
)
patch_bytes = capture_patch(sandbox, worktree, orig_sha)
record["arm"] = arm
record.update(
{
"model": args.model,
"benchmark_model": args.model,
"proposer_model": args.proposer_model,
"effort": args.effort,
"task_ref": task.get("ref", "HEAD"),
"task_base_sha": ctx.task_sha,
"sanitized_task_sha": sanitized_head,
"variant_head_sha": orig_sha,
"task_prompt_digest": hashlib.sha256(task["prompt"].encode()).hexdigest(),
"skill_digest": expected_skill_digest,
"candidate_overlay_digest": (ctx.overlay_digest if arm in CANDIDATE_ARMS else None),
"recorded_at": datetime.now(UTC).isoformat(),
}
)
# Final working-tree patch — the clone is destroyed, so
# this is the only artifact for diagnosing verify fails.
patch_path = ctx.out_dir / f"{task['id']}-{arm}-run{run_idx}.patch"
patch_path.write_bytes(patch_bytes)
except (
ManagedProcessError,
SandboxError,
OSError,
RuntimeError,
ValueError,
) as exc:
# One hung session or failed setup must not abort the
# sweep — record the run as infra-error and move on so
# report.md/promotion.json still get written.
record = infra_error_record(exc)
if isinstance(exc, ManagedProcessError) and exc.result.state == "cancelled":
record["error_kind"] = "cancelled"
record["arm"] = arm
# ManagedProcessError carries up to 1000 raw bytes of stderr_tail, and
# this line now streams live into the CI log (run_managed echoes the
# sweep's stdout). Redact it like every other sink this data reaches.
detail = redact_text(str(exc), credential_secrets(args))
print(f"[{task['id']}][{arm}][run {run_idx}] infra-error: {detail}")
finally:
if worktree is not None and worktree.exists():
try:
remove_clone(worktree)
except OSError as exc:
cleanup_error = exc
assert record is not None
if cleanup_error is not None:
primary_kind = record.get("error_kind")
primary_detail = record.get("error_detail")
record["resolved"] = False
record["ok"] = False
record["error_kind"] = "cleanup-failure"
record["error_detail"] = (
f"primary={primary_kind}: {primary_detail}; cleanup: {type(cleanup_error).__name__}: {cleanup_error}"
)[:2000]
record.update(
{
"task": task["id"],
"class": task.get("class", ""),
"run": run_idx,
"sandbox_backend": ctx.sandbox_backend,
"task_asset_snapshot_digest": (ctx.asset_snapshot.digest if ctx.asset_snapshot is not None else None),
"task_asset_manifest_digest": (
ctx.asset_snapshot.manifest_digest if ctx.asset_snapshot is not None else None
),
"sandbox_dependency_content_digest": (
ctx.asset_snapshot.dependency_content_digest if ctx.asset_snapshot is not None else None
),
"sandbox_dependency_manifest_digest": (
ctx.asset_snapshot.dependency_manifest_digest if ctx.asset_snapshot is not None else None
),
"sanitized_graph_snapshot_digest": (ctx.graph_snapshot.digest if ctx.graph_snapshot is not None else None),
"sanitized_graph_manifest_digest": (
ctx.graph_snapshot.manifest_digest if ctx.graph_snapshot is not None else None
),
"oracle_digest": ctx.oracle_snapshot.digest,
"oracle_command_digest": ctx.oracle_snapshot.command_digest,
"oracle_manifest_digest": ctx.oracle_snapshot.manifest_digest,
"ce_plugin_version": (
ctx.ce_plugin_snapshot.version if arm in CE_ARMS and ctx.ce_plugin_snapshot is not None else None
),
"ce_plugin_manifest_digest": (
ctx.ce_plugin_snapshot.manifest_digest
if arm in CE_ARMS and ctx.ce_plugin_snapshot is not None
else None
),
}
)
return record
def infra_error_record(exc: BaseException) -> dict[str, Any]:
"""Row for a run the harness itself killed (timeout, setup failure)."""
if isinstance(exc, ManagedProcessError):
process = exc.result
detail = f"{process.state}: {process.detail or process.stderr_tail[-1500:]}"
else:
detail = f"{type(exc).__name__}: {exc}"
record: dict[str, Any] = dict.fromkeys(USAGE_FIELDS, 0)
record.update(
{
"ok": False,
"resolved": False,
"error_kind": "infra-error",
"error_detail": detail[:2000],
"session_ids": [],
"cost_usd": 0.0,
"duration_s": 0.0,
"num_turns": 0,
"plan_produced": False,
"authored_tests_passed": False,
"authored_test_output": "",
"oracle_passed": False,
"oracle_output": "",
"verify_output": "",
"skill_invoked": None,
"transcript_missing": False,
}
)
return record
def cell_progress_line(task_id: str, arm: str, run_idx: int, record: dict[str, Any]) -> str:
"""The live one-line summary printed as each cell finishes.
An infra-error row carries 0.0 cost and 0.0 duration as placeholders: the
cell died before any session could report a number. Printed as bare zeros
next to real rows they read as a run that was instant and free — the exact
misreading ``_na`` exists to prevent — so they are rendered "n/a" instead.
The row on disk is untouched: results.jsonl is promotion evidence and its
field types stay as they are.
"""
measured = record.get("error_kind") not in {"infra-error", "cleanup-failure"}
cost_usd = record.get("cost_usd") if measured else None
duration_s = record.get("duration_s") if measured else None
quality = record.get("review_weighted_f1")
quality_text = "n/a" if quality is None else f"{quality:.3f}"
return (
f"[{task_id}][{arm}][run {run_idx}] resolved={record['resolved']} "
f"quality={quality_text} "
f"in={record['input_tokens']} out={record['output_tokens']} "
f"cost={'n/a' if cost_usd is None else f'${cost_usd}'} "
f"took={'n/a' if duration_s is None else f'{duration_s}s'} "
# An excluded run is what actually blocks promotion, so name it here
# instead of leaving it to results.jsonl.
f"error_kind={record.get('error_kind') or 'none'}"
)
# A failing cell's error_kind names the category; the detail names the cause.
# Bounded because a session-error detail carries stdout/stderr tails.
MAX_CELL_DETAIL_CHARS = 1200
def cell_failure_detail_line(
task_id: str,
arm: str,
run_idx: int,
record: Mapping[str, Any],
secrets: Sequence[str] = (),
) -> str | None:
"""The redacted reason a cell failed, or None when it succeeded.
Without this the log says only ``error_kind=plan-evidence-invalid`` and the
reason stays locked in results.jsonl, which is an uploaded artifact rather
than something a watcher can read while the sweep is still running.
"""
if not record.get("error_kind"):
return None
detail = record.get("error_detail")
if detail in (None, "", {}, []):
return None
rendered = detail if isinstance(detail, str) else json.dumps(detail, default=str, sort_keys=True)
rendered = redact_text(rendered, secrets).replace("\n", "")
if len(rendered) > MAX_CELL_DETAIL_CHARS:
rendered = f"{rendered[:MAX_CELL_DETAIL_CHARS]}…[truncated {len(rendered) - MAX_CELL_DETAIL_CHARS} chars]"
return f"[{task_id}][{arm}][run {run_idx}] detail: {rendered}"
def aggregate(records: list[dict[str, Any]]) -> dict[str, Any]:
"""Median metrics + resolve rate across repeated runs of one task+arm.
Session/infra-error rows are excluded from the medians (they measured
nothing); ``valid_runs``/``excluded_runs`` make the exclusion visible.
"""
valid = [
r
for r in records
if r.get("error_kind") not in EXCLUDED_ERROR_KINDS and r.get("review_evidence_valid") is not False
]
metrics = (*USAGE_FIELDS, "duration_s", "num_turns", *CHURN_FIELDS)
out: dict[str, Any] = {m: statistics.median(r.get(m, 0) for r in (valid or [{}])) for m in metrics}
# cost_usd can be None (unmeasured) on an otherwise-valid run; a single
# unmeasured run makes the whole median unavailable so the gate won't rank
# a candidate on a cost that was never actually captured.
valid_costs = [r.get("cost_usd") for r in valid]
out["cost_usd"] = (
None if (not valid or any(cost is None for cost in valid_costs)) else statistics.median(valid_costs)
)
out["resolved"] = sum(1 for r in records if r["resolved"])
out["runs"] = len(records)
out["valid_runs"] = len(valid)
out["excluded_runs"] = len(records) - len(valid)
out["transcripts_missing"] = sum(1 for r in records if r.get("transcript_missing"))
out["class"] = records[0].get("class", "")
error_kinds: dict[str, int] = {}
for r in records:
kind = r.get("error_kind")
if kind:
error_kinds[kind] = error_kinds.get(kind, 0) + 1
out["error_kinds"] = error_kinds
review_metrics = (
"review_true_positives",
"review_false_positives",
"review_false_negatives",
"review_precision",
"review_recall",
"review_f1",
"review_weighted_precision",
"review_weighted_recall",
"review_weighted_f1",
"review_blocker_recall",
"review_severity_accuracy",
"review_category_accuracy",
"review_grounded_evidence",
)
if any("review_weighted_f1" in record for record in valid):
for metric in review_metrics:
values = [record[metric] for record in valid if record.get(metric) is not None]
reducer = min if metric == "review_blocker_recall" else statistics.median
out[metric] = reducer(values) if values and len(values) == len(valid) else None
verdicts = [record.get("review_verdict_correct") for record in valid]
out["review_verdict_correct"] = (
all(value is True for value in verdicts)
if verdicts and all(isinstance(value, bool) for value in verdicts)
else None
)
controls = [record["review_clean_control"] for record in valid if "review_clean_control" in record]
out["review_clean_control"] = all(controls) if controls and len(controls) == len(valid) else None
clean_passes = [record["review_clean_pass"] for record in valid if "review_clean_pass" in record]
out["review_clean_pass"] = all(clean_passes) if clean_passes and len(clean_passes) == len(valid) else None
return out
def savings(baseline: dict[str, Any], workflow: dict[str, Any]) -> dict[str, Any]:
"""Percent saved by the workflow arm per metric (positive = cheaper)."""
out: dict[str, Any] = {}
for metric in (*USAGE_FIELDS, "cost_usd", "duration_s"):
base = baseline.get(metric)
arm = workflow.get(metric)
if base is None or arm is None:
out[metric] = None
else:
out[metric] = round(100 * (base - arm) / base, 1) if base else 0.0
return out
def broken_incumbent_arms(
results: dict[str, dict[str, dict[str, Any]]],
incumbent_arms: set[str],
) -> list[str]:
"""Incumbent arms that resolved nothing across every task they ran.
An incumbent arm is the currently-shipped, presumably-working skill: if it
resolves NOTHING across every task it ran, that reads as an environment or
harness failure (missing trusted interpreter, stale skill fingerprint,
sandbox misconfiguration), not a skill regression. A candidate merely
underperforming is a normal, expected outcome and must not trip this —
only checking incumbents keeps that distinction.
Deliberately does NOT require valid_runs > 0 per task: an incumbent that
fails every run with an excluded-but-non-systemic error_kind (e.g.
"evidence-unverified", which the outage-streak breaker explicitly resets
on rather than accumulates) would otherwise never accumulate a single
valid run and sail through silently — the exact "quiet no-promotion"
outcome this guard exists to catch, and arguably worse than the
some-runs-resolved-zero case since here nothing completed at all.
aggregate() never marks an excluded/unverifiable row resolved=True, so
resolved == 0 alone already covers both cases.
"""
present = incumbent_arms & {arm for arms in results.values() for arm in arms}
return sorted(arm for arm in present if all(arms[arm]["resolved"] == 0 for arms in results.values() if arm in arms))
def _cost_cell(value: Any) -> str:
return "n/a" if value is None else f"{value:.4f}"
def _review_metric_cell(value: Any) -> str:
return "n/a" if value is None else f"{value:.3f}"
def render_report(results: dict[str, dict[str, dict[str, Any]]]) -> str:
"""results: {task_id: {arm: aggregate}} → markdown report."""
lines = [
"# gitnexus workflow benchmark",
"",
"Medians across runs; savings rows = (baseline arm) / baseline per arm.",
"A negative saving means that arm spent more than baseline. churn =",
"files/+insertions/deletions vs the worktree's starting commit.",
"",
"**WARNING:** token columns count only each arm's main-loop session —",
"subagent spend is invisible to them and flatters subagent-heavy arms.",
"cost $ is the only column that includes subagent spend; to rank token",
"efficiency, sum usage from the session transcripts instead",
"(dedup events sharing one message.id).",
"",
"| task | class | arm | resolved | input | cache_create | cache_read | output | cost $ | wall s | turns | churn | errors |",
"| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |",
]
for task_id, arms in results.items():
for arm, agg in arms.items():
excluded = agg.get("excluded_runs", 0)
resolved_cell = f"{agg['resolved']}/{agg.get('valid_runs', agg['runs'])}"
if excluded:
resolved_cell += f" ({excluded} excluded)"
error_cell = ", ".join(f"{kind}×{count}" for kind, count in sorted(agg.get("error_kinds", {}).items()))
lines.append(
f"| {task_id} | {agg['class']} | {arm} | {resolved_cell} "
f"| {agg['input_tokens']:.0f} | {agg['cache_creation_input_tokens']:.0f} "
f"| {agg['cache_read_input_tokens']:.0f} | {agg['output_tokens']:.0f} "
f"| {_cost_cell(agg['cost_usd'])} | {agg['duration_s']:.0f} | {agg['num_turns']:.0f} "
f"| {agg['diff_files']:.0f}/+{agg['diff_insertions']:.0f}/{agg['diff_deletions']:.0f} "
f"| {error_cell} |"
)
for arm in arms:
if arm != "baseline" and "baseline" in arms:
s = savings(arms["baseline"], arms[arm])
lines.append(
f"| {task_id} | {arms[arm]['class']} | **{arm} savings %** | — "
f"| {s['input_tokens']} | {s['cache_creation_input_tokens']} "
f"| {s['cache_read_input_tokens']} | {s['output_tokens']} "
f"| {_na(s['cost_usd'])} | {s['duration_s']} | — | — | — |"
)
lines.append("")
if any("review_clean_control" in agg for arms in results.values() for agg in arms.values()):
lines.extend(
[
"## Review quality",
"",
"| case | arm | TP | FP | FN | precision | recall | blocker recall | weighted F1 | grounding |",
"| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |",
]
)
for task_id, arms in results.items():
for arm, agg in arms.items():
if "review_clean_control" not in agg:
continue
lines.append(
f"| {task_id} | {arm} | {agg['review_true_positives']:.1f} "
f"| {agg['review_false_positives']:.1f} | {agg['review_false_negatives']:.1f} "
f"| {_review_metric_cell(agg['review_precision'])} "
f"| {_review_metric_cell(agg['review_recall'])} "
f"| {_review_metric_cell(agg['review_blocker_recall'])} "
f"| {_review_metric_cell(agg['review_weighted_f1'])} "
f"| {_review_metric_cell(agg['review_grounded_evidence'])} |"
)
lines.append("")
all_aggs = [agg for arms in results.values() for agg in arms.values()]
excluded_total = sum(agg.get("excluded_runs", 0) for agg in all_aggs)
if excluded_total:
lines.append(
f"{excluded_total} run(s) hit session/infra errors or had unverifiable "
"evidence and were excluded "
"from medians and resolve denominators — see error_kind in results.jsonl."
)
missing_total = sum(agg.get("transcripts_missing", 0) for agg in all_aggs)
if missing_total:
lines.append(
f"{missing_total} run(s) had no locatable session transcript or it was "
"unreadable, so they were excluded from promotion evidence "
"(skill_invoked=null in results.jsonl)."
)
lines.append(
"Session ids for every run are in results.jsonl — open the matching "
"transcript to see where each arm spent its tokens."
)
return "\n".join(lines)
# ─── Main ────────────────────────────────────────────────────────────────────
def worker_count(value: str) -> int:
"""``--workers`` as a 1..MAX_WORKERS int, rejected at parse time."""
workers = int(value)
if not 1 <= workers <= MAX_WORKERS:
raise argparse.ArgumentTypeError(f"must be between 1 and {MAX_WORKERS}")
return workers
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--tasks", required=True, type=Path)
parser.add_argument("--runs", type=int, default=1)
parser.add_argument(
"--workers",
type=worker_count,
default=1,
help=f"cells of one task to run at once (default 1, fully serial; max "
f"{MAX_WORKERS}). Size this to the machine: a cell that loses CPU to "
"its siblings takes longer, and a session that reaches its timeout is "
"an excluded run the promotion gate refuses to work with. Above 1 the "
"cells run on worker threads, so Ctrl-C no longer reaches the code "
"owning a sandboxed process and abandons the running cells instead of "
"cleaning up after them.",
)
parser.add_argument(
"--outage-streak",
type=int,
default=DEFAULT_OUTAGE_STREAK,
help="abort the sweep after this many consecutive session/infra/cleanup "
"failures (0 disables the circuit breaker)",
)
parser.add_argument(
"--arms",
nargs="+",
default=["workflow", "workflow_direct", "baseline"],
choices=[
"workflow",
"candidate_workflow",
"workflow_direct",
"candidate_workflow_direct",
"candidate_review",
"ce_workflow",
"ce_workflow_direct",
"review",
"ce_review",
"baseline",
"baseline_nomcp",
],
)
parser.add_argument("--claude-bin", default="claude")
parser.add_argument(
"--ce-plugin-dir",
type=Path,
default=None,
help="operator-supplied Compound Engineering plugin directory; required for ce_* arms",
)
parser.add_argument(
"--ce-plugin-version",
default=None,
help="exact Compound Engineering plugin version; required for ce_* arms",
)
parser.add_argument(
"--timeout",
type=int,
default=SESSION_TIMEOUT_SECONDS,
help="per session, seconds",
)
parser.add_argument("--out", type=Path, default=None)
parser.add_argument(
"--model",
required=True,
help="named, versioned model passed to every `claude --model` invocation",
)
parser.add_argument(
"--effort",
choices=("low", "medium", "high", "xhigh", "max"),
default="xhigh",
help="reasoning effort passed to every `claude --effort` invocation",
)
parser.add_argument(
"--proposer-model",
default=None,
help="model that generated the candidate overlay (recorded for provenance)",
)
parser.add_argument(
"--base-url",
default=None,
help="ANTHROPIC_BASE_URL override — point at an Anthropic-compatible "
"proxy (see free-model.litellm.yaml) to run on a free model",
)
parser.add_argument(
"--anthropic-api-key",
"--auth-token",
dest="auth_token",
default=anthropic_api_key_from_environ(),
help="Anthropic API key for Claude Code sessions (prefer "
"GITNEXUS_BENCH_ANTHROPIC_API_KEY). Not a Claude Code OAuth token. "
"Legacy --auth-token / GITNEXUS_BENCH_AUTH_TOKEN is still accepted.",
)
parser.add_argument(
"--openai-api-key",
default=openai_api_key_from_environ(),
help="OpenAI API key; starts a loopback Anthropic-compatible proxy "
"(prefer GITNEXUS_BENCH_OPENAI_API_KEY). The key never enters the sandbox.",
)
parser.add_argument(
"--include-expensive",
action="store_true",
help="include scenarios marked expensive: true (excluded by default)",
)
parser.add_argument(
"--candidate-overlay",
type=Path,
default=None,
help="directory mirroring one promotable .claude/skills/gitnexus-* tree; applied only to candidate_* arms",
)
parser.add_argument(
"--promotion-metric",
choices=PROMOTION_METRICS,
default="cost_usd",
help="efficiency metric used by the deterministic candidate gate; "
"cost_usd (default) is the only CLI-reported number that includes "
"subagent spend — token metrics count only the main loop",
)
parser.add_argument("--promotion-min-runs", type=int, default=3)
parser.add_argument("--promotion-min-improvement", type=float, default=5.0)
parser.add_argument("--promotion-max-task-regression", type=float, default=20.0)
parser.add_argument("--task-bindings-json", default=None, help=argparse.SUPPRESS)
parser.add_argument("--promotion-target-bases-json", default=None, help=argparse.SUPPRESS)
parser.add_argument("--unsafe-no-bwrap", action="store_true", help=argparse.SUPPRESS)
return parser
def main() -> None:
parser = build_parser()
args = parser.parse_args()
try:
args.model = normalized_model_identifier(args.model)
args.proposer_model = (
normalized_model_identifier(args.proposer_model, flag="--proposer-model")
if args.proposer_model is not None
else None
)
task_document = yaml.safe_load(args.tasks.read_text())
if not isinstance(task_document, Mapping) or not isinstance(task_document.get("tasks"), list):
raise ValueError("task file must contain a tasks list")
tasks, skipped_expensive = select_tasks(
task_document["tasks"],
include_expensive=args.include_expensive,
)
oracle_snapshots = capture_task_oracles(tasks)
expected_task_bindings = json.loads(args.task_bindings_json) if args.task_bindings_json else None
if expected_task_bindings is not None and not isinstance(expected_task_bindings, list):
raise ValueError("--task-bindings-json must contain a list")
supplied_promotion_target_bases = (
json.loads(args.promotion_target_bases_json) if args.promotion_target_bases_json else {}
)
if not isinstance(supplied_promotion_target_bases, dict) or not all(
isinstance(path, str) and isinstance(digest, str)
for path, digest in supplied_promotion_target_bases.items()
):
raise ValueError("--promotion-target-bases-json must contain a string mapping")
ce_plugin_config = validate_ce_plugin_inputs(
args.arms,
args.ce_plugin_dir,
args.ce_plugin_version,
)
except (OSError, SandboxError, ValueError, yaml.YAMLError) as exc:
parser.error(str(exc))
raise AssertionError("ArgumentParser.error() returned unexpectedly")
candidate_arms = [arm for arm in args.arms if arm in CANDIDATE_ARMS]
if candidate_arms and args.candidate_overlay is None:
parser.error("candidate_* arms require --candidate-overlay")
if args.candidate_overlay is not None and not candidate_arms:
parser.error("--candidate-overlay requires at least one candidate_* arm")
for candidate_arm in candidate_arms:
incumbent_arm = CANDIDATE_ARMS[candidate_arm]
if incumbent_arm not in args.arms:
parser.error(f"{candidate_arm} must be paired with {incumbent_arm}")
if args.runs < 1 or args.promotion_min_runs < 1:
parser.error("--runs and --promotion-min-runs must be positive")
candidate_overlay = args.candidate_overlay.expanduser().absolute() if args.candidate_overlay is not None else None
overlay_digest = candidate_overlay_digest(candidate_overlay) if candidate_overlay is not None else None
if candidate_overlay is not None:
required_candidates = required_candidate_arms(candidate_overlay)
required_arms = [arm for candidate in required_candidates for arm in (CANDIDATE_ARMS[candidate], candidate)]
if required_candidates == ["candidate_review"]:
required_arms.insert(0, "ce_review")
if args.arms != required_arms:
parser.error("candidate overlay requires exactly these paired arms: " + " ".join(required_arms))
try:
promotion_target_bases = committed_destination_base_digests(candidate_overlay)
except ValueError as exc:
# Overlay adds a promotion target with no committed base — a clean
# CLI error, not a traceback.
parser.error(str(exc))
raise AssertionError("ArgumentParser.error() returned unexpectedly")
if supplied_promotion_target_bases and supplied_promotion_target_bases != promotion_target_bases:
parser.error("--promotion-target-bases-json does not match the committed incumbent")
else:
if supplied_promotion_target_bases:
parser.error("--promotion-target-bases-json requires --candidate-overlay")
promotion_target_bases = {}
if args.unsafe_no_bwrap and os.environ.get("CI"):
parser.error("--unsafe-no-bwrap is forbidden when CI is set")
if args.unsafe_no_bwrap and args.arms != ["ce_review", "review", "candidate_review"]:
parser.error("--unsafe-no-bwrap is restricted to the paired review arms")
try:
if args.unsafe_no_bwrap:
bwrap_bin = preflight_unsafe_host()
sandbox_backend = "host-unsafe"
print(
"WARNING: --unsafe-no-bwrap runs sessions directly on the host with no "
"containment; model and verifier processes can access the host filesystem, "
"network, and credentials.",
file=sys.stderr,
)
else:
bwrap_bin = preflight_bubblewrap()
sandbox_backend = "bwrap"
require_claude_sandbox_helpers()
runtime_mounts = trusted_gitnexus_runtime_mounts()
except SandboxError as exc:
parser.error(str(exc))
raise AssertionError("ArgumentParser.error() returned unexpectedly")
gateway = attach_openai_gateway(args)
try:
gateway.__enter__()
except ValueError as exc:
parser.error(str(exc))
raise AssertionError("ArgumentParser.error() returned unexpectedly")
try:
with cancellation_scope(handle_signals=True) as cancel_event:
_run_sweep(
args,
cancel_event=cancel_event,
parser=parser,
tasks=tasks,
skipped_expensive=skipped_expensive,
oracle_snapshots=oracle_snapshots,
expected_task_bindings=expected_task_bindings,
ce_plugin_config=ce_plugin_config,
bwrap_bin=bwrap_bin,
sandbox_backend=sandbox_backend,
runtime_mounts=runtime_mounts,
candidate_arms=candidate_arms,
candidate_overlay=candidate_overlay,
overlay_digest=overlay_digest,
promotion_target_bases=promotion_target_bases,
)
finally:
gateway.__exit__(None, None, None)
def _run_sweep(
args: argparse.Namespace,
*,
parser: argparse.ArgumentParser,
tasks: list[Any],
skipped_expensive: list[str],
oracle_snapshots: Any,
expected_task_bindings: Any,
ce_plugin_config: Any,
bwrap_bin: Any,
sandbox_backend: str,
runtime_mounts: Any,
candidate_arms: list[str],
candidate_overlay: Path | None,
overlay_digest: str | None,
promotion_target_bases: dict[str, str],
cancel_event: threading.Event | None = None,
) -> None:
cancel_event = cancel_event or threading.Event()
out_dir = args.out or Path("results") / time.strftime("wfbench-%Y%m%d-%H%M%S")
out_dir.mkdir(parents=True, exist_ok=True)
results_path = out_dir / "results.jsonl"
selected_ids = [task["id"] for task in tasks]
print(
f"selected {len(selected_ids)} task(s): {', '.join(selected_ids)}; "
f"skipped {len(skipped_expensive)} expensive task(s): "
f"{', '.join(skipped_expensive) if skipped_expensive else 'none'}"
)
results: dict[str, dict[str, dict[str, Any]]] = {}
outage_streak = 0
outage_tripped = False
# Progress accounting for the sweep. A generation runs for hours and each
# cell is a full set of agent sessions, so the log needs to say what is in
# flight and how much is left, not only what already finished.
total_cells = len(tasks) * args.runs * len(args.arms)
started_cells = 0
sweep_started = time.monotonic()
with (
tempfile.TemporaryDirectory(prefix="wfbench-trees-") as trees,
TaskAssetCache(Path(trees) / ".task-assets") as task_asset_cache,
staged_ce_plugin_snapshot(
ce_plugin_config,
destination_parent=Path(trees),
) as ce_plugin_snapshot,
):
try:
task_bindings = resolve_task_bindings(
tasks,
expected_task_bindings,
oracle_snapshots=oracle_snapshots,
task_asset_cache=task_asset_cache,
)
except (OSError, SandboxError, ValueError) as exc:
parser.error(str(exc))
raise AssertionError("ArgumentParser.error() returned unexpectedly")
graph_snapshots: dict[tuple[str, str], SanitizedGraphSnapshot] = {}
graph_snapshot_errors: dict[tuple[str, str], BaseException] = {}
for task, task_binding, oracle_snapshot in zip(
tasks,
task_bindings,
oracle_snapshots,
strict=True,
):
if outage_tripped or cancel_event.is_set():
break
repo = Path(task_binding["repo_identity"])
task_sha = task_binding["resolved_sha"]
asset_snapshot: TaskAssetSnapshot | None = None
asset_snapshot_error: BaseException | None = None
graph_key = (str(repo), task_sha)
graph_snapshot: SanitizedGraphSnapshot | None = graph_snapshots.get(graph_key)
graph_snapshot_error: BaseException | None = graph_snapshot_errors.get(graph_key)
try:
validate_no_prebuilt_graph_assets(task)
if graph_snapshot is None and graph_snapshot_error is None:
graph_snapshot = prepare_sanitized_graph(
task,
repo=repo,
resolved_sha=task_sha,
parent=Path(trees),
cache=task_asset_cache,
claude_bin=args.claude_bin,
bwrap_bin=bwrap_bin,
sandbox_backend=sandbox_backend,
runtime_mounts=runtime_mounts,
)
graph_snapshots[graph_key] = graph_snapshot
except (ManagedProcessError, OSError, SandboxError, RuntimeError, ValueError) as exc:
graph_snapshot_error = exc
graph_snapshot_errors[graph_key] = exc
try:
# Prepared here, once, rather than lazily inside the first cell:
# TaskAssetCache is a plain dict, so a lazy build would be a
# read-then-write race the moment cells stop running serially.
asset_snapshot = task_asset_cache.prepare(
task,
repo=repo,
resolved_sha=task_sha,
expected_dependency_binding=task_binding,
)
except (OSError, SandboxError, ValueError) as exc:
asset_snapshot_error = exc
cell_context = TaskCellContext(
task=task,
oracle_snapshot=oracle_snapshot,
repo=repo,
task_sha=task_sha,
graph_snapshot=graph_snapshot,
graph_snapshot_error=graph_snapshot_error,
asset_snapshot=asset_snapshot,
asset_snapshot_error=asset_snapshot_error,
args=args,
out_dir=out_dir,
ce_plugin_snapshot=ce_plugin_snapshot,
trees_dir=Path(trees),
bwrap_bin=bwrap_bin,
sandbox_backend=sandbox_backend,
runtime_mounts=runtime_mounts,
candidate_overlay=candidate_overlay,
overlay_digest=overlay_digest,
)
per_arm: dict[str, list[dict[str, Any]]] = {a: [] for a in args.arms}
cells = [(run_idx, arm) for run_idx in range(args.runs) for arm in args.arms]
def announce(run_idx: int, arm: str) -> None:
nonlocal started_cells
started_cells += 1
print(
f"[{task['id']}][{arm}][run {run_idx}] starting "
f"({started_cells}/{total_cells}, {(time.monotonic() - sweep_started) / 60:.0f}m elapsed)"
)
def keep(run_idx: int, arm: str, record: dict[str, Any]) -> None:
per_arm[arm].append(record)
with results_path.open("a") as fh:
# Redact any API token a session-error stderr_tail echoed
# into error_detail before it enters the uploaded
# results.jsonl artifact (transcripts are redacted; this
# sink was not).
fh.write(redact_text(json.dumps(record), credential_secrets(args)) + "\n")
print(cell_progress_line(task["id"], arm, run_idx, record))
failure = cell_failure_detail_line(task["id"], arm, run_idx, record, credential_secrets(args))
if failure:
print(failure)
outage_streak, outage_tripped = sweep_task_cells(
cells,
workers=args.workers,
run=partial(run_cell, cell_context),
on_start=announce,
on_record=keep,
outage_streak=outage_streak,
outage_limit=args.outage_streak,
cancel_event=cancel_event,
)
results[task["id"]] = {a: aggregate(rs) for a, rs in per_arm.items() if rs}
selection_report = [
"## Run provenance",
"",
f"Benchmark model: `{args.model}`",
f"Proposer model: `{args.proposer_model}`",
f"Reasoning effort: `{args.effort}`",
f"Selected tasks ({len(selected_ids)}): {', '.join(selected_ids)}",
(
f"Skipped expensive tasks ({len(skipped_expensive)}): "
+ (", ".join(skipped_expensive) if skipped_expensive else "none")
),
]
if ce_plugin_snapshot is not None:
selection_report.append(
f"Compound Engineering plugin: `{ce_plugin_snapshot.version}` (`{ce_plugin_snapshot.manifest_digest}`)"
)
if cancel_event.is_set():
selection_report.append("Sweep cancelled: partial evidence; promotion is disabled.")
elif outage_tripped:
selection_report.append("Sweep aborted: partial evidence; promotion is disabled.")
report = render_report(results) + "\n\n" + "\n".join(selection_report) + "\n"
(out_dir / "report.md").write_text(report)
if candidate_arms:
promotion_generated_at = datetime.now(UTC)
promotion = {
"generated_at": promotion_generated_at.isoformat(),
"evidence_expires_at": (promotion_generated_at + timedelta(days=EVIDENCE_MAX_AGE_DAYS)).isoformat(),
"benchmark_model": args.model,
"proposer_model": args.proposer_model,
"effort": args.effort,
"candidate_origin": ("model-proposer" if args.proposer_model is not None else "manual-initial-overlay"),
"candidate_overlay": str(candidate_overlay),
"candidate_overlay_digest": overlay_digest,
"target_base_digests": promotion_target_bases,
"required_candidate_arms": candidate_arms,
"selected_tasks": task_bindings,
"ce_plugin": ce_plugin_snapshot.provenance if ce_plugin_snapshot is not None else None,
**promotion_evidence(
results,
model=args.model,
complete=not outage_tripped and not cancel_event.is_set(),
policy=promotion_policy(
candidate_arms,
metric=args.promotion_metric,
min_runs=args.promotion_min_runs,
min_improvement_pct=args.promotion_min_improvement,
max_task_regression_pct=args.promotion_max_task_regression,
),
),
}
(out_dir / "promotion.json").write_text(json.dumps(promotion, indent=2) + "\n")
print(f"\n{report}\n\nWritten to {out_dir}/")
# A reviewer may legitimately match none of a difficult hidden corpus;
# unlike an implementation arm, zero exact resolutions is quality signal,
# not proof that the harness failed.
broken_incumbents = broken_incumbent_arms(results, set(CANDIDATE_ARMS.values()) - {"review"})
if broken_incumbents:
# Fail loudly rather than let a broken environment read as a quiet
# "no promotion, incumbent stands."
print(
f"[harness-health] incumbent arm(s) {', '.join(broken_incumbents)} resolved zero "
"tasks across every valid run — this looks like an environment/harness failure, "
"not a normal candidate miss. See the errors column in report.md and error_detail "
"in results.jsonl. Exiting non-zero rather than reporting a quiet no-promotion."
)
raise SystemExit(1)
if cancel_event.is_set():
raise SystemExit(130)
if outage_tripped:
# Non-zero exit so a driver (evolve.py) treats the partial benchmark as a
# failed run and halts instead of proposing from outage-truncated evidence.
raise SystemExit(1)
if __name__ == "__main__":
main()