Add a Benchmark
A benchmark owns the dataset contract, task preparation, evaluation logic, and aggregate metrics. It should not own model-provider calls, agent loops, sandbox lifecycle, or provider-specific image selection. Add a benchmark when you are introducing a new task family or a new scoring protocol. If the change only adapts an existing benchmark to a new sandbox image or workspace layout, add a recipe instead.Benchmark File
Create a module undersrc/agentcompass/benchmarks/. Most benchmarks define a RuntimeBenchmarkConfig subclass, an optional BenchmarkPlan, and a BaseBenchmark subclass.
Benchmark Responsibilities
| Hook | Purpose |
|---|---|
id | Stable CLI and config id. It is also used by recipes, analyzers, result paths, and docs. |
description | User-facing description exported by agentcompass list dump. Benchmarks and harnesses must define a non-empty description. |
config_class | Parses --benchmark-params and config defaults. Extend RuntimeBenchmarkConfig when the benchmark needs dataset paths, versions, splits, or sampling controls. |
load_tasks() | Loads raw tasks and returns TaskSpec objects. Keep this deterministic and fail early on missing datasets. |
select_tasks() | Optional override for custom sampling. The base class already supports sample_ids. |
build_plan() | Produces benchmark-side execution state for one task. Recipes may rewrite this plan later. |
prepare_task() | Converts a TaskSpec into the benchmark-to-harness PreparedTask contract. Upload files or write prompts here when needed. |
evaluate() | Scores a harness RunResult and returns the normalized final RunResult. |
aggregate_metrics() | Optional aggregate metric override. The default path aggregates binary correctness. |
TaskSpec.metadata for source records, image names, docker metadata, expected files, and benchmark-specific IDs. Use PreparedTask.input and PreparedTask.output for the actual harness contract.
Register And Verify
Export the benchmark insrc/agentcompass/benchmarks/__init__.py so registry import side effects can discover it:
Add a Harness
A harness owns the agent or model execution loop. It consumesPreparedTask, RunRequest.model, and an EnvironmentSession, then returns a normalized RunResult. It should not load datasets, score benchmark correctness, or hard-code provider-specific sandbox images.
Add a harness when you are integrating a new agent framework, coding assistant, GUI agent, direct model-call path, or tool-use strategy.
Harness File
Create a module undersrc/agentcompass/harnesses/. A typical harness defines a user-facing config, a runtime plan, and a BaseHarness subclass.
Harness Responsibilities
| Hook | Purpose |
|---|---|
id / description | Stable CLI id and exported description. Include the agent/framework website when available. |
supports() | Validates the environment and model protocol combination before execution starts. Raise a clear error for unsupported protocols. |
config_class | Parses --harness-params and config defaults. Keep public knobs here, not in ad hoc metadata. |
plan_class | Runtime execution state derived from config. Recipes may inspect or rewrite the final execution plan. |
start_session() | Initializes clients, agent config files, persistent processes, or per-run state. |
run_task() | Executes exactly one PreparedTask and returns RunResult. Preserve trajectory, artifacts, usage, and errors whenever possible. |
close_session() | Releases external processes, clients, temporary sessions, or remote framework state. |
prepared.input.workspace, prepared.input.files, prepared.input.media, prepared.input.tools, and prepared.input.messages instead of reaching back into benchmark internals. A harness should be reusable across benchmarks that emit the same prepared-task contract.
Register And Verify
Export the harness insrc/agentcompass/harnesses/__init__.py:
Add an Environment
An environment provider owns execution primitives: commands, file transfer, text IO, directory sync, endpoint discovery, and teardown. It should not know benchmark scoring rules or agent behavior. Add an environment when AgentCompass needs to run the same benchmark/harness matrix on a new local process, container runtime, cloud sandbox, or remote execution provider.Environment File
Create a module undersrc/agentcompass/environments/. Each provider usually has a session class, config dataclass, and BaseEnvironment implementation.
Environment Responsibilities
| Surface | Purpose |
|---|---|
EnvironmentSession.exec() | Implements subprocess-like execution. Respect shell, cwd, env, timeout, detach, and return ExecResult with stdout, stderr, returncode, and timeout status. |
| File primitives | Implement upload, download, write_text, read_text, upload_dir, and download_dir so benchmark and harness code can stay provider-neutral. |
endpoint() | Returns a reachable URL when the provider exposes one, otherwise None. |
RuntimeEnvironmentConfig | Documents public provider params such as image, resources, region, credentials, or workspace root. |
BaseEnvironment.open() | Builds the provider session from the recipe-adjusted ExecutionPlan. Always use plan.environment.params, not only the original request. |
BaseEnvironment.close() | Terminates or detaches remote resources deterministically. It should tolerate partially initialized sessions. |
config/defaults.yaml when they are useful for normal users. Benchmark-specific image, snapshot, workspace, or resource mapping belongs in recipes.
Register And Verify
Export the environment fromsrc/agentcompass/environments/__init__.py. Guard optional SDK imports if the dependency is not always installed:
Add a Recipe
A recipe is a compatibility layer between one benchmark family and one environment provider. It rewrites the per-taskExecutionPlan before the sandbox starts, usually to select images, workspaces, resource requests, snapshots, environment variables, or evaluation layout.
Add a recipe when task metadata already contains provider-relevant information and users should not have to pass it manually on the CLI.
Recipe File
Create a module undersrc/agentcompass/recipes/<benchmark_family>/. Keep one provider recipe per file when the provider behavior is meaningfully different.
Recipe Responsibilities
| Rule | Why it matters |
|---|---|
| Match narrowly | Check benchmark id, environment id, and required task metadata. Avoid recipes that fire for unrelated tasks. |
| Preserve user overrides | Use setdefault() or explicit compatibility checks when users pass image, resources, workspace, or credentials themselves. |
| Fail before sandbox startup | If a heavy benchmark requires a task image and none is available, raise a clear ValueError in apply(). |
| Rewrite plans, not configs | Mutate a copied ExecutionPlan; do not mutate the original RunRequest. |
| Keep execution out | Recipes should not run commands, create sessions, evaluate results, or call model APIs. |
| Share helpers by benchmark family | Put shared layout helpers in recipes/<benchmark_family>/common.py when multiple providers need the same workspace mapping. |
execution.enabled_recipes is empty. Users can restrict recipe selection with --enabled-recipes.
Register And Verify
Export the recipe from the package initializer:Recipe matched with the new recipe id:
Add an Analyzer
Analyzers run after a task produces aRunResult. They inspect trajectories, metrics, errors, latency, model output, or tool calls, then attach an AnalysisResult under analysis_result.<AnalyzerId> in each details JSON. Aggregate fields are rendered into analysis_summary.md and analysis_summary.json.
Add an analyzer when the logic is post-execution diagnosis or statistics. Do not put analyzer logic in a benchmark or harness if it can be rerun later with agentcompass analysis.
Analyzer File
Create a new file undersrc/agentcompass/analyzers/basic/, or create a dedicated sub-package for a larger analyzer family. Each analyzer must subclass BaseAnalyzer, register with @ANALYZERS.register(), and implement analysis().
Analyzer Attributes
| Attribute | Purpose |
|---|---|
id | Stable analyzer id. Used by the registry, CLI selection, details JSON, and summaries. |
description | User-facing description exported by agentcompass list dump. |
category | Summary grouping. Use AnalyzerCategory.ERROR, EFFICIENCY, BEHAVIOR, ABILITY, BASIC_BADCASE, or ENV_FRAMEWORK_ERROR. |
datasets | Benchmark ids this analyzer supports. Empty list means all benchmarks. |
data_requirements | JSONPath requirements checked before analysis. Missing fields skip the sample. |
conf | Analyzer config merged from execution.analysis_params.<AnalyzerId>. Common keys include only_incorrect and threshold. |
distribution_fields | Details fields to aggregate across tasks. Values are numeric_stats or value_counts. |
base_analyzer / priority | Override mechanism for benchmark-specific analyzer variants. Higher priority wins within the same family. |
AnalysisResult.is_badcase should be True for detected badcases, False for explicitly clean samples, and None for statistics-only analyzers that should not affect badcase ratios.
Aggregate Fields
Declare every cross-task summary field indistribution_fields:
| Method | Use for | Summary output |
|---|---|---|
numeric_stats | Numeric values or numeric lists. | count, min, mean, percentiles, and max. |
value_counts | Strings or string lists. | frequency counts and ratios. |
AnalysisResult.details can be aggregated.
Register And Verify
Export the analyzer from package initializers so registry import side effects can see it:Developer Notes
- Keep analyzer code read-only with respect to task execution. It should inspect persisted results, not mutate benchmark behavior.
- Use
datasetsfor benchmark-specific analyzers instead of branching on unrelated task metadata. - Use
base_analyzerandprioritywhen a benchmark-specific analyzer should override a generic analyzer. - Return a descriptive
errorinstead of raising when a sample cannot be analyzed because optional trajectory fields are missing.
