Part II: Discovery Through Software Engineering and Vibe Coding
Chapter 23: Evaluating AI Coding Agents

23.1 SWE-bench and Task Suites

"They gave me a GitHub issue, a frozen repository, and a test suite I had to pass. I thought it was an interview. Turns out it was a benchmark, and I was the one being graded."

A Coding Agent Who Mistook a Benchmark for a Job Application

Prerequisites

This section opens Chapter 23. You should have completed Chapter 18: AI-Assisted Testing and QA, which introduced pytest, test oracles, and the idea that tests are the empirical science of software engineering. Familiarity with Git operations (checkout, diff, apply) and Docker basics will help you follow the harness construction. The search-as-discovery framework from Chapter 1 provides the conceptual grounding: an agent solving a coding task is searching the space of possible edits for one that satisfies the test oracle.

The Big Picture

Evaluating a coding agent requires a controlled experiment: a precisely defined task, a reproducible environment, and an objective success criterion. SWE-bench (Software Engineering Benchmark, a dataset of real GitHub issues paired with test oracles for evaluating coding agents) pioneered this methodology by mining thousands of real GitHub issues that come with their own test patches. Each task freezes a repository at the commit before a bug fix, provides the issue description as the agent's prompt, and uses the pull request's test additions as the oracle. This section dissects how these tasks are constructed, what makes a good task instance, and how to build your own task suites from any repository.

1. The Anatomy of a Benchmark Task

Without a rigorous way to measure whether a coding agent actually fixes bugs, teams ship tools that look impressive in demos but silently produce broken patches in production. The discipline of benchmark construction turns "does the agent work?" from a subjective impression into a repeatable experiment.

Imagine handing an AI the same GitHub issue a human developer resolved last Tuesday, freezing the repository at the exact commit before the fix, and asking: does the AI's patch make the tests pass? A coding agent benchmark task has four components that make this experiment precise and repeatable, each serving a distinct role in the evaluation. Figure 23.1 illustrates how these four components interact during a single evaluation run.

Benchmark Task Evaluation Flow Repository Snapshot (frozen commit) Issue Description (agent prompt) Test Patch (Oracle) (pass/fail criterion) Gold Patch (Reference) (diagnostic only) Coding Agent produces patch Evaluation Harness applies both patches Pass Fail test oracle verdict
Figure 23.1: The four components of a benchmark task and their roles in the evaluation flow. The repository snapshot and issue description feed the coding agent, whose output patch joins the test oracle in the evaluation harness. The gold patch is used only for post-hoc diagnosis, not for grading.

A benchmark task packages everything needed to test whether a coding agent can fix a real software bug. Without a standardized task format, comparing agents is like comparing test-takers who each took a different exam; the results are meaningless. The task freezes a repository at the moment a bug existed, hands the agent only the bug report, and checks the proposed fix against test cases from the actual human-written solution. Use benchmark tasks for objective, reproducible comparisons between agents or configurations. Use informal manual testing for quick sanity checks on a single prompt.

The Four Components

The repository snapshot is a frozen checkout of the project at a specific commit, typically the commit immediately before the fix was merged. This snapshot defines the "world" the agent operates in: the codebase it can read, the dependencies it can install, and the test suite it can run. Freezing at a specific commit ensures reproducibility; every agent sees exactly the same starting state.

The issue description is the natural-language prompt the agent receives. In SWE-bench, this is the actual GitHub issue body, including any stack traces, error messages, or reproduction steps the original reporter provided. The quality and specificity of this description varies enormously across tasks, which is both a feature (it mirrors real-world variation) and a challenge (it makes task difficulty hard to calibrate).

The test patch contains the test additions from the pull request (PR) that fixed the issue. These tests fail on the base commit (confirming the bug exists) and pass after the fix is applied (confirming the fix is correct). The test patch is the oracle, where a test oracle is an automated criterion that determines whether a given output is correct: an agent's proposed patch is judged correct if and only if it causes the test patch's tests to pass. This is a critical design choice, because it means the evaluation is only as good as the test patch. We will examine the consequences of this choice in Section 23.2. In short: A benchmark is only as honest as its test oracle; everything else is theatre.

Mental Model

Think of a benchmark task like a cooking competition where contestants receive an ingredient list (the issue description), a fully stocked kitchen frozen in time (the repository snapshot), and a panel of taste testers who will only try specific dishes (the test patch). The contestants never see the winning recipe (the gold patch). A contestant passes not by replicating the original chef's recipe exactly, but by producing any dish that satisfies each tester's criteria. If the testers only check whether the soup is salty enough but ignore texture and temperature, a contestant can "win" with a mediocre soup, which is exactly why the quality of the testers (the test patch) determines the quality of the whole competition.

