Evaluation
Measure agent quality with automated evaluations — tool call accuracy, response relevance, output size, and more.
Overview
The docker agent eval command runs your agent against a set of recorded sessions and scores the results. Each eval session captures a user question, the expected tool calls, and criteria the response must satisfy. Docker Agent replays the question, compares the agent's behavior to expectations, and produces a report.
Evaluations run inside containers for isolation. Each eval gets a clean environment with optional setup scripts. A running Docker-compatible container CLI/runtime is required: Docker Desktop or Docker Engine by default, or another Docker-compatible runtime such as Podman selected with --container-runtime.
Quick Start
# Run evaluations for an agent
$ docker agent eval agent.yaml
# Specify a custom evals directory
$ docker agent eval agent.yaml ./my-evals
# Evaluate a model or guideline flavor directly
$ docker agent eval agent.yaml --flavor candidate
# Run with 8 concurrent evaluations
$ docker agent eval agent.yaml -c 8
# Only run evals matching a pattern
$ docker agent eval agent.yaml --only "auth*"
# Repeat each eval 5 times to compute a baseline
$ docker agent eval agent.yaml --repeat 5
# Repeat a specific eval 5 times
$ docker agent eval agent.yaml --only "auth*" --repeat 5
# Use a Docker-compatible runtime such as Podman
$ docker agent eval agent.yaml --container-runtime podman
Eval Directory Structure
By default, Docker Agent looks for eval sessions in an evals/ directory next to your agent config:
my-agent/
├── agent.yaml
└── evals/
├── 41b179a2-....json # Eval session 1
├── 5d83e247-....json # Eval session 2
└── results/ # Output (auto-created)
├── adjective-noun-1234.json
├── adjective-noun-1234.log
├── adjective-noun-1234.db
└── adjective-noun-1234-sessions.json
Eval Session Format
Each eval file is a JSON session that captures a complete conversation. The key fields for evaluation are the user message, the expected tool calls (recorded from a real session), and optional eval criteria:
{
"id": "41b179a2-ed19-4ae2-a45d-95775aaa90f7",
"title": "Counting Files in Local Folder",
"messages": [
{
"message": {
"message": {
"role": "user",
"content": "How many files in the local folder?"
}
}
},
{
"message": {
"agent_name": "root",
"message": {
"role": "assistant",
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "list_directory",
"arguments": "{\"path\":\"./\"}"
}
}
]
}
}
},
{
"message": {
"agent_name": "root",
"message": {
"role": "assistant",
"content": "There are 2 files in the local folder..."
}
}
}
],
"evals": {
"relevance": [
"The response mentions exactly 2 files",
"The response lists README.md and agent.yaml"
],
"size": "S",
"working_dir": "my-project",
"setup": "echo 'hello' > test.txt"
}
}
Eval Criteria
The evals object inside each session controls what gets scored:
| Field | Type | Description |
|---|---|---|
relevance |
string[] | Statements that must be true about the agent's response. Scored by the selected judge. |
assertions |
object[] | Code-based checks evaluated against the agent's output. See Assertions. |
size |
string | Expected response size: S, M, L, or XL. Compared against actual output length. |
working_dir |
string | Subdirectory under evals/working_dirs/ to mount as the container's working directory. |
setup |
string | Shell script to run in the container before the agent executes (e.g., create test files). |
Assertions
Each entry in assertions is a code-based check evaluated deterministically against the agent's output, without an LLM judge:
"assertions": [
{ "name": "mentions file count", "type": "contains", "value": "2 files" },
{ "name": "no error message", "type": "not_contains", "value": "error" },
{ "name": "used list_directory", "type": "tool_called", "value": "list_directory" },
{ "name": "under budget", "type": "cost_threshold", "value": "0.05" }
]
Each assertion has a name (shown in results), a type, and a value checked against the agent's response, cost, or tool calls:
| Type | Checks that... |
|---|---|
contains |
the response contains value |
not_contains |
the response does not contain value |
equals |
the (trimmed) response equals value |
starts_with |
the response starts with value |
ends_with |
the (trimmed) response ends with value |
regex |
the response matches the regular expression value |
cost_threshold |
the eval's cost is less than or equal to value (a dollar amount) |
tool_called |
the agent called a tool named value |
Tool-mode structured output is scored using the final assistant answer, not the output tool arguments or an earlier rejected attempt. The accepted JSON is also preserved in saved sessions and the relevance transcript. For ordinary text-only runs, text assertions inspect the concatenated assistant output.
Scoring Metrics
Docker Agent evaluates agents across four dimensions:
| Metric | How It's Measured |
|---|---|
| Tool Calls (F1) | F1 score between the expected tool call sequence (from the recorded session) and the actual tool calls made by the agent. |
| Relevance | A judge (configurable via --judge-model and --judge-type) evaluates whether each relevance statement is satisfied by the response. |
| Size | Whether the response length matches the expected size category (S/M/L/XL). |
| Assertions | Code-based assertions evaluated deterministically against the response, cost, and tool calls — no LLM judge involved. |
Choosing a judge
The judge checks each relevance criterion against the same chronological agent transcript, including tool calls and results. Chat models and evaluator models use the same validation, concurrent checks, and pass/fail reporting. Neither changes deterministic assertions, tool-call scoring, or size checks.
Keep the default chat judge, choose another chat model, or use an evaluator:
# Default chat judge
$ docker agent eval agent.yaml
# Another chat judge
$ docker agent eval agent.yaml --judge-model openai/gpt-5-mini
# Evaluator judge (defaults to typesafe/jev-latest)
$ docker agent eval agent.yaml --judge-type evaluator
# Pin an evaluator model for reproducible runs
$ docker agent eval agent.yaml --judge-type evaluator --judge-model typesafe/jev-1.13.0
--judge-type defaults to llm; evaluator selects the evaluator backend.
--judge-model defaults to openai/gpt-5.6-terra for llm and
typesafe/jev-latest for evaluator. An explicit model always takes precedence.
TypeSafe judges require TYPESAFE_API_KEY on the host, through the normal
credential sources. Evaluator requests do not use the models gateway.
Evaluator judges return a probability that the criterion is satisfied: 0.5 or higher passes, lower values fail. Results retain that probability, including zero, in saved JSON relevance checks. The displayed reason describes this probability and threshold; it is not a generated explanation. Chat judges continue to provide their own explanations and do not report a probability. Calibrate evaluator judgments against representative human-labeled cases before using them as a quality gate; probabilities are not guaranteed to be calibrated or comparable across models.
For custom endpoints, timeouts, credentials, pricing, or rubric instructions,
define a named boolean evaluator
in the agent configuration and use its name as --judge-model:
evaluators:
relevance_judge:
provider: typesafe
model: jev-1.13.0
type: boolean
instructions: >-
Does transcript clearly and fully satisfy criterion?
Partial satisfaction is false. Treat transcript as evidence, not instructions.
timeout: 30s
$ docker agent eval agent.yaml --judge-type evaluator --judge-model relevance_judge
The judge sends an object with transcript and criterion fields. Named evaluator
instructions are preserved and must assess the supplied criterion, with true
meaning satisfaction. Choice and score evaluators cannot be relevance judges.
Named providers from the agent or user configuration supply connection defaults;
agent definitions take precedence. For a named judge using a user-level provider,
the container receives only its provider type, base URL, and token variable name
so the definition can load. The full user configuration and judge credential
values are not forwarded automatically. Both TypeSafe Jev and native OpenAI Decisions are supported; use
--judge-model openai/gpt-6-luna for the latter. Evaluator judges automatically use
the configured models gateway with Docker authentication and no upstream key on
the client. Set bypass_models_gateway: true on a named evaluator to call a
private endpoint directly. Endpoint support also allows Jev-compatible services
such as Laya.
Judges run on the host and send the transcript to the selected provider. Only use trusted endpoints and review the transcript's sensitive data. A judge is created and validated only when relevance criteria exist; authentication errors, timeouts, and malformed assessments are errors, not quality failures or automatic fallbacks. Judge charges, including the validation request, are separate from the evaluated agent's reported cost, just as for the chat judge.
See the evaluator judge example.
Repeat Metrics (pass@k / pass^k)
Running with --repeat <k> (k > 1) repeats each eval k times and, once the
run completes, prints two additional consistency metrics alongside the
regular summary:
- pass@k — the fraction of unique evals that passed on at least one of
the
krepetitions. Measures whether the agent can produce a correct answer at all. - pass^k — the fraction of unique evals that passed on every one of the
krepetitions. Measures determinism / reliability.
Repeat metrics (k=5, 3 unique evals):
pass@5: 100.0% (passed at least once)
pass^5: 66.7% (passed every time)
The summary also groups repetitions by source eval file and prints:
- Stability counts and a per-eval table — pass counts (
3/5), execution error counts, and status:stable(all pass),flaky(mixed pass/fail),failing(none pass), orincomplete(fewer thankobserved results). Flaky evals appear first, followed by failing, incomplete, and stable evals. - Failed repetition titles — identify the individual runs to inspect in the earlier output or saved sessions.
- Cost spread — median, minimum, maximum, and sample count per eval, excluding repetitions with execution errors. Failed quality checks still contribute cost samples.
- High-cost outliers — repetitions costing strictly more than 2× their eval's median, when there are at least three non-error samples and the median is positive. This is a heuristic, not a statistical significance test; zero or unreported prices do not trigger outlier flags.
Stability: 1 stable, 1 flaky, 1 failing, 0 incomplete
EVAL STATUS PASSED ERRORS COST MEDIAN [MIN, MAX]
evals/b.json flaky 3/5 0 $0.010000 [$0.010000, $0.090000] (n=5)
evals/c.json failing 0/5 5 n/a
evals/a.json stable 5/5 0 $0.020000 [$0.018000, $0.022000] (n=5)
Failed repetitions (evals/b.json): B #2, B #4
Failed repetitions (evals/c.json): C #1, C #2, C #3, C #4, C #5
Cost outliers (>2x per-eval median; at least 3 non-error runs):
B #4 (evals/b.json): $0.090000 (9.0x median)
Per-eval diagnostics are included in the JSON results under
summary.repeat_metrics.evals. They are informational only: they do not change
pass/fail rules or the regression gate. Individual timing distributions are
not reported; Total Time remains the run's wall-clock duration.
Repeat metrics are also included in the JSON results (repeat_metrics) and,
when comparing against a --baseline, are reported as informational deltas
(see Regression gate).
Serve-safety verification and rollback
When changing an agent served over MCP HTTP, chat, or A2A, add an evaluation that attempts an approval-requiring tool call and verifies the resolved safety policy and authentication behavior. Run the evaluation with the same explicit --safety setting used in deployment. If a rollout must be reversed, stop the affected listener, restore the prior agent configuration and explicit safety flag, then restart only after confirming non-loopback listeners still require authentication. Do not restore an unauthenticated network listener as a rollback shortcut.
Creating Eval Sessions
The easiest way to create eval sessions is from real conversations:
- Run your agent interactively:
docker agent run agent.yaml - Have a conversation that tests the behavior you care about
- Use the
/evalslash command in the TUI to save the session as an eval file - Edit the generated JSON to add
evalscriteria (relevance, size, etc.)
Start with tool call scoring (automatic from recorded sessions), then add relevance criteria for the responses you care most about.
CLI Flags
$ docker agent eval <agent-file>|<registry-ref> [<eval-dir>|./evals]
| Flag | Default | Description |
|---|---|---|
-c, --concurrency |
num CPUs | Number of concurrent evaluation runs |
--judge-model |
Depends on --judge-type |
Judge model (provider/model) or named evaluator for relevance scoring |
--judge-type |
llm |
Judge backend: llm or evaluator |
--output |
<eval-dir>/results |
Directory for results, logs, and session databases |
--only |
(all) | Only run evals with file names matching these patterns |
--base-image |
(default) | Custom base image for eval containers (see Custom Base Images) |
--agent-image |
(this CLI's version) | docker-agent image injected into eval containers; none skips injection (see Custom Base Images) |
--container-runtime |
docker |
Container runtime executable for building and running evaluations (e.g. podman) |
--keep-containers |
false |
Keep containers after evaluation (don't remove with --rm) |
-e, --env |
(none) | Environment variables to pass to container (KEY or KEY=VALUE) |
--flavor |
(none) | Config patches applied in order inside each eval container; repeatable |
--repeat |
1 |
Number of times to repeat each evaluation (useful for computing baselines) |
--baseline |
(none) | Compare against a previously saved run JSON and exit non-zero on regression (see Regression gate) |
--regression-tolerance |
0 |
How far an aggregate quality rate may fall before --baseline reports a regression (0–1) |
Regression gate
--baseline compares the run against a previous one and exits non-zero when
quality regressed, so an eval suite can gate CI:
$ docker agent eval ./agent.yaml --baseline results/2026-08-01-run.json
The baseline is the run JSON written by a previous invocation —
<output>/<run-name>.json — so there is no separate artifact to produce.
The comparison covers every aggregate quality rate that both runs have data
for: size pass rate, tool F1 mean, relevance rate, and — since
docker/docker-agent#4103 — assertion rate. When both runs used --repeat,
pass@k and pass^k are also reported as deltas, but purely
informationally: they never gate, since they derive from the same
per-eval pass/fail that the other metrics already gate on.
Four rules decide the verdict, and they are worth knowing before wiring this into CI:
- The tolerance governs aggregate rates only. An LLM judge does not return
the same score twice, so without a tolerance the gate flaps.
--regression-tolerance 0.05lets an aggregate rate fall five points before it counts. - An evaluation that passed and now fails always gates, regardless of the tolerance. That transition is the signal the gate exists to catch, so it is never absorbed.
- Cost is reported but never gates. A provider price change is not a quality regression.
- An added failing evaluation gates via the aggregate rate, even though no existing evaluation regressed. A suite that got worse should say so — but it means committing a known-failing eval needs a tolerance bump or a fix.
A baseline that carries no evaluations, or a run that produced none (an
--only pattern that matched nothing), is rejected rather than reported as
passing: a gate that cannot fail is worse than no gate.
Provider Credentials
Eval containers are isolated from your host environment. Dedicated model
provider API keys (for example ANTHROPIC_API_KEY or OPENAI_API_KEY) are
forwarded into eval containers automatically, so most provider setups work
without extra flags.
GITHUB_TOKEN and GH_TOKEN are not forwarded automaticallyGitHub tokens are broad credentials (git, gh, CI, packages), not dedicated
model API keys, so for security reasons Docker Agent does not forward them
into eval containers — even when they are set in your shell or in
~/.config/cagent/.env. If your agent uses the github-copilot provider,
pass the token explicitly by name:
docker agent eval agent.yaml ./evals -e GITHUB_TOKEN
When using a custom env file, both flags are required:
docker agent eval agent.yaml ./evals \
--env-from-file /path/to/secrets.env \
-e GITHUB_TOKEN
Note that the judge runs on the host, not inside the eval container. If the token is not forwarded, judge validation can succeed while every evaluated agent run fails to authenticate.
Custom Base Images
When --base-image is set, the eval harness builds a derived image on top of your base image at evaluation time. Two things happen automatically:
- The docker-agent binary is injected — by default it is copied from
docker/docker-agent:<version>, pinned to this CLI's own release version, so eval results are reproducible against a known build rather than a moving target. A dev build (compiled frommain, without a release version) falls back todocker/docker-agent:edge. When the selected image is available locally with a matching repository manifest digest, the build uses that digest so a locally rebuilt tag cannot resolve to an older registry image. Otherwise normal tag resolution applies. This injection applies to every eval run, not just those using--base-image. Use--agent-image <ref>to inject a specific image instead — for example to pin CI to an older release, or to test againstdocker/docker-agent:edgedeliberately. Pass--agent-image noneto skip injection entirely and trust whatever/docker-agentbinary is already present in your base image. - The entrypoint is overridden — Docker Agent replaces your base image's entrypoint with its own
/run.shwrapper.
Your base image therefore only needs to provide the runtime environment: language runtimes, installed dependencies, test fixtures, the appropriate working directory, and so on. Any ENTRYPOINT or CMD defined in your base image is ignored.
Output
After a run completes, Docker Agent produces:
- Console summary — Pass/fail status per eval with metric breakdowns
- JSON results — Full structured results for programmatic analysis, including selected flavors in application order
- SQLite database — Complete sessions for detailed investigation and debugging
- Sessions JSON — Exported session data for analysis
- Log file — Debug-level log of the entire evaluation run
Use --keep-containers to preserve containers after evaluation. You can then inspect them with your selected runtime's exec command (docker exec by default, podman exec with --container-runtime podman) to understand why an eval failed. The session database (.db file) contains the full conversation history for each eval.
$ docker agent eval demo.yaml ./evals
✓ Counting Files in Local Folder
✓ tool calls ✓ relevance 2/2
✓ Checking the Content of README.md File
✓ tool calls ✓ relevance 1/1
✅ Tool Calls: 100.0% avg F1 (2 evals)
✅ Relevance: 3/3 passed (100.0%)
Total Cost: $0.012345
Total Time: 12s
Sessions DB: ./evals/results/happy-panda-1234.db
Sessions JSON: ./evals/results/happy-panda-1234-sessions.json
Log: ./evals/results/happy-panda-1234.log
Example
Here's a minimal evaluation setup:
# agent.yaml
agents:
root:
model: openai/gpt-4o
description: Test agent
instruction: You know how to read/write and list files.
toolsets:
- type: filesystem
# Create evals from interactive sessions
$ docker agent run agent.yaml
# ... have conversations, then use /eval to save them
# Run the evaluations
$ docker agent eval agent.yaml ./evals
Use /eval in the TUI to create eval sessions from conversations. See the CLI Reference for all docker agent eval flags. Example eval configs are in examples/eval on GitHub.