Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions apps/cli/src/commands/eval/commands/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ function definitionsWithEvalTargetRefs(
function buildBundleExecution(options: {
readonly targetNames: readonly string[];
readonly targetRefs?: readonly EvalTargetRef[];
readonly workers?: number;
readonly cache?: boolean;
readonly cachePath?: string;
readonly budgetUsd?: number;
Expand All @@ -121,9 +120,6 @@ function buildBundleExecution(options: {
targets: options.targetNames.map((name) => serializeTargetRef(name)),
};

if (options.workers !== undefined) {
execution.workers = options.workers;
}
if (options.cache !== undefined) {
execution.cache = options.cache;
}
Expand Down Expand Up @@ -224,7 +220,6 @@ export const evalBundleCommand = command({
execution: buildBundleExecution({
targetNames,
targetRefs: suite.targetRefs,
workers: suite.workers,
cache: suite.cacheConfig?.enabled,
cachePath: suite.cacheConfig?.cachePath,
budgetUsd: suite.budgetUsd,
Expand Down
17 changes: 3 additions & 14 deletions apps/cli/src/commands/eval/run-eval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ function normalizeOptions(
yamlExecution?: ExecutionDefaults,
): NormalizedOptions {
const cliWorkers = normalizeOptionalNumber(rawOptions.workers);
const configWorkers = config?.execution?.workers;
const configWorkers = config?.execution?.workers ?? yamlExecution?.workers;
const workers = cliWorkers ?? configWorkers ?? 0;

const cliOutputDir = normalizeString(rawOptions.output);
Expand Down Expand Up @@ -759,7 +759,6 @@ function applyExperimentOptions(
...options,
target: options.target,
agentTimeoutSeconds: options.agentTimeoutSeconds ?? experiment.timeoutSeconds,
workers: options.workers ?? experiment.workers,
workspaceMode: options.workspaceMode,
workspacePath: options.workspacePath,
budgetUsd: options.budgetUsd ?? experiment.budgetUsd,
Expand Down Expand Up @@ -1110,7 +1109,6 @@ async function prepareFileMetadata(params: {
readonly selections: readonly { selection: TargetSelection; inlineTargetLabel: string }[];
readonly trialsConfig?: TrialsConfig;
readonly suiteTargets?: readonly string[];
readonly yamlWorkers?: number;
readonly yamlCache?: boolean;
readonly yamlCachePath?: string;
readonly budgetUsd?: number;
Expand Down Expand Up @@ -1160,7 +1158,6 @@ async function prepareFileMetadata(params: {
selections: [],
trialsConfig: effectiveOptions.experimentTrialsConfig,
suiteTargets,
yamlWorkers: suite.workers,
yamlCache: suite.cacheConfig?.enabled,
yamlCachePath: suite.cacheConfig?.cachePath,
budgetUsd: defaultBudgetUsd,
Expand Down Expand Up @@ -1318,7 +1315,6 @@ async function prepareFileMetadata(params: {
selections,
trialsConfig: effectiveOptions.experimentTrialsConfig,
suiteTargets,
yamlWorkers: suite.workers,
yamlCache: suite.cacheConfig?.enabled,
yamlCachePath: suite.cacheConfig?.cachePath,
budgetUsd: defaultBudgetUsd,
Expand Down Expand Up @@ -1360,7 +1356,6 @@ async function runSingleEvalFile(params: {
readonly cache?: EvaluationCache;
readonly evaluationRunner: typeof defaultRunEvaluation;
readonly workersOverride?: number;
readonly yamlWorkers?: number;
readonly progressReporter: ProgressReporter;
readonly seenTestCases: Set<string>;
readonly displayIdTracker: { getOrAssign(testCaseKey: string): number };
Expand Down Expand Up @@ -1388,7 +1383,6 @@ async function runSingleEvalFile(params: {
cache,
evaluationRunner,
workersOverride,
yamlWorkers,
progressReporter,
seenTestCases,
displayIdTracker,
Expand Down Expand Up @@ -1439,13 +1433,10 @@ async function runSingleEvalFile(params: {
const agentTimeoutMs =
agentTimeoutSeconds != null ? Math.max(0, agentTimeoutSeconds) * 1000 : undefined;

// Resolve workers: CLI flag > eval YAML execution.workers > target setting > default
// Resolve workers: CLI/config > target setting > default
const workerPreference = workersOverride ?? options.workers;
let resolvedWorkers =
workerPreference ??
yamlWorkers ??
resolvedTargetSelection.resolvedTarget.workers ??
DEFAULT_WORKERS;
workerPreference ?? resolvedTargetSelection.resolvedTarget.workers ?? DEFAULT_WORKERS;
if (resolvedWorkers < 1 || resolvedWorkers > 50) {
throw new Error(`Workers must be between 1 and 50, got: ${resolvedWorkers}`);
}
Expand Down Expand Up @@ -1931,7 +1922,6 @@ export async function runEvalCommand(
}[];
readonly trialsConfig?: TrialsConfig;
readonly suiteTargets?: readonly string[];
readonly yamlWorkers?: number;
readonly yamlCache?: boolean;
readonly yamlCachePath?: string;
readonly budgetUsd?: number;
Expand Down Expand Up @@ -2301,7 +2291,6 @@ export async function runEvalCommand(
cache,
evaluationRunner,
workersOverride: fileOptions.workers,
yamlWorkers: targetPrep.yamlWorkers,
progressReporter,
seenTestCases,
displayIdTracker,
Expand Down
23 changes: 11 additions & 12 deletions apps/cli/test/eval.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -616,8 +616,6 @@ describe('agentv eval CLI', () => {
'name: native-exp',
'target: codex-target',
'model: gpt-5-codex',
'execution:',
' workers: 4',
'policy:',
' timeout_seconds: 12',
' threshold: 0.8',
Expand All @@ -639,7 +637,7 @@ describe('agentv eval CLI', () => {
'utf8',
);

const { stdout, exitCode } = await runCli(fixture, ['eval', wrapperPath]);
const { stdout, exitCode } = await runCli(fixture, ['eval', wrapperPath, '--workers', '4']);

expect(exitCode).toBe(0);
const outputPath = extractOutputPath(stdout);
Expand Down Expand Up @@ -669,15 +667,14 @@ describe('agentv eval CLI', () => {
model: 'gpt-5-codex',
runs: 2,
timeout_seconds: 12,
workers: 4,
});
expect(
(benchmark.metadata?.experiment_config as Record<string, unknown>).fingerprint,
).toMatch(/^[a-f0-9]{64}$/);
expect(benchmark.metadata?.runtime_source).toMatchObject({
schema_version: 'agentv.runtime_source.v1',
kind: 'wrapper_eval',
config_source: 'inline_experiment',
config_source: 'mixed',
experiment_namespace: 'native-exp',
experiment_namespace_source: 'eval_metadata',
eval_files: ['native-exp.eval.yaml'],
Expand All @@ -689,7 +686,7 @@ describe('agentv eval CLI', () => {
}
}, 30_000);

it('keeps inline runtime policy isolated across multiple eval files', async () => {
it('keeps non-concurrency runtime policy isolated across multiple eval files', async () => {
const fixture = await createFixture();
try {
const firstPath = path.join(fixture.suiteDir, 'first.eval.yaml');
Expand All @@ -699,8 +696,6 @@ describe('agentv eval CLI', () => {
[
'name: first',
'target: cli-target',
'execution:',
' workers: 1',
'policy:',
' timeout_seconds: 11',
' budget_usd: 0.11',
Expand All @@ -717,8 +712,6 @@ describe('agentv eval CLI', () => {
[
'name: second',
'target: file-target',
'execution:',
' workers: 2',
'policy:',
' timeout_seconds: 22',
' budget_usd: 0.22',
Expand All @@ -731,7 +724,13 @@ describe('agentv eval CLI', () => {
'utf8',
);

const { stdout, exitCode } = await runCli(fixture, ['eval', firstPath, secondPath]);
const { stdout, exitCode } = await runCli(fixture, [
'eval',
firstPath,
secondPath,
'--workers',
'2',
]);

expect(exitCode).toBe(0);
const outputPath = extractOutputPath(stdout);
Expand All @@ -743,7 +742,7 @@ describe('agentv eval CLI', () => {
expect(calls[0]).toMatchObject({
target: 'cli-target',
agentTimeoutMs: 11_000,
maxConcurrency: 1,
maxConcurrency: 2,
budgetUsd: 0.11,
runBudgetCapUsd: 0.11,
evalCaseIds: ['first-case'],
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/content/docs/docs/evaluation/eval-files.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ sidebar:
Evaluation files define the test cases, graders, workspace lifecycle, and run policy for an evaluation run. Top-level `name` is the experiment/result namespace, top-level `target` identifies the system under test, and top-level [`policy`](/docs/evaluation/experiments/) owns thresholds, budgets, timeouts, and repeat runs. Workspace reuse belongs under `workspace.isolation`; Docker/container binding belongs under `workspace.docker`. Install, build, and reset commands belong under `workspace.hooks`; runner-specific setup belongs under `targets[].hooks`. AgentV supports two eval data formats: YAML and JSONL.

YAML is the canonical portable model. TypeScript helpers, generated fixtures, and Python scripts should lower to the same YAML/JSONL shapes rather than inventing a separate eval contract.
Eval files describe the task, target binding, and run policy. Concurrency is an operator/run setting: pass `--workers` or set `execution.workers` in `agentv.config.*` / `.agentv/config.yaml` instead of authoring `workers` in eval YAML.

## Authoring Shapes

Expand Down
6 changes: 5 additions & 1 deletion apps/web/src/content/docs/docs/evaluation/experiments.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ top-level `target` for the system under test, and top-level `policy` for
runtime and gating controls such as run count, timeout, budgets, and thresholds.
AgentV does not have a separate `experiment.yaml` file, top-level `run_group`,
or schema-significant `experiments/` directory.
Concurrency is intentionally outside eval YAML. Use `agentv eval --workers N`
or project config defaults such as `agentv.config.*` / `.agentv/config.yaml`
`execution.workers` for operator-side parallelism.

```yaml
name: support-regression
Expand All @@ -37,7 +40,8 @@ tests:
`model`, and runtime controls to top-level `policy` with `runs`,
`timeout_seconds`, `threshold`, and `budget_usd`.
`execution:` is accepted only as a legacy top-level runtime alias for existing
eval files and target matrices.
eval files and target matrices. It does not accept `workers`; use `--workers`
or project config for concurrency.

## Layout Conventions

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Use this split when deciding where a benchmark key belongs:
| `workspace.template` | Yes | Copies a workspace template into the run workspace. |
| `workspace.hooks` | Yes | Runs lifecycle commands with workspace and case context on stdin. |
| `workspace.isolation` | Yes | Controls shared vs per-case folder isolation. Runtime workspace paths are machine-local config/CLI bindings, not benchmark provenance. |
| `experiment` | Yes | Selects targets, thresholds, repeat policy, budgets, workers, and default grader behavior. |
| `experiment` | Yes | Selects targets, thresholds, repeat policy, budgets, and default grader behavior. Concurrency is an operator/run setting from `--workers` or project config. |
| `input`, `input_files`, `expected_output` | Yes | Builds the target prompt and passive reference answer. |
| `assertions` | Yes | Runs deterministic, LLM, composite, or code graders. |
| Top-level `name`, `version`, `tags`, `license`, `requires` | Informational | Identifies and categorizes the suite. |
Expand Down
6 changes: 2 additions & 4 deletions docs/adr/0006-separate-experiments-from-eval-definitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,6 @@ fields:
name: cargowise-sql-migration-codex
target: agent
model: gpt-5-codex
execution:
workers: 4
policy:
threshold: 0.8
runs: 3
Expand Down Expand Up @@ -109,7 +107,6 @@ already-existing eval files and target matrices. New surfaces should not teach
The old experiment runtime fields are ported into the parent eval file:

- target or target matrix
- workers
- thresholds
- repeated run count through `policy.runs`
- timeout
Expand All @@ -129,7 +126,8 @@ Parent-versus-child is not the main composition rule. Contract ownership is:
| Task prompt | `input`, `input_files`, shared prompt defaults | Imported child suite |
| Task environment | `workspace`, `workspace.repos[]`, templates, workspace hooks | Imported child suite |
| Scoring | `assertions`, graders, expected references | Imported child suite |
| Run policy | `experiment`, CLI target flags, workers, repeat, gates, budget | Parent wrapper eval or CLI |
| Run policy | `experiment`, CLI target flags, repeat, gates, budget | Parent wrapper eval or CLI |
| Run concurrency | `--workers`, project config defaults, target/provider caps | Operator or selected target |
| Target runtime | selected target config and `targets[].hooks` | Selected target |

`workspace` can influence what an agent perceives through tools, but it is not
Expand Down
4 changes: 1 addition & 3 deletions examples/contract/evals/code-grader-contract.eval.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
name: code-grader-contract
description: Release gate verifying the code-grader stdin payload contract.

execution:
target: github-models-contract
workers: 1
target: github-models-contract

tests:
- id: code-grader-stdin-payload
Expand Down
4 changes: 1 addition & 3 deletions examples/contract/evals/release-gate.eval.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ description: Lightweight release gate for npm latest promotion.
workspace:
template: ../workspace-template

execution:
target: github-models-contract
workers: 1
target: github-models-contract

tests:
- id: json-contract
Expand Down
4 changes: 1 addition & 3 deletions examples/contract/evals/repo-materialization.eval.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ workspace:
repo: EntityProcess/agentv-contract-fixture
commit: 21a34daed7ebcfe36cbed053607622a55e5e94cb

execution:
target: github-models-contract
workers: 1
target: github-models-contract

tests:
- id: repo-materializes-previous-commit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

name: docker-workspace-example
description: Example eval using Docker workspace for grading
target: mock_agent

workspace:
docker:
Expand All @@ -18,10 +19,6 @@ workspace:
memory: 2g
cpus: 1

execution:
target: mock_agent
workers: 1

tests:
- id: hello-world
input: "Write a Python function that returns 'hello world'"
Expand Down
4 changes: 1 addition & 3 deletions examples/features/repo-lifecycle/evals/pool-e2e.eval.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
description: >-
E2E test for workspace pooling. No pool config in YAML — pooling is
enabled by default for shared workspaces with repos.
Run with --workers 2 to exercise multiple pool slots.

workspace:
repos:
- path: ./repo
repo: https://github.com/EntityProcess/agentv.git
commit: main

execution:
workers: 2

tags: [agent]

tests:
Expand Down
22 changes: 21 additions & 1 deletion packages/core/scripts/generate-eval-schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env bun
import { spawn } from 'node:child_process';
/**
* Generates AgentV JSON schemas from Zod schemas.
* Run: bun run generate:schema (from packages/core)
Expand All @@ -9,6 +10,24 @@ import path from 'node:path';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { EvalFileSchema } from '../src/evaluation/validation/eval-file.schema.js';

async function formatWithBiome(filePath: string): Promise<void> {
await new Promise<void>((resolve, reject) => {
const child = spawn(process.execPath, ['x', 'biome', 'format', '--write', filePath], {
stdio: 'inherit',
});

child.on('error', reject);
child.on('exit', (code) => {
if (code === 0) {
resolve();
return;
}

reject(new Error(`Biome exited with code ${code}`));
});
});
}

async function writeSchema(options: {
readonly schema: Parameters<typeof zodToJsonSchema>[0];
readonly name: string;
Expand Down Expand Up @@ -36,6 +55,7 @@ async function writeSchema(options: {
);

await writeFile(outputPath, `${JSON.stringify(schema, null, 2)}\n`);
await formatWithBiome(outputPath);
console.log(`Generated: ${outputPath}`);
}

Expand All @@ -44,5 +64,5 @@ await writeSchema({
name: 'EvalFile',
title: 'AgentV Eval File',
description: 'Schema for AgentV evaluation YAML files (.eval.yaml)',
outputFile: 'eval-schema.json',
outputFile: 'eval.schema.json',
});
2 changes: 1 addition & 1 deletion packages/core/src/evaluation/evaluate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ export async function materializeEvalConfig(
return {
testFilePath,
tests,
workers: config.workers ?? suite.workers,
workers: config.workers,
cache: config.cache ?? suite.cacheConfig?.enabled,
cachePath: config.cachePath ?? suite.cacheConfig?.cachePath,
budgetUsd: config.budgetUsd ?? suite.budgetUsd,
Expand Down
Loading
Loading