.vally.yaml Configuration
The .vally.yaml file defines project-level configuration for eval discovery, named environments, and eval suites. Place it at your project root.
Minimal Example
Section titled “Minimal Example”paths: evals: evals/Configures where to find skills, evals, and results.
| Field | Type | Default | Description |
|---|---|---|---|
skills |
string or string[] |
["skills/"] |
Directories containing SKILL.md files |
evals |
string or string[] |
["evals/"] |
Directories to scan for eval files |
results |
string |
"vally-results/" |
Directory for output results |
evalFilenames |
string or string[] |
["eval.yaml", "eval.yml"] |
Filename patterns for eval discovery (supports globs) |
Custom Filename Patterns
Section titled “Custom Filename Patterns”paths: evals: [evals/, tests/evals/] evalFilenames: ["eval.yaml", "eval.yml", "*.eval.yaml"]suites
Section titled “suites”Named groups of evals for running specific subsets. Each suite must have at least one of filter or evals.
Filter-Only Suite
Section titled “Filter-Only Suite”Select stimuli by tags across all discovered evals:
suites: fast: description: "P0 tests for inner loop" filter: priority: p0Filter semantics: AND across keys, OR within values. Suite filter: blocks are include-only — the --tag key!=value exclusion form is available only on the CLI, and a bare key like flaky! here is treated as a literal tag key, not a negation.
Evals-Only Suite
Section titled “Evals-Only Suite”Scope to specific eval files by path or glob:
suites: safety: description: "All safety evals" evals: - "evals/safety/**/*.eval.yaml" - "evals/shared/baseline.eval.yaml"Combined Suite
Section titled “Combined Suite”Scope by file path, then filter by tags:
suites: safety-p0: evals: ["evals/safety/**/*.eval.yaml"] filter: { priority: p0 }File scoping narrows which eval files are loaded. Tag filtering narrows which stimuli within those files run.
environments
Section titled “environments”Named environments for reuse across eval specs.
environments: auth-workspace: skills: - skills/auth files: - src: fixtures/users.json dest: test-data/users.json commands: - npm installReference in eval specs by name:
agent_environment: auth-workspacestimuli: - name: login-test prompt: "Test the login flow"Environment Fields
environment: skills: - ./path/to/my-skill # Skill directory (containing SKILL.md) files: - src: fixtures/input.txt dest: input.txt - src: fixtures/test-data # directories are copied recursively dest: test-data - src: fixtures/session-log # staged outside the graded workspace dest: session-state/session-1 dest_root: assets commands: - npm install commandTimeout: 2m git: type: worktree ref: v2.1.0 source: ../my-repo mcpServers: db: type: stdio command: db-serve args: ["--port", "5432"] api: type: http url: http://localhost:3000/mcp env: COPILOT_AGENT_ACTION: review LOG_LEVEL: debug| Field | Type | Description |
|---|---|---|
commands |
string[] |
Shell commands to run during setup (uses /bin/sh on Unix, cmd.exe on Windows) |
commandTimeout |
duration |
Per-command timeout for commands (e.g. 2m, 90s). Must be positive (0 is rejected). Defaults to 60s |
env |
Record<string, string> |
Environment variables set on the agent process (see below) |
files |
{src, dest, dest_root?}[] |
Files or directories to copy during setup (see below) |
git |
object |
Git configuration for fixture data — local worktree or remote clone (see below) |
mcpServers |
Record<string, McpServerConfig> |
Named MCP servers to start or connect to |
skills |
string[] |
Paths to skill directories (each containing a SKILL.md) to load |
Staging files
Each files entry copies src to a destination chosen by dest_root:
| Field | Type | Required | Description |
|---|---|---|---|
src |
string | Yes | File or directory to copy, relative to the eval file’s directory (or absolute) |
dest |
string | Yes | Destination path. Under dest_root: assets it must stay inside the assets root once normalized (no absolute paths, and no traversal that ends up above the root); under the default workdir it is resolved relative to the workspace root |
dest_root |
"workdir" | "assets" |
No | Which root dest is resolved under. Defaults to workdir |
dest_root: workdir(default) — the file lands inside the graded workspace, exactly as before. It stays out of diffs — the workspace baseline is captured after setup — but it is a real file inworkDir, so tree- and file-scanning graders (file-exists,file-contains, …) do see it.dest_root: assets— the file is staged into a managed, per-trial assets directory that sits outside the graded workspace. Nothing is copied intoworkDir, so neither diff- nor tree-scanning graders see it, and the directory is cleaned up automatically at the end of the trial (no manual delete step). Use this for setup inputs that must exist on disk but should not contaminate the graded tree — recorded session logs to seed a session, or large fixtures a grader reads.
The assets directory is exposed as the EVALUATE_ASSETS environment variable (see
Agent env) and to graders as trajectory.assetsDir (the program grader also receives
it as EVALUATE_ASSETS). It is a host path allocated fresh per trial; treat it as absent when
re-grading a stored trajectory on a different host.
Harness customization files
Use files to stage agent customizations at the location expected by the selected executor:
GitHub Copilot
environment: files: - src: ./agents/reviewer.agent.md dest: .github/agents/reviewer.agent.mdClaude Code
environment: files: - src: ./agents/reviewer.md dest: .claude/agents/reviewer.mdBoth harnesses discover staged profiles natively — Vally does not parse agent files, so the full frontmatter each harness supports works as documented upstream. Staging the file is the entire setup: there is no flag to turn on and no agent field in the eval spec. For Copilot, a Git-backed workspace with a GitHub remote may additionally expose organization or enterprise agents to the authenticated identity, so the available roster can differ between runners.
Neither harness replaces the default root agent. A matching profile is invoked as a model-routed subagent: the harness compares the stimulus against each profile’s name and description and decides whether to delegate. Write a stimulus that clearly matches the profile you want to exercise.
Because that routing is a model decision, gate it with a grader rather than assuming it happened.
For Copilot, given an agent file whose frontmatter sets name: Reviewer:
stimuli: - name: delegates-to-reviewer prompt: "Delegate this to the Reviewer specialist and report its findings." graders: - type: tool-calls config: required: - name: "^task$" args: agent_type: "^Reviewer$"The eval now fails when the agent is never used. agent_type is the agent’s display name: the
frontmatter name when present, otherwise the filename stem — a reviewer.agent.md with no name
is matched as ^reviewer$. Argument names are harness-specific, so inspect a recorded trajectory
before writing the matcher for another executor. Subagent events also carry an agentId, which
grader-level scope can target to assert what the subagent
actually did, not just that it ran.
Agent env
The env field sets environment variables on the agent process for every
run of this environment. All process-spawning executors (claude-cli, cca,
copilot-sdk) merge these over the inherited environment. The built-in mock
executor has no agent process and ignores env; Vally prints a warning listing
the ignored variable names so the misconfiguration is visible.
environment: env: COPILOT_AGENT_ACTION: review LOG_LEVEL: debug| Field | Type | Description |
|---|---|---|
env |
Record<string, string> |
Variable name → value, set on the agent process. In experiment variant overrides, values support ${…} interpolation like other override fields. |
Git config
The git field sets up the evaluation workspace from a Git repository. It has two
modes, discriminated by type:
worktree— check out a ref from a local repository as a detached worktree.clone— clone a remote repository at a given ref into the workspace.
For Harbor-exported tasks, both worktree and clone exports are finalized as self-contained, pinned checkouts baked into the task image at /app on Linux or C:\app on Windows. Before shipping the checkout, Harbor strips the transient clone origin (for worktrees, an implementation-created file:// remote), reflogs, fetch record, and hooks so remote credentials and local source paths are not shipped. Git LFS-tracked files remain pointers rather than materialized content and emit warnings; submodules are not materialized and emit warnings; and symlink and executable-mode fidelity depend on the export host OS. When the Harbor exporter installs the runtime, it also installs Git for Git-backed tasks; with runtime installation disabled, the custom base image must provide Git for agent-side Git commands.
type: worktree
For Harbor-exported tasks, environment.git with type: worktree is materialized at export time from the local source repository as a self-contained checkout of the pinned commit baked into the task image at the OS-specific workspace path. The export retains history reachable from that commit, which can increase output size for repositories with large histories.
environment: git: type: worktree ref: v2.1.0 source: ../my-repo timeout: 10m # optional; raise for very large repos commands: - dotnet restore| Field | Type | Required | Description |
|---|---|---|---|
type |
"worktree" |
Yes | Must be "worktree" |
ref |
string | Yes | A commit-ish value (tag, commit SHA, or branch name) to check out |
source |
string | Yes | Path to the local repo used as the worktree source |
timeout |
duration |
No | Timeout for the local git steps that materialize the worktree (add, remove), e.g. 10m. Must be positive. Defaults to 5m |
type: clone
environment: git: type: clone url: https://github.com/octocat/hello.git ref: v2.1.0 # optional; defaults to the remote's default branch shallow: true # optional; true → depth 1, or an explicit integer depth sparse: # optional; cone-mode paths to materialize - src/core timeout: 10m # optional; local clone steps only (not the network fetch)| Field | Type | Required | Description |
|---|---|---|---|
type |
"clone" |
Yes | Must be "clone" |
url |
string | Yes | Remote repository URL to clone: http(s), ssh, git, file://, or scp-style git@host:path |
ref |
string | No | Commit-ish to check out; defaults to the remote’s default branch |
shallow |
boolean | integer | No | Shallow-clone history: true fetches depth 1, an integer sets an explicit depth |
sparse |
string[] | No | Sparse-checkout (cone mode) paths — only these directories are materialized |
timeout |
duration |
No | Timeout for each local git step (init, remote add, sparse-checkout, checkout), e.g. 10m. Does not cap the network fetch. Must be positive. Defaults to 5m |
HTTP(S) clone URLs must not contain userinfo, query/fragment parameters, or backslashes, and SSH URLs must not contain passwords. Use host Git authentication such as Git Credential Manager, an SSH agent, or GIT_ASKPASS.
For Harbor-exported tasks, environment.git with type: clone contacts the remote repository during export and uses the host’s Git configuration, including credential helpers. After materialization, the resulting checkout is a self-contained, pinned checkout (commit SHA) baked into the task image. The Docker build may still require network access for the base image, runtime installation, or environment commands.
MCP server config
Each entry in mcpServers is either a stdio server (launched as a child process) or a remote server (connected over HTTP/SSE).
Stdio (child process)
mcpServers: db: type: stdio command: db-serve args: ["--port", "5432"] env: DB_HOST: localhost cwd: ./services/db timeout: 5000| Field | Type | Required | Description |
|---|---|---|---|
type |
"stdio" |
Yes | Launch as a child process |
command |
string | Yes | Executable to run |
args |
string[] | No | Arguments passed to the command |
env |
Record<string, string> |
No | Extra environment variables for the child process |
cwd |
string | No | Working directory for the child process |
timeout |
number | No | Timeout in milliseconds for connecting to / invoking the server |
Remote (HTTP/SSE)
mcpServers: api: type: http # or "sse" url: http://localhost:3000/mcp headers: Authorization: "Bearer ${API_TOKEN}" timeout: 10000| Field | Type | Required | Description |
|---|---|---|---|
type |
"http" | "sse" |
Yes | Connect to a remote server |
url |
string | Yes | Server endpoint URL |
headers |
Record<string, string> |
No | Extra HTTP headers (e.g. auth tokens) |
timeout |
number | No | Timeout in milliseconds for connecting to / invoking the server |
Complete Example
Section titled “Complete Example”paths: skills: [skills/] evals: [evals/] results: vally-results/ evalFilenames: ["eval.yaml", "eval.yml", "*.eval.yaml"]
environments: default-workspace: skills: [skills/copilot] commands: [npm install] mcpServers: db: type: stdio command: db-serve args: ["--port", "5432"]
suites: ci-gate: description: "Fast checks for every PR" filter: priority: [p0, p1]
safety: description: "All safety evals" evals: ["evals/safety/**/*.eval.yaml"]
safety-p0: description: "Critical safety evals only" evals: ["evals/safety/**/*.eval.yaml"] filter: { priority: p0 }Schema Validation
Section titled “Schema Validation”The JSON schema is at packages/core/src/config/vally-config.schema.json. For VS Code autocompletion, add to settings:
{ "yaml.schemas": { "./packages/core/src/config/vally-config.schema.json": ".vally.yaml" }}