The gold patch is the actual code change from the merged pull request. It is not shown to the agent and is not used for grading. Its role is diagnostic: when an agent fails a task, comparing its attempted patch against the gold patch reveals whether the agent was on the right track or completely lost. The gold patch also helps benchmark maintainers assess task difficulty.

Key Insight

A benchmark task is a controlled experiment with four parts: an initial state (repository snapshot), a stimulus (issue description), a measurement instrument (test patch), and a reference solution (gold patch). The quality of the experiment depends on all four. A vague issue description makes the task ambiguous. A weak test patch allows false positives. A trivial gold patch inflates pass rates. Evaluating the evaluator is as important as evaluating the agent.

2. How SWE-bench Constructs Tasks

The four components define what a task contains. The next question is sourcing: where do all those snapshots, issue descriptions, test patches, and gold patches come from?

The SWE-bench construction pipeline mines GitHub repositories for pull requests that meet specific criteria. Understanding this pipeline reveals which kinds of tasks end up in the benchmark and, more importantly, which kinds are excluded. The pipeline has four stages: repository selection, pull request filtering, task validation, and difficulty stratification. Figure 23.1.1 illustrates the SWE-bench task construction pipeline.

SWE-bench task construction pipeline
Figure 23.1.1: The SWE-bench task construction pipeline, from mining GitHub pull requests through filtering, validation, and assembly into benchmark task instances with four components.

2.1 Repository Selection

SWE-bench draws from well-maintained open-source Python projects with comprehensive test suites: libraries like Django, Flask, scikit-learn, sympy, matplotlib, and requests. These projects share several properties that make them suitable for benchmarking. They have mature Continuous Integration/Continuous Deployment (CI/CD) pipelines, so test infrastructure exists. They follow conventional project layouts, so agents can navigate the codebase. They have extensive issue trackers with detailed bug reports. And they are popular enough that many issues have been resolved by experienced maintainers, producing high-quality gold patches.

This selection introduces a systematic bias: SWE-bench measures agent performance on well-maintained Python libraries with conventional structures. Performance on a disorganized monorepo, a polyglot microservice architecture, or a domain-specific scientific codebase may differ substantially. We return to this gap in Section 23.2.

2.2 Pull Request Filtering

From each repository, the pipeline selects merged pull requests that satisfy three conditions: (1) the PR resolves at least one GitHub issue, (2) the PR includes at least one new test, and (3) the new tests fail on the base commit and pass on the merge commit. The third condition is crucial: it ensures the tests actually verify the fix rather than testing unrelated functionality.

"""
Simplified SWE-bench-style task construction from a Git repository.
Demonstrates the core logic of extracting evaluation tasks from
merged pull requests that include test additions.
"""
import subprocess
import json
from dataclasses import dataclass, field, asdict
from pathlib import Path


@dataclass
class TaskInstance:
    """A single evaluation task extracted from a pull request."""
    instance_id: str
    repo: str
    base_commit: str          # Commit before the fix
    issue_text: str           # GitHub issue body (the agent's prompt)
    test_patch: str           # Diff of added/modified test files
    gold_patch: str           # Diff of the actual fix (not shown to agent)
    test_cmd: str             # Command to run the relevant tests
    created_at: str           # ISO timestamp of the PR merge
    hints: list[str] = field(default_factory=list)  # Optional hints


def extract_task_from_pr(
    repo_path: str,
    merge_commit: str,
    issue_text: str,
    test_files: list[str],
    instance_id: str
) -> TaskInstance | None:
    """Extract a benchmark task from a merged pull request.

    Args:
        repo_path: Path to the local clone of the repository.
        merge_commit: The merge commit SHA of the pull request.
        issue_text: The body of the linked GitHub issue.
        test_files: Paths to test files added or modified in the PR.
        instance_id: Unique identifier for this task.

    Returns:
        A TaskInstance if the PR qualifies, None otherwise.
    """
    # Step 1: Find the base commit (parent of the merge)
    base_commit = subprocess.check_output(
        ["git", "rev-parse", f"{merge_commit}^"],
        cwd=repo_path, text=True
    ).strip()

    # Step 2: Extract the test patch (changes to test files only)
    test_patch = subprocess.check_output(
        ["git", "diff", base_commit, merge_commit, "--"] + test_files,
        cwd=repo_path, text=True
    )
    if not test_patch.strip():
        return None  # No test changes, skip this PR

    # Step 3: Extract the gold patch (changes to non-test files)
    all_files = subprocess.check_output(
        ["git", "diff", "--name-only", base_commit, merge_commit],
        cwd=repo_path, text=True
    ).strip().split("\n")
    src_files = [f for f in all_files if f not in test_files]
    gold_patch = subprocess.check_output(
        ["git", "diff", base_commit, merge_commit, "--"] + src_files,
        cwd=repo_path, text=True
    )

    # Step 4: Validate that tests fail on base and pass on merge
    # (In production, this runs inside Docker for isolation)
    if not _validate_test_behavior(
        repo_path, base_commit, merge_commit, test_files
    ):
        return None

    return TaskInstance(
        instance_id=instance_id,
        repo=Path(repo_path).name,
        base_commit=base_commit,
        issue_text=issue_text,
        test_patch=test_patch,
        gold_patch=gold_patch,
        test_cmd=f"pytest {' '.join(test_files)} -x",
        created_at=subprocess.check_output(
            ["git", "log", "-1", "--format=%aI", merge_commit],
            cwd=repo_path, text=True
        ).strip()
    )


