169 Commits
Author SHA1 Message Date
melmennaoui fcb865d638 fix(evaluation): preserve tool evidence in judge transcripts
Remove the implicit 500-byte tool-result cutoff so relevance judges can assess the same evidence recorded for the agent. Cover boundary lengths, tail and middle evidence, UTF-8, and large outputs.

Closes #4770

Assisted-By: docker-agent
Signed-off-by: melmennaoui <mohamed.elmennaoui@docker.com>
2026-10-29 23:02:31 +01:00
Djordje Lukic 306a0b8cb0 refactor: centralize providers in an embedded catalog
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
2026-10-28 22:03:27 +01:00
David Gageot 51cd9c4fe6 refactor(evaluation): scope progress rendering to its run 2026-10-21 19:17:29 +02:00
David Gageot 3a2d99a548 test(evaluation): reuse an immutable runtime launcher
Assisted-By: docker-agent
Signed-off-by: David Gageot <david.gageot@docker.com>
2026-10-17 16:32:22 +02:00
David Gageot 2611ab0f7c test(eval): create fake runtimes before parallel execution
Write fake container runtime scripts before t.Parallel and symlink
them from each subtest's temp dir, so forked children don't inherit
a writable executable fd and hit ETXTBSY on Linux.

Assisted-By: cagent
2026-10-08 18:10:58 +02:00
David Gageot 0b240a1c2c feat: route evaluators through the models gateway, add OpenAI Decisions
Recording/replay now scrubs evaluator credentials and matches on
canonical request bodies, with tests covering the gateway, direct,
and replay paths.

Assisted-By: docker-agent
2026-10-07 12:36:48 +02:00
David Gageot 8e81df5f79 feat(eval): add evaluator judge backend
Signed-off-by: David Gageot <david.gageot@docker.com>
2026-10-07 10:30:07 +02:00
Arnaud Héritier a01b9102d5 test(eval): avoid writing executable runtimes during parallel tests
Create the shared shell fixture before the parent parallel barrier and use per-case symlinks and data files. Preserve digest pinning and fallback assertions without concurrent writes to executable fixture inodes.

Signed-off-by: Arnaud Héritier <aheritier@users.noreply.github.com>
2026-10-06 09:30:58 +00:00
David Gageot e235955631 eval: default judge model to openai/gpt-5.6-terra
Assisted-By: docker-agent
2026-10-05 10:39:24 +02:00
David Gageot f2c8671000 fix(eval): pin local agent image digest when building containers 2026-10-04 18:07:35 +02:00
David Gageot 9ecdba17ad feat(eval): report repeat stability and cost outliers 2026-10-02 11:49:05 +02:00
David Gageot a7bfb6dc39 fix(eval): skip POSIX flavor runtime fixture on Windows 2026-10-02 11:33:13 +02:00
David Gageot 594a2c07c3 feat(eval): support --flavor and capture structured tool output
Flavors are now forwarded to eval containers in order, and tool-mode
structured output is treated as the agent's real final answer instead
of the output tool call itself or an earlier rejected attempt.

Assisted-By: docker-agent
2026-10-02 11:15:38 +02:00
David Gageot b285b0abf1 refactor: migrate UUID generation and parsing to Go 1.27 stdlib
Use crypto/rand-backed stdlib uuid.NewV4().String() for random UUIDs and stdlib parsing instead of github.com/google/uuid, wherever the stdlib can express the behavior.

github.com/google/uuid is kept only in pkg/model/provider/base/opencode.go for uuid.NewSHA1, since the stdlib has no UUIDv5 support; the namespace and resulting IDs are unchanged.

pkg/userid/userid.go keeps a compatibility helper that preserves every legacy persisted-ID form the Google parser accepted, without rewriting or rotating existing IDs. pkg/evaluation/eval.go keeps reading the first 4 UUID bytes as big-endian to preserve existing decimal evaluation names.

Added tests covering v4 bit layout, legacy persisted-ID forms, the evaluation name format, and the OpenCode UUIDv5 fallback.

