Guides
Evaluating Coding Agents
Build evaluation systems to measure how well coding agents perform on real-world tasks. Create structured test suites from Level 100 (easy) to Level 400 (expert), benchmark against Terminal Bench, and generate actionable reports.
Why evaluations matter
Coding agents claim impressive capabilities, but claims without measurement are marketing. Rigorous evaluations answer concrete questions: Can this agent refactor a module without breaking tests? Does it handle edge cases in database migrations? How does it perform compared to last week's version?
A well-designed evaluation system provides:
- Objective comparison: Benchmark Autohand against other coding tools using standardized tasks
- Regression detection: Catch capability degradation before it reaches production
- Skill gap identification: Discover which task categories need improvement
- Confidence calibration: Know when to trust agent output versus when to verify manually
Difficulty levels
Evaluations are organized into four difficulty tiers, following a progression from fundamental skills to expert-level challenges.
Level 100: Foundational
Basic tasks that any competent coding agent should handle reliably. Failure at this level indicates fundamental problems.
- Single-file edits with clear instructions
- Simple bug fixes with obvious solutions
- Adding basic functions or methods
- Formatting and linting fixes
- Writing simple unit tests
# Example Level 100 task
name: fix-syntax-error
level: 100
description: Fix the syntax error in the JavaScript file
setup: |
Create a file with a missing closing bracket
verification: |
File parses without errors
Original logic preserved
Level 200: Intermediate
Tasks requiring understanding of code context and multi-step reasoning.
- Multi-file refactoring
- Implementing features from specifications
- Debugging with stack traces
- Writing integration tests
- API endpoint implementation
# Example Level 200 task
name: implement-pagination
level: 200
description: Add pagination to the /api/users endpoint
setup: |
Existing Express app with user listing
Database has 1000+ test records
verification: |
Accepts page and limit query params
Returns correct subset of results
Includes total count in response
Level 300: Advanced
Complex tasks requiring architectural decisions and cross-cutting concerns.
- Performance optimization with profiling
- Security vulnerability remediation
- Database schema migrations
- Implementing caching strategies
- Cross-service integration
# Example Level 300 task
name: optimize-n-plus-one
level: 300
description: Identify and fix N+1 query problem in order listing
setup: |
Django app with Order, LineItem, Product models
Test database with 100 orders, 500 line items
verification: |
Query count reduced from 501 to under 10
Response time improved by at least 80%
All existing tests pass
Level 400: Expert
Challenges that require deep expertise and sophisticated reasoning.
- Concurrent system debugging
- Compiler or interpreter modifications
- Cryptographic implementation review
- Distributed system consistency fixes
- Low-level performance optimization
# Example Level 400 task
name: fix-race-condition
level: 400
description: Fix race condition in distributed lock implementation
setup: |
Redis-based distributed lock with subtle timing bug
Concurrent test harness that triggers the race
verification: |
100,000 concurrent lock attempts with zero conflicts
No deadlocks under high contention
Maintains lock semantics under network partitions
Task categories
Organize evaluations by the type of coding work to identify specific strengths and weaknesses.
| Category | Description | Example tasks |
|---|---|---|
| Bug Fixing | Identifying and resolving defects | Null pointer fix, off-by-one errors, logic bugs |
| Feature Implementation | Building new functionality | API endpoints, UI components, data models |
| Refactoring | Improving code structure | Extract method, rename symbol, reorganize modules |
| Testing | Writing and improving tests | Unit tests, integration tests, test coverage |
| Performance | Optimizing speed and resources | Query optimization, memory leaks, caching |
| Security | Finding and fixing vulnerabilities | SQL injection, XSS, authentication flaws |
| DevOps | Infrastructure and deployment | CI pipelines, Docker configs, monitoring |
| Documentation | Technical writing | API docs, README updates, code comments |
Creating an evaluation suite
Structure your evaluations in a dedicated directory with consistent naming.
Directory structure
evals/
config.yaml # Suite configuration
tasks/
level-100/
fix-syntax-error/
task.yaml # Task definition
setup/ # Initial state files
expected/ # Expected outcomes
add-function/
...
level-200/
implement-pagination/
...
level-300/
...
level-400/
...
scripts/
run-eval.sh # Execution script
generate-report.py # Report generator
Task definition format
Each task uses a YAML definition that specifies setup, execution, and verification:
# evals/tasks/level-200/implement-auth/task.yaml
name: implement-jwt-auth
level: 200
category: feature-implementation
timeout: 300 # seconds
description: |
Implement JWT authentication for the Express API.
Users should be able to login and receive a token.
Protected routes should verify the token.
setup:
files:
- src/server.js
- src/routes/users.js
- package.json
commands:
- npm install
prompt: |
Add JWT authentication to this Express app:
1. Create a POST /auth/login endpoint
2. Return a JWT token on successful login
3. Add middleware to protect /api/* routes
4. Use the JWT_SECRET environment variable
verification:
tests:
- npm test
assertions:
- file_exists: src/middleware/auth.js
- endpoint_returns_200: POST /auth/login
- endpoint_returns_401: GET /api/users (no token)
- endpoint_returns_200: GET /api/users (valid token)
Running evaluations
Execute tasks against Autohand and collect results:
# Run a single task
autohand eval run evals/tasks/level-200/implement-auth
# Run all tasks at a difficulty level
autohand eval run evals/tasks/level-200 --all
# Run the complete suite
autohand eval run evals --all --output results.json
# Run with verbose output for debugging
autohand eval run evals/tasks/level-100 --verbose
Terminal Bench integration
Terminal Bench is a standardized benchmark for measuring AI agent performance in command-line environments. It provides a curated set of terminal-based tasks with varying difficulty.
What Terminal Bench measures
- Task resolution rate: Percentage of tasks completed successfully
- Standard error: Confidence intervals for results
- Cross-agent comparison: Leaderboard rankings against other tools
Running Terminal Bench with Autohand
Use the Harbor Framework to execute Terminal Bench tasks:
# Install the benchmark harness
pip install terminal-bench
# Run the benchmark with Autohand as the agent
tbench run --agent autohand --output results.json
# Run specific difficulty levels
tbench run --agent autohand --difficulty easy
tbench run --agent autohand --difficulty medium
tbench run --agent autohand --difficulty hard
# Submit results to the leaderboard
tbench submit results.json --agent-name "Autohand v1.2.0"
Sample Terminal Bench tasks
The benchmark includes tasks like:
- Building Linux kernels from source
- Configuring web servers with specific requirements
- File encryption and decryption operations
- SSL certificate generation and management
- Data transformation with constraints
- Machine learning model training pipelines
Building a simple evaluation script
For quick benchmarking without external dependencies, create a standalone evaluation script.
Evaluation runner
#!/usr/bin/env python3
"""
Simple evaluation runner for coding agent benchmarks.
Runs tasks, measures success, and generates reports.
"""
import json
import subprocess
import time
from pathlib import Path
from dataclasses import dataclass, asdict
from datetime import datetime
@dataclass
class TaskResult:
name: str
level: int
category: str
passed: bool
duration_seconds: float
error: str | None = None
@dataclass
class EvalReport:
agent: str
timestamp: str
total_tasks: int
passed_tasks: int
pass_rate: float
by_level: dict
by_category: dict
results: list[TaskResult]
def run_task(task_dir: Path, agent_cmd: str) -> TaskResult:
"""Execute a single evaluation task."""
task_config = json.loads((task_dir / "task.json").read_text())
start = time.time()
try:
# Setup the task environment
setup_dir = task_dir / "setup"
work_dir = Path(f"/tmp/eval-{task_config['name']}")
work_dir.mkdir(exist_ok=True)
# Copy setup files
if setup_dir.exists():
subprocess.run(["cp", "-r", str(setup_dir) + "/.", str(work_dir)], check=True)
# Run the agent with the task prompt
result = subprocess.run(
[agent_cmd, "--prompt", task_config["prompt"], "--cwd", str(work_dir)],
capture_output=True,
timeout=task_config.get("timeout", 300),
text=True
)
# Verify the result
passed = verify_task(work_dir, task_config["verification"])
duration = time.time() - start
return TaskResult(
name=task_config["name"],
level=task_config["level"],
category=task_config["category"],
passed=passed,
duration_seconds=duration
)
except subprocess.TimeoutExpired:
return TaskResult(
name=task_config["name"],
level=task_config["level"],
category=task_config["category"],
passed=False,
duration_seconds=task_config.get("timeout", 300),
error="Timeout"
)
except Exception as e:
return TaskResult(
name=task_config["name"],
level=task_config["level"],
category=task_config["category"],
passed=False,
duration_seconds=time.time() - start,
error=str(e)
)
def verify_task(work_dir: Path, verification: dict) -> bool:
"""Check if task was completed successfully."""
# Run test commands
for test_cmd in verification.get("tests", []):
result = subprocess.run(
test_cmd, shell=True, cwd=work_dir,
capture_output=True
)
if result.returncode != 0:
return False
# Check file assertions
for assertion in verification.get("assertions", []):
if "file_exists" in assertion:
if not (work_dir / assertion["file_exists"]).exists():
return False
if "file_contains" in assertion:
path, pattern = assertion["file_contains"]
content = (work_dir / path).read_text()
if pattern not in content:
return False
return True
def generate_report(results: list[TaskResult], agent: str) -> EvalReport:
"""Generate evaluation report from results."""
by_level = {}
by_category = {}
for r in results:
# Aggregate by level
if r.level not in by_level:
by_level[r.level] = {"total": 0, "passed": 0}
by_level[r.level]["total"] += 1
if r.passed:
by_level[r.level]["passed"] += 1
# Aggregate by category
if r.category not in by_category:
by_category[r.category] = {"total": 0, "passed": 0}
by_category[r.category]["total"] += 1
if r.passed:
by_category[r.category]["passed"] += 1
# Calculate pass rates
for level in by_level:
stats = by_level[level]
stats["pass_rate"] = stats["passed"] / stats["total"]
for cat in by_category:
stats = by_category[cat]
stats["pass_rate"] = stats["passed"] / stats["total"]
passed = sum(1 for r in results if r.passed)
return EvalReport(
agent=agent,
timestamp=datetime.now().isoformat(),
total_tasks=len(results),
passed_tasks=passed,
pass_rate=passed / len(results) if results else 0,
by_level=by_level,
by_category=by_category,
results=[asdict(r) for r in results]
)
def main():
import argparse
parser = argparse.ArgumentParser(description="Run coding agent evaluations")
parser.add_argument("--tasks", required=True, help="Path to tasks directory")
parser.add_argument("--agent", default="autohand", help="Agent command")
parser.add_argument("--output", default="eval-report.json", help="Output file")
parser.add_argument("--level", type=int, help="Run only specific level")
args = parser.parse_args()
tasks_dir = Path(args.tasks)
results = []
for level_dir in sorted(tasks_dir.iterdir()):
if not level_dir.is_dir():
continue
if args.level and f"level-{args.level}" not in level_dir.name:
continue
for task_dir in sorted(level_dir.iterdir()):
if not task_dir.is_dir():
continue
print(f"Running: {task_dir.name}")
result = run_task(task_dir, args.agent)
results.append(result)
status = "PASS" if result.passed else "FAIL"
print(f" {status} ({result.duration_seconds:.1f}s)")
report = generate_report(results, args.agent)
Path(args.output).write_text(json.dumps(asdict(report), indent=2))
# Print summary
print(f"\n{'='*50}")
print(f"Evaluation Report: {report.agent}")
print(f"{'='*50}")
print(f"Total: {report.passed_tasks}/{report.total_tasks} ({report.pass_rate:.1%})")
print(f"\nBy Level:")
for level, stats in sorted(report.by_level.items()):
print(f" Level {level}: {stats['passed']}/{stats['total']} ({stats['pass_rate']:.1%})")
print(f"\nBy Category:")
for cat, stats in sorted(report.by_category.items()):
print(f" {cat}: {stats['passed']}/{stats['total']} ({stats['pass_rate']:.1%})")
if __name__ == "__main__":
main()
Running the script
# Run all evaluations
python run-eval.py --tasks ./evals/tasks --agent autohand
# Run only Level 200 tasks
python run-eval.py --tasks ./evals/tasks --level 200
# Output to specific file
python run-eval.py --tasks ./evals/tasks --output autohand-v1.2.json
# Compare two agents
python run-eval.py --tasks ./evals/tasks --agent autohand --output autohand.json
python run-eval.py --tasks ./evals/tasks --agent other-agent --output other.json
python compare-reports.py autohand.json other.json
Report format
Evaluation reports use a structured JSON format for easy analysis and comparison.
{
"agent": "autohand",
"timestamp": "2025-01-15T14:32:00Z",
"total_tasks": 40,
"passed_tasks": 35,
"pass_rate": 0.875,
"by_level": {
"100": {"total": 10, "passed": 10, "pass_rate": 1.0},
"200": {"total": 15, "passed": 13, "pass_rate": 0.867},
"300": {"total": 10, "passed": 9, "pass_rate": 0.9},
"400": {"total": 5, "passed": 3, "pass_rate": 0.6}
},
"by_category": {
"bug-fixing": {"total": 8, "passed": 8, "pass_rate": 1.0},
"feature-implementation": {"total": 12, "passed": 10, "pass_rate": 0.833},
"refactoring": {"total": 8, "passed": 7, "pass_rate": 0.875},
"testing": {"total": 6, "passed": 5, "pass_rate": 0.833},
"performance": {"total": 4, "passed": 3, "pass_rate": 0.75},
"security": {"total": 2, "passed": 2, "pass_rate": 1.0}
},
"results": [
{
"name": "fix-syntax-error",
"level": 100,
"category": "bug-fixing",
"passed": true,
"duration_seconds": 12.5
}
]
}
Generating HTML reports
Convert JSON reports to readable HTML:
# Generate HTML report
autohand eval report results.json --format html --output report.html
# Generate markdown for documentation
autohand eval report results.json --format markdown --output EVAL_RESULTS.md
# Compare multiple runs
autohand eval compare v1.1.json v1.2.json --output comparison.html
CI/CD integration
Run evaluations automatically on every release to catch regressions.
GitHub Actions workflow
# .github/workflows/eval.yml
name: Agent Evaluation
on:
release:
types: [published]
workflow_dispatch:
jobs:
evaluate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Autohand
run: |
curl -fsSL https://autohand.ai/install.sh | bash
autohand --version
- name: Run evaluations
run: |
python evals/scripts/run-eval.py \
--tasks evals/tasks \
--agent autohand \
--output eval-results.json
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
- name: Check pass rate
run: |
PASS_RATE=$(jq '.pass_rate' eval-results.json)
if (( $(echo "$PASS_RATE < 0.85" | bc -l) )); then
echo "Pass rate $PASS_RATE is below threshold 0.85"
exit 1
fi
- name: Upload results
uses: actions/upload-artifact@v4
with:
name: eval-results
path: eval-results.json
- name: Post to Terminal Bench
if: github.event_name == 'release'
run: |
tbench submit eval-results.json \
--agent-name "Autohand ${{ github.ref_name }}"
env:
TBENCH_API_KEY: ${{ secrets.TBENCH_API_KEY }}
Best practices
Task design
- Clear success criteria: Define exactly what constitutes passing
- Isolated environments: Each task runs in a fresh directory
- Realistic complexity: Tasks should reflect actual development work
- Version control setup files: Keep tasks reproducible across runs
Avoiding contamination
Ensure evaluations measure real capability, not memorization:
- Use unique identifiers in task files (Terminal Bench uses a canary GUID)
- Vary surface details while keeping core challenges consistent
- Regularly refresh task content with equivalent alternatives
- Monitor for suspiciously perfect scores on novel tasks
Statistical rigor
- Run multiple trials to establish confidence intervals
- Report standard error alongside pass rates
- Use consistent hardware and network conditions
- Document model versions and configuration
Sample evaluation tasks
Get started with these example tasks across all difficulty levels.
Level 100: Fix typo in error message
{
"name": "fix-typo",
"level": 100,
"category": "bug-fixing",
"timeout": 60,
"prompt": "Fix the typo in the error message in src/utils.js",
"verification": {
"tests": ["grep -q 'successfully' src/utils.js"],
"assertions": [
{"file_contains": ["src/utils.js", "successfully"]}
]
}
}
Level 200: Add input validation
{
"name": "add-validation",
"level": 200,
"category": "feature-implementation",
"timeout": 180,
"prompt": "Add email validation to the user registration endpoint. Return 400 for invalid emails.",
"verification": {
"tests": ["npm test"],
"assertions": [
{"file_exists": "src/validators/email.js"}
]
}
}
Level 300: Implement caching layer
{
"name": "add-caching",
"level": 300,
"category": "performance",
"timeout": 300,
"prompt": "Add Redis caching to the product listing API. Cache should expire after 5 minutes. Invalidate on product updates.",
"verification": {
"tests": ["npm test", "npm run benchmark"],
"assertions": [
{"file_exists": "src/cache/redis.js"},
{"response_time_under_ms": ["GET /api/products", 50]}
]
}
}
Level 400: Fix memory leak
{
"name": "fix-memory-leak",
"level": 400,
"category": "performance",
"timeout": 600,
"prompt": "The WebSocket server has a memory leak. Find and fix it. Memory usage should remain stable under sustained load.",
"verification": {
"tests": ["npm run load-test"],
"assertions": [
{"memory_stable_over_requests": 10000}
]
}
}
Next steps
- Terminal Bench - Run standardized benchmarks
- CI/CD Automation - Integrate evals into your pipeline
- Skills System - Create custom evaluation skills