def _validate_test_behavior(
    repo_path: str,
    base_commit: str,
    merge_commit: str,
    test_files: list[str]
) -> bool:
    """Verify tests fail on base commit and pass on merge commit."""
    test_cmd = ["pytest"] + test_files + ["-x", "--tb=no", "-q"]

    # Tests should FAIL on the base commit
    subprocess.run(["git", "checkout", base_commit], cwd=repo_path,
                   capture_output=True)
    base_result = subprocess.run(test_cmd, cwd=repo_path,
                                capture_output=True)

    # Tests should PASS on the merge commit
    subprocess.run(["git", "checkout", merge_commit], cwd=repo_path,
                   capture_output=True)
    merge_result = subprocess.run(test_cmd, cwd=repo_path,
                                 capture_output=True)

    return base_result.returncode != 0 and merge_result.returncode == 0
Extracting a TaskInstance from a merged pull request by separating test-file diffs (the oracle) from source-file diffs (the gold patch), then validating that the tests distinguish the buggy commit from the fixed commit.

Step-Through: Task Construction from a Pull Request

Trace through extract_task_from_pr with a concrete example. Suppose PR #4821 in the requests library fixes a bug where Session.resolve_redirects drops query parameters on 303 redirects.

Input: merge_commit = "a1b2c3d", test_files = ["tests/test_requests.py"], issue_text = "Query params lost on 303 redirect".

Step 1 (find base commit): git rev-parse a1b2c3d^ returns "e4f5g6h". This is the commit where the bug still exists.

Step 2 (extract test patch): git diff e4f5g6h a1b2c3d -- tests/test_requests.py yields a 14-line diff adding test_303_redirect_preserves_query. The diff is non-empty, so we proceed.

Step 3 (extract gold patch): git diff --name-only lists two files: tests/test_requests.py and requests/models.py. After filtering out the test file, src_files = ["requests/models.py"]. The gold patch is a 7-line diff adding a query-string preservation check in resolve_redirects.

Step 4 (validate): checkout e4f5g6h, run pytest tests/test_requests.py::test_303_redirect_preserves_query: exit code 1 (test fails, confirming the bug). Checkout a1b2c3d, run same test: exit code 0 (test passes, confirming the fix). Validation succeeds; return a TaskInstance with all four fields populated.

2.3 Task Validation and Difficulty

Not every filtered PR produces a valid benchmark task. The validation stage checks three conditions: the repository installs from the base commit, the test suite runs without infrastructure failures, and the test patch fails deterministically (not flakily, meaning the test does not alternate between passing and failing across runs due to timing, ordering, or resource sensitivity). The pipeline then stratifies validated tasks by difficulty using several proxies. These include the number of files the gold patch changes, lines added and removed, whether the fix spans multiple modules, and whether the issue description includes a reproduction script.

Checkpoint

So far: a candidate task must come from a PR that resolves an issue, adds new tests, and whose new tests fail before the fix and pass after it; the pipeline then checks that the repository installs cleanly, the test suite runs without infrastructure failures, and the tests fail deterministically rather than flakily.

SWE-bench Lite (300 tasks) and SWE-bench Verified (500 tasks) are curated subsets with human review applied to task selection. Verified in particular uses human annotators to confirm that the issue description is sufficiently clear, the test patch is adequate, and the task is solvable from the information given. This human curation addresses many of the validity concerns we explore in Section 23.2, but it also means these subsets are small enough that statistical comparisons between agents require careful confidence interval estimation.

Practical Example: A Real SWE-bench Task