Signed-off-by: David Gageot <david.gageot@docker.com>
2026-09-23 15:44:22 +02:00
David Gageot 5896a98f52 fix: preserve parallel tool batches when replaying evaluator events 2026-09-22 17:59:59 +02:00
David Gageot e1aa29e63b feat: track evaluator cost in sessions, budget, and cost dialog
Add per-evaluation usage accounting: evaluations are persisted as session
items, surfaced in the /cost dialog under a dedicated "By Evaluator" section,
and feed into budget enforcement. Unknown pricing renders as "unknown" rather
than a misleading $0.

Assisted-By: Claude
2026-09-22 17:29:55 +02:00
Arnaud Héritier e3319d1edb fix(eval): validate agent before judge setup 2026-09-16 16:13:25 +02:00
David Gageot 8fa421a615 feat(lint): require atomicfile.Write for marshalled state
Assisted-By: docker-agent
2026-09-14 12:15:59 +02:00
Arnaud Héritier d00afb028c test(evaluation): stop mutating version.Version from parallel tests
withVersion wrote the global version.Version from a t.Parallel() test
while sibling parallel tests read it through DefaultAgentImage(), which
the race detector flagged. Extract the pure defaultAgentImageFor helper
and table-test it with explicit versions instead.
2026-09-10 18:18:27 +02:00
Arnaud Héritier 2470dc87f8 fix(evaluation): read container stderr through exec.Cmd to avoid a data race
runDockerAgentInContainer read stderrData after cmd.Wait() while a
hand-rolled goroutine was still writing it; Wait does not wait for our
own goroutine. Let exec.Cmd own the copy via a bytes.Buffer so Wait
(bounded by WaitDelay) guarantees the data is complete before we read it.
2026-09-10 18:18:26 +02:00
Arnaud Héritier 085611ed83 feat(#3996): persist absolute WorkingDir provenance at every local creation path
Every session created against a local workspace now records the absolute
workspace root that owns it, so generated files can later be resolved
against the owning workspace instead of whatever cwd the viewer happens
to run from:

- delegated/nested sub-sessions inherit parent.WorkingDir
- serve mcp / a2a / chatserver capture the server workspace once at
  startup instead of reading os.Getwd per request (or not at all)
- embeddedchat conversations and `docker-agent new` capture the
  configured root or the creation-time cwd
- --working-dir writes the absolutized path back into RuntimeConfig so a
  relative flag value cannot leak into persisted provenance
- compaction runs inherit the compacted session's workspace

Remote/headless surfaces (remote backend template, API templates without
working_dir, ACP without client cwd, evaluation container transcripts)
intentionally keep an empty WorkingDir and now document that choice.

session.ResolveWorkingDir adds a bounded, cycle-safe, validated
parent-chain fallback for old persisted sub-sessions with an empty
WorkingDir; it rejects relative/malformed roots and never falls back to
the process cwd. It is the primitive the generated-media resolver will
build on.
2026-09-09 17:55:34 +02:00
Arnaud Héritier 3b0506aa24 fix(eval): always record resolved agent image in saved run JSON
RunOutputConfig.AgentImage previously used omitempty on the *resolved*
image (ResolvedAgentImage), which is empty precisely when
--agent-image none was passed to skip binary injection. That made an
explicit opt-out indistinguishable from a run predating this field.
Drop omitempty so the field is always present, with an empty string
now unambiguously meaning injection was skipped.

Also clarifies that binary injection applies to every eval run, not
just --base-image ones, and adds save_test.go coverage for all three
--agent-image outcomes.
2026-09-04 19:14:07 +00:00
Arnaud Héritier 05c6c3a1c4 feat(eval): pin injected docker-agent image to host CLI version by default
Previously the eval harness always injected the docker-agent binary from
the rolling docker/docker-agent:edge tag, so `docker agent eval` tested
against a moving main-HEAD binary regardless of the host CLI's own
version. Default the injected image to docker/docker-agent:<version>,
matching this CLI's release version, falling back to :edge for dev/main
builds where version.Version isn't a valid semver.

Add --agent-image to override the default explicitly, including a
"none" value that skips binary injection entirely and trusts whatever
/docker-agent is already present in the base image.

Fixes part 2 of #4175.
2026-09-04 19:00:51 +00:00
David Gageot 1d2224aa23 refactor(config): split agent sources so embedders link only the source types they use
- config.Load is now YAML-only. HCL support becomes a source decorator,
  hcl.NewSource, applied by the resolver; embedders wrap sources that may
  carry HCL themselves.
- The OCI source moves to pkg/config/ocisource (ocisource.New).
- Resolve, ResolveSources, ResolveAlias and BuiltinAgentNames move to
  pkg/config/sources together with the embedded built-in agents. This is
  the package that knows every source type.
- teamloader no longer hardwires the resolver for external sub-agents:
  WithSourceResolver supplies it, and loaderdefaults.Opts wires
  sources.Resolve so CLI behaviour is unchanged.

pkg/config drops hcl, go-cty, levenshtein, textseg, wordwrap,
klauspost/compress and image-spec, and only reaches
go-containerregistry/pkg/name for reference parsing.
2026-09-04 09:21:52 +02:00
melmennaoui c90793b77c feat: include assertions and pass@k in baseline regression comparison
Extend the baseline regression gate to cover assertion pass rates and
surface pass@k/pass^k consistency metrics from --repeat runs.

Also removes the verify script dead code (verify field, VerifyCheck type,
verify.go/verify_test.go) — the assertion system covers verifiable cases
via the captured event stream, and verify cannot work without container
lifecycle changes that aren't warranted.

Assertion rate gates:
- AssertionRate added to Metrics, derived from Summary totals
- Compare() gates on assertion rate drops (same semantics as size
  and relevance)
- computeSummary() accumulates assertion counts
- printSummary() displays the Assertions metric line

Repeat metrics (informational):
- PassK, HatK, RepeatK added to Metrics
- Compare() reports pass@k and pass^k as informational deltas

Verify cleanup:
- Removed EvalCriteria.Verify, VerifyCheck, verify.go, verify_test.go
- Schema's DisallowUnknownFields ensures clean future re-addition

Closes #4087
2026-09-01 18:01:16 +02:00
melmennaoui 54e78f0868 feat: wire assertion runner into runSingleEval execution pipeline
Add a code-based assertion evaluator and integrate it into the eval
pipeline so assertions declared in the eval JSON are executed after the
agent run, alongside the existing size/tool-call/relevance checks.

New files:
- assertions.go: runAssertions() evaluates 8 assertion types
  (contains, not_contains, equals, starts_with, ends_with, regex,
  cost_threshold, tool_called) against the agent response, cost,
  and tool-call list.
- assertions_test.go: comprehensive tests for all assertion types.

Modified files:
- types.go: AssertionResults/AssertionsPassed/AssertionsTotal fields
  on Result; checkResults() reports assertion successes and failures.
- eval.go: runSingleEval() runs assertions after relevance checks.
- save.go: populateEvalResult() populates AssertionsCheck on the
  session's EvalResult.
- progress.go: tracks and displays assertion failure counts in the
  live progress bar.
- eval_test.go: checkResults tests for assertion pass/fail/zero.

Closes #4086
2026-09-01 16:48:52 +02:00
David Gageot 668cbd5b89 Merge pull request #4092 from docker/feat/4085-pass-at-k-metrics
feat: expose pass@k and pass^k metrics for --repeat runs
2026-09-01 16:01:39 +02:00
melmennaoui 7c5212eed8 Merge pull request #4091 from docker/feat/4084-judge-rubric
feat: upgrade relevance judge prompt with rubric, CoT, and anti-bias rules
2026-09-01 15:59:59 +02:00
melmennaoui def2d27d01 feat: expose pass@k and pass^k metrics for --repeat runs
When --repeat k is greater than 1, compute and display two
consistency metrics after the standard summary:

- pass@k: fraction of unique evals that passed at least once across
  k repetitions. Measures whether the agent can produce the correct
  answer at all.
- pass^k: fraction of unique evals that passed every repetition.
  Measures determinism / reliability.

Results are grouped by InputPath (the source eval JSON file) so
repeated runs of the same eval are correctly paired.

Changes:
- types.go: RepeatMetrics struct, new field on Summary
- scoring.go: computeRepeatMetrics(), updated printSummary
- eval.go: wire computeRepeatMetrics into Evaluate()
- eval_test.go: tests for all-pass, flaky, never-pass, mixed,
  and printSummary rendering

Closes #4085
2026-09-01 10:01:02 +02:00
melmennaoui fb855f09bc feat: upgrade relevance judge prompt with rubric, CoT, and anti-bias rules
Replace the minimal judge prompt with a structured evaluation prompt
that produces more consistent and fair relevance judgments:

- Rubric: explicit pass/fail definitions (fully satisfies vs. partial)
- Chain of thought: step-by-step reasoning before the verdict
- Anti-bias rules: ignore length, politeness, formatting, and effort
  unless the criterion explicitly requires them; evaluate substance
  over style; binary criterion — no partial credit

The structured-output schema (pass/fail + reason) is unchanged.

Closes #4084
2026-09-01 09:27:26 +02:00
melmennaoui d028a9e374 feat: add verify script runner for post-agent outcome verification
Add runVerifyScript() to the evaluation package. It executes a shell
script via `docker exec` on the eval container after the agent run
completes, enabling filesystem-level and side-effect checks that
cannot be expressed as text assertions on the response.

- Zero exit code → pass; non-zero → fail
- 60-second timeout prevents runaway scripts
- stdout + stderr captured, capped at 8 KB
- Empty script is a no-op pass (verify is optional)
- Fake-runtime test helper avoids a real Docker daemon

Closes #4083
2026-08-31 18:02:45 +02:00
Dwin Gharibi 1ff99fd167 test(evaluation): exercise the real baseline file format
Tests now write a run with SaveRunSessionsJSON and read it back through
LoadBaseline, so the round trip the command actually performs is covered
rather than one that no caller uses.

Adds the fail-closed cases (foreign baseline, empty run), pins that
resultPassed agrees with checkResults for every expectation kind, and pins the
specific divergence the old definition hid: a tool score dropping below 1.0
flips a printed PASS to FAIL and must gate even inside the tolerance.
2026-08-08 22:04:38 +03:30
Dwin Gharibi 4cae8da959 fix(evaluation): ground the baseline gate on the file eval writes, and fail closed
Three defects, each of which made the gate unusable or untrustworthy.

--baseline could not read anything `docker agent eval` produces. LoadBaseline
expected an EvalRun; the command writes a RunOutput via SaveRunSessionsJSON,
and their Duration fields are typed differently (string vs time.Duration), so
the load failed outright. SaveRunJSON, which the description named as the
producer, has no non-test callers. Baselines are now read from RunOutput —
its summary carries the aggregate rates and sessions[].eval_result.passed
carries per-eval pass/fail — so "no new format" is finally true.

The gate failed open. Any JSON object loaded successfully, and an empty
baseline yields all-zero metrics: every rate is skipped by its has-flag guard,
the failure rate by its own guard, and the run reports "No regression" while
every evaluation in it fails. The same held when the current run had no
evaluations, e.g. an --only pattern matching nothing. Both are now rejected, as
is a baseline sharing no metric and no evaluation with the run — a comparison
that can only pass is worse than no comparison.

resultPassed contradicted the project's own definition of pass. checkResults
requires a tool-call score of 1.0; resultPassed required only
>= ToolCallsExpected, so an evaluation whose printed status flipped from PASS
to FAIL was recorded as no change and the aggregate movement was then absorbed
by the tolerance. It now delegates to checkResults, so there is one notion of
"passed" in the package and the gate cannot drift from the product.

Also: a run that did not complete no longer prints a baseline verdict at all.
Its missing evaluations register as absent and do not gate, so it printed
"✅ No regression against baseline" and then exited non-zero.

And --regression-tolerance above 1 is rejected at startup: no rate can move
that far, so it silently disabled the aggregate gate.
2026-08-08 22:04:38 +03:30
Dwin Gharibi 5480ffa077 test(pkg/evaluation/baseline_test.go): adding up some amazing tests for new eval matrix 2026-08-07 09:51:08 +03:30
Dwin Gharibi 0db2061d38 feat(cmd/root/eval.go): compare a run against a saved baseline and fail on regression 2026-08-07 09:49:13 +03:30
Sayt-0 94c7b59cea feat(eval): support configurable container runtime 2026-08-03 14:39:47 +02:00
Sayt-0 bf270776d0 fix(eval): preserve budget termination outcomes 2026-07-28 15:47:36 +02:00
David Gageot a5637e9619 refactor(session)!: move file-backed SQLite store to session/sqlitestore
pkg/session mixed the in-memory store with the file-backed constructor,
so every embedder of pkg/runtime linked modernc.org/sqlite (large and
slow to compile) even when using purely in-memory sessions.

session.NewSQLiteSessionStore(ctx, path) becomes sqlitestore.New(ctx,
path); behavior (open, migrate, backup+retry recovery) is unchanged.
SQLiteSessionStore, NewSQLiteSessionStoreFromDB and the migrations stay
in pkg/session - they only use database/sql and don't drag the driver.

BREAKING CHANGE: callers of session.NewSQLiteSessionStore must import
github.com/docker/docker-agent/pkg/session/sqlitestore and call New.
2026-07-21 15:02:51 +02:00
Sayt-0 ac18222479 fix(session): lock title and usage scalar access 2026-07-15 15:46:12 +02:00
Sayt-0 52b7640334 test(eval): cover container interruption on cancellation 2026-07-15 11:44:06 +02:00
David Gageot c07386422b fix(eval): stop running containers on Ctrl-C
On context cancellation, exec.CommandContext's default Cancel SIGKILLs
the `docker run` CLI, which is never proxied to the container — the
container keeps running daemon-side and --rm never fires. This broke
when the containerTracker was removed (75654ed12).

Override cmd.Cancel to send SIGINT instead, letting the docker CLI
proxy the signal to the container so it stops and --rm removal fires.
WaitDelay force-kills the CLI as a backstop.

Assisted-By: Claude
2026-07-13 11:16:35 +02:00
David Gageot 1c70169c42 fix(config): scope ProviderAPIKeyEnvVars to forwardable model API keys
Only forward dedicated single-secret model API keys into the eval
container. Exclude non-secret detection/mode flags (GOOGLE_GENAI_USE_VERTEXAI),
multi-variable AWS/Bedrock credentials, and the broad GITHUB_TOKEN, none of
which should leak into a privileged container. Sort output for determinism
and add test coverage.
2026-07-01 14:34:55 +02:00
David Gageot 5ea914acc8 refactor(evaluation): add InputSession.criteria accessor to dedupe nil checks 2026-07-01 14:33:48 +02:00
David Gageot bb2b151378 refactor(evaluation): extract addEnv helper for container env forwarding 2026-07-01 14:33:48 +02:00
David Gageot 3f0bb2900b refactor(evaluation): derive provider API-key env vars from config
Assisted-By: docker/docker-agent
2026-07-01 14:33:48 +02:00
David Gageot 90d35a8d81 feat: add OpenRouter provider 2026-06-30 18:42:48 +02:00
David Gageot 8adf4b5145 Merge pull request #3288 from dgageot/worktree-board-f22a7d0fabad4f46
lint: enforce deferred mutex unlocks
2026-06-29 11:03:56 +02:00
David Gageot fab2ac7452 lint: add defer mutex unlock cop
Assisted-By: claude-sonnet-4-5
2026-06-29 10:50:40 +02:00
David Gageot 0a87da1f97 refactor: thread context through DB and test helper setup
Thread context through sqlite/session/RAG/memory DB setup so constructors and migrations no longer mint fresh roots. Fake proxy cleanup and askpass connection handling now use context providers derived from their startup context. LocalRuntime stores a context provider for tool-change refresh callbacks.

The remaining suppressions are intentionally detached event hooks/MCP supervisor callbacks or compatibility fallbacks.
2026-06-29 10:17:44 +02:00
David Gageot 7e5de8e17a fix: restore provider registry after cf5a430d2 emptied the default
RAG toolsets, the eval judge, and the js/wasm build all broke with
'unknown provider type' because the default registry is now empty.
Thread the resolved registry through RuntimeConfig and repopulate
the wasm-safe provider set directly in factory_js.go.

Assisted-By: Claude
2026-06-25 13:39:08 +02:00