#!/usr/bin/env python3 """ Local benchmark runner for claw-code-agent. Runs the REAL agent binary against a suite of coding tasks and scores pass/fail automatically. No Docker required. Usage: # Run all tasks python3 -m benchmarks.run # Run a single task python3 -m benchmarks.run --task file-create-basic # Run a category python3 -m benchmarks.run --category bugfix # Run a difficulty level python3 -m benchmarks.run --difficulty easy # List available tasks python3 -m benchmarks.run --list # Verbose output python3 -m benchmarks.run --verbose """ from __future__ import annotations import argparse import json import os import shutil import subprocess import sys import tempfile import time from dataclasses import dataclass from pathlib import Path from benchmarks.tasks.definitions import TASKS, BenchmarkTask, get_task # --------------------------------------------------------------------------- # Result tracking # --------------------------------------------------------------------------- @dataclass class TaskResult: task_id: str category: str difficulty: str passed: bool duration_sec: float agent_exit_code: int verify_exit_code: int error: str = "" # --------------------------------------------------------------------------- # Task execution # --------------------------------------------------------------------------- def _run_shell(cmd: str, cwd: str, timeout: float = 30.0) -> tuple[int, str]: """Run a shell command, return (exit_code, combined_output).""" try: proc = subprocess.run( cmd, shell=True, cwd=cwd, capture_output=True, text=True, timeout=timeout, ) output = (proc.stdout + proc.stderr).strip() return proc.returncode, output except subprocess.TimeoutExpired: return 1, f"[TIMEOUT after {timeout}s]" except Exception as exc: return 1, str(exc) def run_task( task: BenchmarkTask, *, project_root: str, agent_timeout: float = 300.0, verbose: bool = False, ) -> TaskResult: """Run a single benchmark task end-to-end.""" # Create isolated temp workspace workspace = tempfile.mkdtemp(prefix=f"claw_bench_{task.id}_") if verbose: print(f" workspace: {workspace}") try: # --- SETUP --- if task.setup: code, out = _run_shell(task.setup, cwd=workspace) if code != 0: return TaskResult( task_id=task.id, category=task.category, difficulty=task.difficulty, passed=False, duration_sec=0.0, agent_exit_code=-1, verify_exit_code=-1, error=f"Setup failed: {out}", ) # --- RUN AGENT --- agent_cmd = ( f"{sys.executable} -m src.main agent " f"{_shell_quote(task.instruction)} " f"--cwd {_shell_quote(workspace)} " f"--allow-write " f"--allow-shell" ) if verbose: print(f" agent cmd: {agent_cmd[:120]}...") start = time.time() agent_code, agent_out = _run_shell( agent_cmd, cwd=project_root, timeout=agent_timeout, ) duration = time.time() - start if verbose: print(f" agent exit={agent_code} duration={duration:.1f}s") if agent_out: # Print last few lines of agent output lines = agent_out.split("\n") for line in lines[-5:]: print(f" > {line}") # --- VERIFY --- verify_code, verify_out = _run_shell(task.verify, cwd=workspace, timeout=30.0) if verbose: status = "PASS" if verify_code == 0 else "FAIL" print(f" verify exit={verify_code} -> {status}") if verify_code != 0 and verify_out: print(f" verify output: {verify_out[:200]}") return TaskResult( task_id=task.id, category=task.category, difficulty=task.difficulty, passed=(verify_code == 0), duration_sec=duration, agent_exit_code=agent_code, verify_exit_code=verify_code, error=verify_out if verify_code != 0 else "", ) finally: # Clean up workspace shutil.rmtree(workspace, ignore_errors=True) def _shell_quote(s: str) -> str: """Quote a string for shell use.""" import shlex return shlex.quote(s) # --------------------------------------------------------------------------- # Reporting # --------------------------------------------------------------------------- def print_results(results: list[TaskResult]) -> None: """Print a formatted results table.""" total = len(results) passed = sum(1 for r in results if r.passed) failed = total - passed print() print("=" * 72) print(" CLAW CODE AGENT — BENCHMARK RESULTS") print("=" * 72) print() print(f" {'Task ID':<30} {'Category':<12} {'Diff':<8} {'Result':<8} {'Time':>6}") print(f" {'─' * 30} {'─' * 12} {'─' * 8} {'─' * 8} {'─' * 6}") for r in results: status = "PASS" if r.passed else "FAIL" icon = " ✅" if r.passed else " ❌" time_str = f"{r.duration_sec:.1f}s" print(f"{icon} {r.task_id:<30} {r.category:<12} {r.difficulty:<8} {status:<8} {time_str:>6}") print() print("─" * 72) print(f" Total: {total} | Passed: {passed} | Failed: {failed} | Score: {passed}/{total} ({100*passed/total:.0f}%)") print("─" * 72) # Breakdown by category categories: dict[str, list[TaskResult]] = {} for r in results: categories.setdefault(r.category, []).append(r) print() print(" Category Breakdown:") for cat, cat_results in sorted(categories.items()): cat_passed = sum(1 for r in cat_results if r.passed) cat_total = len(cat_results) bar = "█" * cat_passed + "░" * (cat_total - cat_passed) print(f" {cat:<14} {bar} {cat_passed}/{cat_total}") # Breakdown by difficulty difficulties: dict[str, list[TaskResult]] = {} for r in results: difficulties.setdefault(r.difficulty, []).append(r) print() print(" Difficulty Breakdown:") for diff in ("easy", "medium", "hard"): if diff in difficulties: diff_results = difficulties[diff] diff_passed = sum(1 for r in diff_results if r.passed) diff_total = len(diff_results) print(f" {diff:<14} {diff_passed}/{diff_total} ({100*diff_passed/diff_total:.0f}%)") total_time = sum(r.duration_sec for r in results) print() print(f" Total time: {total_time:.1f}s") print() def save_results(results: list[TaskResult], output_path: str) -> None: """Save results to JSON.""" data = { "benchmark": "claw-code-agent-local", "timestamp": time.strftime("%Y-%m-%dT%H:%M:%S"), "model": os.environ.get("OPENAI_MODEL", "unknown"), "total": len(results), "passed": sum(1 for r in results if r.passed), "score_pct": round(100 * sum(1 for r in results if r.passed) / len(results), 1) if results else 0, "results": [ { "task_id": r.task_id, "category": r.category, "difficulty": r.difficulty, "passed": r.passed, "duration_sec": round(r.duration_sec, 2), "agent_exit_code": r.agent_exit_code, "verify_exit_code": r.verify_exit_code, "error": r.error, } for r in results ], } with open(output_path, "w") as f: json.dump(data, f, indent=2) print(f" Results saved to {output_path}") # --------------------------------------------------------------------------- # CLI # --------------------------------------------------------------------------- def main() -> None: parser = argparse.ArgumentParser(description="Claw Code Agent local benchmark") parser.add_argument("--task", help="Run a single task by ID") parser.add_argument("--category", help="Run tasks in a category") parser.add_argument("--difficulty", choices=["easy", "medium", "hard"], help="Run tasks by difficulty") parser.add_argument("--list", action="store_true", help="List available tasks") parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output") parser.add_argument("--timeout", type=float, default=300.0, help="Agent timeout per task in seconds (default: 300)") parser.add_argument("--output", "-o", help="Save results to JSON file") args = parser.parse_args() if args.list: print(f"\n {'ID':<30} {'Category':<12} {'Difficulty':<10}") print(f" {'─' * 30} {'─' * 12} {'─' * 10}") for t in TASKS: print(f" {t.id:<30} {t.category:<12} {t.difficulty:<10}") print(f"\n Total: {len(TASKS)} tasks\n") return # Select tasks tasks_to_run: list[BenchmarkTask] = [] if args.task: t = get_task(args.task) if t is None: print(f"Unknown task: {args.task}") print("Use --list to see available tasks") sys.exit(1) tasks_to_run = [t] else: tasks_to_run = list(TASKS) if args.category: tasks_to_run = [t for t in tasks_to_run if t.category == args.category] if args.difficulty: tasks_to_run = [t for t in tasks_to_run if t.difficulty == args.difficulty] if not tasks_to_run: print("No tasks matched the filters.") sys.exit(1) # Find project root project_root = str(Path(__file__).resolve().parent.parent) # Check environment model = os.environ.get("OPENAI_MODEL", "not set") base_url = os.environ.get("OPENAI_BASE_URL", "not set") print() print("=" * 72) print(" CLAW CODE AGENT — LOCAL BENCHMARK") print("=" * 72) print(f" Model: {model}") print(f" Base URL: {base_url}") print(f" Tasks: {len(tasks_to_run)}") print(f" Timeout: {args.timeout}s per task") print("=" * 72) print() # Run tasks results: list[TaskResult] = [] for i, task in enumerate(tasks_to_run, 1): print(f"[{i}/{len(tasks_to_run)}] {task.id} ({task.category}, {task.difficulty})") result = run_task( task, project_root=project_root, agent_timeout=args.timeout, verbose=args.verbose, ) results.append(result) status = "PASS ✅" if result.passed else "FAIL ❌" print(f" -> {status} ({result.duration_sec:.1f}s)") print() # Report print_results(results) if args.output: save_results(results, args.output) if __name__ == "__main__": main()