Consider task django__django-16379 from SWE-bench. The issue reports that FileBasedCache.has_key() returns True for expired cache entries because it only checks file existence, not expiration time. The base commit is Django 4.2-dev at the commit before the fix. The test patch adds three test cases to tests/cache/tests.py that create a cache entry, wait for expiration, and assert that has_key() returns False. The gold patch modifies django/core/cache/backends/filebased.py to check the expiration timestamp before returning True. A successful agent must: (1) read the issue and understand the bug, (2) locate the has_key method in the file-based cache backend, (3) add expiration checking logic, and (4) ensure the test patch passes. The fix is seven lines of Python, but finding the right file and understanding the cache backend's internal timestamp format requires genuine code comprehension.

3. Running an Evaluation Harness

An evaluation harness (the infrastructure that executes benchmark tasks in isolated, reproducible environments and records results) must isolate each task in its own environment (so side effects from one task cannot affect another), enforce resource limits (so an agent cannot run indefinitely), and capture detailed telemetry (so you can diagnose failures). The SWE-bench harness uses Docker containers for isolation, with each task running in a fresh container built from the repository's dependency specification.

The following code implements a minimal evaluation harness. It does not match the full SWE-bench harness in robustness (the production harness handles conda environments, complex build systems, and timeout enforcement), but it captures the essential evaluation loop: set up the environment, apply the agent's patch, run the test oracle, and record the result.

"""
Minimal evaluation harness for coding agent benchmarks.
Runs a single task instance: applies the agent's patch,
executes the test oracle, and records pass/fail with timing.
"""
import subprocess
import tempfile
import shutil
import time
from dataclasses import dataclass
from pathlib import Path


@dataclass
class EvalResult:
    """Result of evaluating an agent's patch on a single task."""
    instance_id: str
    passed: bool
    duration_seconds: float
    test_output: str
    patch_applied: bool
    error: str | None = None


def evaluate_patch(
    task: "TaskInstance",
    agent_patch: str,
    repo_path: str,
    timeout: int = 300
) -> EvalResult:
    """Evaluate an agent's patch against a task's test oracle.

    Args:
        task: The benchmark task instance.
        agent_patch: The agent's proposed patch as a unified diff
            (a standard format showing added and removed lines
            with @@ range headers).
        repo_path: Path to a clean clone of the repository.
        timeout: Maximum seconds for test execution.

    Returns:
        An EvalResult with pass/fail status and diagnostics.
    """
    start = time.time()

    # Work in a temporary copy to preserve the original
    work_dir = tempfile.mkdtemp(prefix="eval_")
    try:
        shutil.copytree(repo_path, f"{work_dir}/repo", dirs_exist_ok=True)
        work_repo = f"{work_dir}/repo"

        # Step 1: Checkout the base commit
        subprocess.run(
            ["git", "checkout", task.base_commit],
            cwd=work_repo, capture_output=True, check=True
        )

        # Step 2: Apply the test patch (the oracle)
        test_apply = subprocess.run(
            ["git", "apply", "--allow-empty"],
            input=task.test_patch, cwd=work_repo,
            capture_output=True, text=True
        )
        if test_apply.returncode != 0:
            return EvalResult(
                instance_id=task.instance_id,
                passed=False,
                duration_seconds=time.time() - start,
                test_output="",
                patch_applied=False,
                error=f"Test patch failed to apply: {test_apply.stderr}"
            )

        # Step 3: Apply the agent's patch
        agent_apply = subprocess.run(
            ["git", "apply", "--allow-empty"],
            input=agent_patch, cwd=work_repo,
            capture_output=True, text=True
        )
        patch_applied = agent_apply.returncode == 0

        if not patch_applied:
            return EvalResult(
                instance_id=task.instance_id,
                passed=False,
                duration_seconds=time.time() - start,
                test_output="",
                patch_applied=False,
                error=f"Agent patch failed to apply: {agent_apply.stderr}"
            )

        # Step 4: Run the test oracle
        test_result = subprocess.run(
            task.test_cmd.split(),
            cwd=work_repo, capture_output=True, text=True,
            timeout=timeout
        )

        return EvalResult(
            instance_id=task.instance_id,
            passed=test_result.returncode == 0,
            duration_seconds=time.time() - start,
            test_output=test_result.stdout + test_result.stderr,
            patch_applied=True
        )

    except subprocess.TimeoutExpired:
        return EvalResult(
            instance_id=task.instance_id,
            passed=False,
            duration_seconds=timeout,
            test_output="",
            patch_applied=True,
            error="Test execution timed out"
        )
    finally:
        shutil.rmtree(work_dir, ignore_errors=True)
Single-task evaluation loop: checkout the base commit, layer on the test oracle patch and the agent's proposed patch, then run pytest to produce a pass/fail EvalResult with timing and error diagnostics.

4. Repo-Level Task Suites

SWE-bench draws tasks from multiple repositories, which tests an agent's ability to generalize across codebases. But a complementary evaluation strategy is the repo-level task suite: a collection of tasks drawn from a single repository, designed to measure an agent's performance on a specific codebase. This approach is particularly valuable for teams evaluating whether to adopt a coding agent for their own project, because it directly measures the metric that matters: "how well does this agent handle our code?"

A good repo-level suite samples tasks across several dimensions:

The sampling strategy matters because agents have uneven capability profiles. An agent might excel at single-file bug fixes with clear reproduction steps but fail completely at cross-module refactors described only in natural language. A suite that over-represents one task type produces misleading aggregate scores.

Real-World Application: Google's Internal Coding Agent Evaluation

Google's Gemini-powered coding agent uses a repo-level task suite drawn from Google's own monorepo to evaluate performance before each deployment. Rather than relying on public benchmarks, the team constructs tasks from recently resolved internal bug reports across multiple languages (C++, Java, Python, Go), stratified by component and severity. This internal suite reportedly revealed that the agent's resolve rate on Google's codebase was roughly 15 percentage points lower than its SWE-bench score, likely because Google's monorepo uses custom build tooling and style conventions that public benchmarks never exercise.

Research Frontier

Recent work extends the SWE-bench paradigm well beyond Python bug fixing. SWE-bench Multimodal (2024) adds tasks involving visual outputs where the test oracle includes image comparison. SWE-bench for JavaScript, Java, and Rust are under active development. CrossCodeBench (Zhang et al., 2025) constructs tasks that require changes across multiple programming languages in the same repository. A significant methodological advance is SWE-bench+ (Aleithan et al., 2025), which audits the original SWE-bench tasks and finds that many test patches are too weak: patches that are clearly incorrect (for example, hardcoding a return value) can still pass the oracle tests. SWE-bench+ strengthens the test suites with additional assertions, reducing false-positive resolve rates for several leading agents by 5 to 15 percentage points. This finding underscores that benchmark quality depends critically on the test oracle, not just on task selection. For scientific discovery applications, SciCode (Tian et al., 2024) evaluates agents on scientific computing tasks drawn from computational physics, chemistry, and biology codebases, where correct patches require domain knowledge beyond software engineering. The methodology from this section applies directly to constructing such domain-specific evaluation suites. As of mid-2025, several leading coding agents report resolve rates above 50% on SWE-bench Verified, up from single-digit percentages when the benchmark launched in 2023; this rapid score growth has intensified interest in harder successors and domain-specific suites.

Real-World Application: Google's Internal Coding Agent Evaluation
Real-World Application: Google's Internal Coding Agent Evaluation

5. Human Review Calibration

The task suites above rely entirely on automated test oracles, but a passing test suite cannot tell you whether the task itself was fair or whether the test patch was strong enough to catch a subtly wrong fix.

Automated evaluation (did the tests pass?) is necessary but not sufficient for understanding agent capabilities. Human review adds two dimensions that automated metrics miss: patch quality (is the fix elegant, maintainable, and correct beyond the test cases?) and task calibration (is the task actually solvable from the information given?).

SWE-bench Verified uses a structured human review protocol. For each task, a human reviewer answers three questions: (1) Is the issue description clear enough that a competent developer could solve the task without additional context? (2) Does the test patch adequately verify the fix, or could an incorrect patch pass the tests? (3) Is the gold patch the only reasonable fix, or are there multiple valid approaches? Tasks where the answer to (1) is "no" are excluded as ambiguous. Tasks where the answer to (2) is "could pass incorrectly" are flagged for test enhancement.

For internal benchmarks, a lighter-weight calibration works: have a developer who did not create the task solve it from the issue description alone, without seeing the gold patch. If the developer cannot solve it, the description needs improvement. If the developer finds an alternative fix that also passes, the test patch needs strengthening. This "red team" step, analogous to adversarial testing (Chapter 18), catches ambiguous tasks before they contaminate evaluation results.

"""
Human review calibration protocol for benchmark tasks.
Tracks reviewer assessments and flags tasks that need
revision before inclusion in the benchmark.
"""
from dataclasses import dataclass
from enum import Enum


class Clarity(Enum):
    CLEAR = "clear"              # Solvable from description alone
    AMBIGUOUS = "ambiguous"      # Requires assumptions not in description
    INSUFFICIENT = "insufficient"  # Missing critical information


class TestAdequacy(Enum):
    ADEQUATE = "adequate"        # Only correct patches pass
    WEAK = "weak"                # Incorrect patches could pass
    FLAKY = "flaky"              # Tests pass/fail non-deterministically


@dataclass
class TaskReview:
    """Human review of a benchmark task's quality."""
    instance_id: str
    reviewer: str
    clarity: Clarity
    test_adequacy: TestAdequacy
    alternative_fixes: int      # Number of valid fixes beyond the gold
    estimated_difficulty: int    # 1-5 scale (1=trivial, 5=expert)
    notes: str = ""

    @property
    def is_valid(self) -> bool:
        """A task is valid if it's clear and has adequate tests."""
        return (
            self.clarity == Clarity.CLEAR
            and self.test_adequacy == TestAdequacy.ADEQUATE
        )


def calibrate_suite(
    tasks: list["TaskInstance"],
    reviews: list[TaskReview]
) -> dict:
    """Compute calibration statistics for a task suite.

    Returns:
        Dictionary with validity rate, difficulty distribution,
        and flagged tasks needing revision.
    """
    review_map = {r.instance_id: r for r in reviews}
    valid = [t for t in tasks if review_map.get(t.instance_id,
             TaskReview(t.instance_id, "", Clarity.INSUFFICIENT,
                       TestAdequacy.WEAK, 0, 3)).is_valid]
    flagged = [t.instance_id for t in tasks
               if t.instance_id in review_map
               and not review_map[t.instance_id].is_valid]

    difficulties = [review_map[t.instance_id].estimated_difficulty
                    for t in tasks if t.instance_id in review_map]

    return {
        "total_tasks": len(tasks),
        "valid_tasks": len(valid),
        "validity_rate": len(valid) / len(tasks) if tasks else 0,
        "flagged_for_revision": flagged,
        "difficulty_distribution": {
            d: difficulties.count(d) for d in range(1, 6)
        },
        "mean_difficulty": sum(difficulties) / len(difficulties)
            if difficulties else 0
    }
Task quality gate using Clarity and TestAdequacy enums: calibrate_suite aggregates human reviewer judgments to compute validity rates, flag weak tasks for revision, and produce a difficulty histogram before any agent evaluation begins.

6. Metrics Beyond Pass Rate

The headline metric for SWE-bench is the resolve rate, where resolve rate is the fraction of tasks where the agent's patch causes all oracle tests to pass. This single number dominates leaderboard discussions, but it obscures critical operational details. Two agents with identical 40% resolve rates may differ dramatically in cost, latency, reliability, and failure modes.

Common Misconception

A frequent misreading is that a high resolve rate on SWE-bench means an agent is ready to use in production on your codebase. This confuses benchmark performance with deployment readiness. SWE-bench tasks are drawn from well-maintained Python libraries with clear issue descriptions and strong test suites; your production environment likely has messier code, vaguer requirements, weaker test coverage, and languages or frameworks not represented in the benchmark. A 60% resolve rate on SWE-bench Verified tells you the agent has strong code comprehension and patching skills, but it does not predict how that agent will perform on your proprietary Java monolith or your undocumented data pipeline.

A complete evaluation captures at least five metrics:

The cost and latency metrics connect directly to the operational monitoring in Chapter 22: MLOps, LLMOps, and AgentOps. In production, an agent that costs \$2 per task and takes 3 minutes is operationally very different from one that costs \$0.10 and takes 30 seconds, even if both achieve the same resolve rate. The right choice depends on your deployment context: a CI/CD gate needs low latency; a batch overnight repair system can tolerate higher latency for better accuracy.

"""
Multi-dimensional evaluation metrics for coding agent benchmarks.
Captures resolve rate, cost, latency, and consistency
across a suite of evaluation tasks.
"""
import statistics
from dataclasses import dataclass


@dataclass
class TaskMetrics:
    """Metrics for a single task evaluation."""
    instance_id: str
    resolved: bool
    patch_applied: bool
    cost_usd: float
    latency_seconds: float
    input_tokens: int
    output_tokens: int
    num_tool_calls: int
    num_retries: int


@dataclass
class SuiteMetrics:
    """Aggregate metrics across an entire evaluation suite."""
    agent_name: str
    total_tasks: int
    resolved: int
    patches_applied: int
    total_cost_usd: float
    total_latency_seconds: float

    @property
    def resolve_rate(self) -> float:
        return self.resolved / self.total_tasks if self.total_tasks else 0

    @property
    def apply_rate(self) -> float:
        return self.patches_applied / self.total_tasks if self.total_tasks else 0

    @property
    def cost_per_task(self) -> float:
        return self.total_cost_usd / self.total_tasks if self.total_tasks else 0

    @property
    def mean_latency(self) -> float:
        return self.total_latency_seconds / self.total_tasks if self.total_tasks else 0


def compute_suite_metrics(
    agent_name: str,
    task_metrics: list[TaskMetrics]
) -> SuiteMetrics:
    """Aggregate per-task metrics into suite-level statistics."""
    return SuiteMetrics(
        agent_name=agent_name,
        total_tasks=len(task_metrics),
        resolved=sum(1 for t in task_metrics if t.resolved),
        patches_applied=sum(1 for t in task_metrics if t.patch_applied),
        total_cost_usd=sum(t.cost_usd for t in task_metrics),
        total_latency_seconds=sum(t.latency_seconds for t in task_metrics),
    )


def compute_consistency(
    runs: list[list[TaskMetrics]]
) -> dict[str, float]:
    """Measure outcome consistency across repeated runs.

    Args:
        runs: Multiple runs of the same suite, each a list of TaskMetrics.
              All runs must cover the same tasks in the same order.

    Returns:
        Per-task consistency scores (fraction of runs with same outcome).
    """
    if not runs or not runs[0]:
        return {}

    consistency = {}
    for task_idx in range(len(runs[0])):
        instance_id = runs[0][task_idx].instance_id
        outcomes = [run[task_idx].resolved for run in runs]
        # Consistency = fraction of runs agreeing with the majority
        majority = sum(outcomes) > len(outcomes) / 2
        agreement = sum(1 for o in outcomes if o == majority)
        consistency[instance_id] = agreement / len(outcomes)

    return consistency
Suite-wide metrics aggregation with compute_suite_metrics and cross-run consistency measurement via compute_consistency, which scores each task by the fraction of repeated runs that agree on pass or fail.
Library Shortcut: SWE-bench Harness

The from-scratch harness above is instructive but not what you would use in practice. The official swebench package (installable via pip install swebench) provides the complete evaluation infrastructure in a few commands (as of 2025, the CLI interface and Docker workflow have evolved; consult the latest documentation at the princeton-nlp/SWE-bench GitHub repository for current command syntax):

# Download task instances
python -m swebench.harness.prepare --dataset_name princeton-nlp/SWE-bench_Verified

# Run evaluation on agent predictions
python -m swebench.harness.run_evaluation \
    --predictions_path ./predictions.json \
    --swe_bench_tasks princeton-nlp/SWE-bench_Verified \
    --log_dir ./eval_logs \
    --testbed /tmp/testbed
Official SWE-bench CLI commands for downloading the Verified task set and running batch evaluation against a JSON file of agent-generated patches.

The official harness handles Docker container management, conda (a package and environment manager that creates isolated Python installations with pinned dependency versions) environment setup, dependency installation, and parallel execution across tasks. It reduces the evaluation code from hundreds of lines to two shell commands. Internally, it implements the same four-step logic (checkout, apply test patch, apply agent patch, run tests) with production-grade error handling and timeout enforcement.

Fun Note

The original SWE-bench paper reports that the initial dataset contained over 10,000 candidate tasks extracted from 12 repositories. After filtering for test validity, environment reproducibility, and deduplication, only 2,294 tasks survived. That is a 78% attrition rate, which tells you something important about the state of test infrastructure in open-source projects: even in well-maintained Python libraries, a large share of bug-fixing PRs either lack adequate tests, have tests that do not cleanly distinguish the buggy state from the fixed state, or encounter environment reproducibility issues that prevent automated validation.

7. From Public Benchmarks to Internal Evaluation

Public benchmarks like SWE-bench answer a general question: which agent architectures are most capable at resolving GitHub issues across well-known Python libraries? But the practitioner's question is more specific: which agent will work best on my codebase, with my coding conventions, my test infrastructure, and my deployment constraints?

The gap between these questions motivates building internal benchmarks. In Section 23.3, we construct a complete five-issue benchmark from your own repository. The task construction pipeline from this section provides the methodology: extract tasks from your own merged PRs, validate test behavior, calibrate with human review, and run evaluations with multi-dimensional metrics. The statistical tools in Section 23.3 (bootstrap confidence intervals and Wilcoxon signed-rank tests, statistical methods for estimating uncertainty and comparing paired outcomes) then let you make rigorous comparisons between agent workflows rather than relying on single-number resolve rates.

Before building that internal benchmark, however, we need to understand what can go wrong with evaluation. Section 23.2 examines the validity threats that plague both public and internal benchmarks: data contamination, test leakage, metric gaming, and the gap between benchmark scores and production utility.

Try It: Build a Mini Benchmark from Any Git Repository

Pick any open-source Python project with merged bug-fix PRs and construct two benchmark tasks by hand. This exercise takes about 30 minutes and requires only Git, Python, and pytest.

  1. Clone and select PRs. Clone a project (for example, git clone https://github.com/psf/requests). Run git log --oneline --grep="fix" --merges to find merged PRs whose messages mention a fix. Pick two PRs that modified both source files and test files.
  2. Extract the four components. For each PR, identify its merge commit hash. Use git diff <merge>^..<merge> -- tests/ to extract the test patch, and git diff <merge>^..<merge> -- src/ (or the appropriate source directory) to extract the gold patch. Find the linked issue on GitHub and copy its body as the issue description. Record the parent commit (git rev-parse <merge>^) as the base commit.
  3. Validate test behavior. Check out the base commit, apply the test patch with git apply, and run the tests. Confirm they fail. Then apply the gold patch and run the tests again. Confirm they pass. If either check fails, the PR does not qualify; pick another.
  4. Write the task as JSON. Save a JSON file with fields for instance_id, repo, base_commit, issue_text, test_patch, gold_patch, and test_cmd. This is the same format the SWE-bench harness uses.
  5. Test with a trivial "agent." Write a script that loads your JSON task, checks out the base commit, applies the test patch, applies the gold patch as if it were the agent's output, and runs pytest. If the tests pass, your task construction is correct. If they fail, debug the patch extraction from step 2.

Exercise 23.1.1

You extract a benchmark task from a pull request and find that the test patch adds two assertions: one checks that process_data([]) returns an empty list, and another checks that process_data([1, 2, 3]) returns [2, 4, 6]. The gold patch fixes a bug in process_data that crashed on empty input. An agent submits a patch that hardcodes if not data: return [] at the top of the function but leaves the original doubling logic (which already worked for non-empty input) untouched. Does this agent patch pass the oracle? Is the patch a correct fix? What additional test assertion would distinguish a correct fix from this shortcut?

Hint

Think about inputs the test patch does not cover. What happens with process_data(None) or process_data("abc")? The hardcoded empty-list check passes both existing assertions but may not handle the root cause (for example, a missing type check or a division-by-zero on len(data)). An assertion testing a boundary case like process_data([0]) returning [0] would not help here, but one testing the specific crash trigger (say, process_data(None) raising TypeError) would expose whether the agent addressed the actual bug or just papered over the symptom.

Lab: Measuring Oracle Weakness in a Real Benchmark

Goal: Quantify how many SWE-bench tasks can be "solved" by trivially incorrect patches, revealing the gap between passing the oracle and producing a correct fix.

Tools needed: Python 3.10+, the datasets library (pip install datasets), Git, and Docker (for the SWE-bench harness).

Procedure (20-30 minutes): Load the SWE-bench Lite dataset from Hugging Face (datasets.load_dataset("princeton-nlp/SWE-bench_Lite")). Select five tasks at random. For each task, read the gold patch and construct a "degenerate" patch: one that hardcodes the expected output for the specific inputs tested by the test patch (for example, adding an early-return branch that matches the test's exact arguments). Run each degenerate patch through the evaluation harness. Record how many of the five pass the oracle despite being clearly incorrect.

What to vary: Try different degeneracy strategies: hardcoded return values, input-specific branches, and monkey-patching (dynamically replacing a function or method at runtime to alter its behavior) the test expectations themselves. Compare against the SWE-bench+ strengthened test suites if available.

What to observe: Track the fraction of tasks where degenerate patches pass (the "false positive rate" of the oracle). Note which tasks have strong enough test patches to reject all degenerate patches and examine what makes those test patches more robust (multiple assertions, diverse inputs, edge-case coverage).

Exercises

  1. Conceptual: A benchmark task has a clear issue description and a gold patch that changes one line. However, the test patch only adds one assertion that checks the function's return value for one input. What validity risk does this create? How would you strengthen the test patch? (Hint: think about what other patches might also pass this single assertion.)
  2. Coding: Using the extract_task_from_pr function as a starting point, extend it to compute two difficulty proxies: (a) the number of files changed in the gold patch, and (b) the ratio of added lines to total changed lines. Add these as fields to the TaskInstance dataclass. Test your extension on a small Git repository with at least three merged PRs.
  3. Analysis: Pick any open-source Python project you contribute to. Identify five merged pull requests that fixed bugs and included test additions. For each, evaluate whether it would qualify as a valid SWE-bench task using the three filtering criteria from Section 2.2. How many of the five survive? What does this tell you about the relationship between your project's PR practices and benchmark suitability?