Przejdź do głównej zawartości

Ciągłe ewaluacje (continuous evals) to stały zestaw regresyjny dla harnessu agenta: prawdziwe zadania z repozytorium, które CI uruchamia w trybie headless przy każdej zmianie CLAUDE.md, AGENTS.md, reguł, skilli, hooków, przypiętej wersji CLI lub modelu, a raz w tygodniu także na main. Deterministyczne sprawdzenia oceniają każdy przebieg, a merge jest blokowany, gdy pass rate z powtórzeń spada.

W zeszłym tygodniu ktoś skrócił CLAUDE.md z 300 linii do 90 i pull request wyglądał na zwykłe sprzątanie. Dwa dni później PR-y agenta przestały uruchamiać sprawdzenie migracji, a pierwszą osobą, która to zauważyła, był dyżurny inżynier. Żaden test produktu nie mógł tego wyłapać, bo kod produktu się nie zmienił. Ta strona jest dla dewelopera, który buduje zestaw ewaluacji, i dla tech leada, który odpowiada za jego punkt odniesienia (baseline) i zatwierdza zmiany harnessu.

Scorecard Q17: Jak testujesz regresyjnie konfigurację swoich agentów (CLAUDE.md, reguły, skille, hooki)?

Odpowiedź na maksymalny wynik (3 pkt): ciągły workflow ewaluacji w CI (.github/workflows/agent-evals.yml) uruchamiany przy pull requestach zmieniających konfigurację i skille, z incydentami produkcyjnymi dodawanymi jako testy regresyjne.

  • Format przypadku (prompt.md, base-commit, check.sh), który ocenia przebieg agenta kodem, z testami, których agent nigdy nie widzi.
  • Runner, który odtwarza każdy przypadek kilka razy na testowanym harnessie, dla Claude Code, Codeksa albo Cursora.
  • Bramkę, która blokuje merge, gdy przypadek obowiązkowy (must-pass) nie przejdzie albo pass rate spadnie poniżej zatwierdzonego baseline’u.
  • Workflow GitHub Actions z minimalnymi uprawnieniami, uruchamiany przez zmiany harnessu, podbicie wersji CLI i cotygodniowy harmonogram.
  • Procedurę „incydent → przypadek”, dzięki której błąd, który dotarł na produkcję, jest sprawdzany przy każdej kolejnej zmianie harnessu.

Czym ciągła ewaluacja różni się od jednorazowego testu A/B?

Dział zatytułowany „Czym ciągła ewaluacja różni się od jednorazowego testu A/B?”

Test A/B odpowiada raz na jedno pytanie: czy proponowany CLAUDE.md jest lepszy od obecnego? Ewaluacja agentów i zmian w CLAUDE.md opisuje taki eksperyment w promptfoo, Inspect SWE i Harborze. Ciągła ewaluacja codziennie odpowiada na inne pytanie: czy cokolwiek, co steruje agentem, pogorszyło się od ostatniego zaakceptowanego baseline’u?

Różnicę widać w trzech miejscach. Zestaw uruchamia się bez niczyjej decyzji. Porównuje wynik z baseline’em zaakceptowanym przez człowieka, a nie z drugim ramieniem eksperymentu. I rośnie: każdy incydent dodaje przypadek, a przypadki, które przestały rozróżniać warianty, są wycofywane.

Większość regresji harnessu przychodzi bez zmiany w kodzie, którą recenzent by oznaczył. Podepnij wyzwalacz pod każde źródło dryfu:

WyzwalaczCo wyłapujeJak to podłączyć
Pull request zmieniający CLAUDE.md, AGENTS.md, .claude/, .agents/, .codex/ lub .cursor/Reguły, skille, hooki lub subagenty, które przestały działaćpull_request z paths:
Podbicie przypiętej wersji Claude Code lub CodeksaZmienione domyślne ustawienia, narzędzia lub sposób ładowania w nowym wydaniuTrzymaj wersję w pliku workflow, który sam jest w paths:
Zmiana przypiętego modeluRegułę dostrojoną na jednym modelu, która psuje się na kolejnymPrzypnij model w skrypcie agenta; zmieniaj go w pull requeście
Nic, co tydzieńZmiany modelu i usług po stronie dostawcy, które przychodzą bez commitaschedule: na main
IncydentDokładnie ten błąd, który się prześlizgnąłNowy przypadek must-pass, mergowany razem z postmortemem

Przypnij wersje CLI i model w zadaniu ewaluacji. Nieprzypięte npm install -g @anthropic-ai/claude-code zamienia każde wydanie w niezrecenzowaną zmianę harnessu, a nieudany przebieg nie powie ci już, czy ruszyły się twoje reguły, czy narzędzie.

  1. Zbierz od 20 do 50 prawdziwych zadań z ostatnich miesięcy. Weź je ze zmergowanych pull requestów i z zadań, które agent zrobił źle. Pomieszaj pracę, którą zespół powtarza: endpoint z uwierzytelnianiem, migrację schematu, komponent UI, który musi używać design tokenów, poprawkę w asynchronicznym workerze.

  2. Daj każdemu przypadkowi osobny katalog. evals/cases/<id>/ zawiera prompt.md (zadanie tak, jak wpisałby je deweloper), base-commit (commit, od którego zadanie się zaczęło), check.sh (skrypt oceniający) i ewentualne ukryte pliki testów, które ten skrypt kopiuje do drzewa roboczego.

  3. Oceniaj kodem, nie opinią. check.sh dostaje ścieżkę do worktree, ścieżkę do logu agenta i SHA commita z harnessem, od którego ruszył przebieg, a przy zaliczeniu zwraca 0. Stawiaj na testy, kontrolę typów, linter i sprawdzenia zakresu zmian; ocenę przez model dodawaj tylko tam, gdzie kod nie rozstrzygnie, i dopiero po jej kalibracji.

  4. Oznacz przypadki, które nie mogą nigdy zawieść. Pusty plik must-pass w katalogu przypadku mówi bramce, że jeden nieudany przebieg blokuje merge. Dostaje go każdy przypadek z incydentu i przypadek kanarkowy.

  5. Dodaj przypadek kanarkowy. Zadaje pytanie, na które odpowie tylko harness, na przykład: „Which command must pass before you declare a task done in this repository? Reply with the command only.” Jego check.sh szuka w logu polecenia, które wskazuje twój CLAUDE.md lub AGENTS.md. Gdy kanarek nie przechodzi, harness się nie załadował i wszystkie pozostałe wyniki nic nie znaczą.

Skrypt oceniający dla przypadku, który dodaje uwierzytelnioną trasę ustawień:

evals/cases/authz-settings-route/check.sh
#!/usr/bin/env bash
# Usage: check.sh <worktree> <agent-log> <base-sha>. Exit 0 = pass.
set -euo pipefail
here=$(cd "$(dirname "$0")" && pwd)
base=$3 # the "harness under test" commit the run started from
cd "$1"
test -f src/routes/settings.ts
grep -q 'requireAuth' src/routes/settings.ts
# A test the agent never saw: it lives in the case directory, not in the repository.
cp "$here/settings.hidden.test.ts" tests/routes/
npm run typecheck
npx vitest run tests/routes/settings.hidden.test.ts
# Scope: the agent may touch routes and their tests, nothing else, committed or not.
outside=$( { git diff --name-only --no-renames "$base" -- .; git ls-files --others --exclude-standard; } \
| grep -vE '^(src|tests)/routes/' || true)
if [ -n "$outside" ]; then
echo "change outside src/routes or tests/routes:"; echo "$outside"; exit 1
fi

To ukryty test sprawia, że ocena jest wiarygodna. Agent, który widzi test, potrafi napisać kod, który przechodzi ten test i nic więcej; ochrona wyroczni opisuje inne drogi, którymi sprawdzenie może wyciec.

Runner tworzy dla każdego przypadku worktree z commita bazowego poza repozytorium, nakłada na niego harness z testowanego checkoutu i commituje go, żeby jedyną różnicą były zmiany agenta, a potem uruchamia agenta i ocenia wynik. Zapisz go jako evals/run.sh:

evals/run.sh
#!/usr/bin/env bash
# Run every case EVAL_RUNS times with the harness in this checkout.
set -uo pipefail
root=$(pwd); runs=${EVAL_RUNS:-3}; agent=${EVAL_AGENT:-claude}
codex_src=${CODEX_HOME:-$HOME/.codex}
out="$root/evals/out"; mkdir -p "$out"; : > "$out/results.tsv"
for case in "$root"/evals/cases/*/; do
id=$(basename "$case")
for n in $(seq 1 "$runs"); do
dir=$(mktemp -d); log="$out/$id.$n.log"
git worktree add --detach "$dir" "$(cat "$case/base-commit")" >/dev/null 2>&1
# The base commit carries the OLD harness: replace it with the one under test.
for f in CLAUDE.md AGENTS.md .claude .agents .codex .cursor; do
rm -rf "${dir:?}/$f"
if [ -e "$root/$f" ]; then cp -R "$root/$f" "$dir/$f"; fi
done
rm -rf "${dir:?}/evals" # the agent never sees cases or graders
git -C "$dir" add -A
git -C "$dir" -c user.name=evals -c user.email=evals@localhost commit -qm "harness under test" --allow-empty
base=$(git -C "$dir" rev-parse HEAD)
if [ "$agent" = codex ]; then # a throwaway Codex home: trust entries never reach ~/.codex
export CODEX_HOME=$(mktemp -d); cp "$codex_src/auth.json" "$CODEX_HOME/"
fi
(cd "$dir" && npm ci --no-audit --prefer-offline >/dev/null 2>&1)
(cd "$dir" && timeout 900 bash "$root/evals/agent-$agent.sh" "$(cat "$case/prompt.md")") > "$log" 2>&1
if bash "$case/check.sh" "$dir" "$log" "$base" >> "$log" 2>&1; then r=pass; else r=fail; fi
printf '%s\t%s\t%s\n' "$id" "$n" "$r" | tee -a "$out/results.tsv"
git worktree remove --force "$dir"
if [ "$agent" = codex ]; then rm -rf "$CODEX_HOME"; fi
done
done

Trzy przebiegi na przypadek to minimum. Agenci nie są deterministyczni, więc jeden przebieg jednego przypadku to próbka, a nie werdykt. Worktree ląduje w katalogu tymczasowym, bo Claude Code wczytuje też pliki CLAUDE.md z katalogów nadrzędnych, a worktree wewnątrz repozytorium czytałby stary plik obok nowego.

Dla każdego narzędzia różni się tylko wywołanie agenta. Każdy skrypt dostaje prompt jako $1 i działa w worktree:

evals/agent-claude.sh
#!/usr/bin/env bash
# Loads project settings, CLAUDE.md, skills and hooks: the harness under test.
exec claude -p "$1" \
--model "${EVAL_MODEL:-claude-opus-5-5}" \
--setting-sources project \
--permission-mode acceptEdits \
--allowedTools "Bash(npm run *)" "Bash(npx vitest *)" "Bash(git diff *)" \
--max-budget-usd 3 \
--no-session-persistence \
--output-format json

Flagi sprawdzone w dokumentacji CLI Claude Code i w claude --help dla 2.1.283 (kanał latest na 2026-09-26). Nie dodawaj --bare: dokumentacja trybu headless poleca go do wywołań ze skryptów, ale pomija on automatyczne wykrywanie CLAUDE.md, hooki z ustawień i pluginów, synchronizację pluginów oraz automatycznie wykrywaną konfigurację MCP (skille nadal działają, ale tylko wywołane po nazwie); od v2.1.286 nie wysyła też przypomnień systemowych i nie uruchamia zadań w tle. To większość tego, co ten zestaw ma testować. --setting-sources project odcina osobiste ustawienia dewelopera z ~/.claude. --max-budget-usd zatrzymuje przypadek, który zapętlił się na nieudanych wywołaniach narzędzi.

Do oceny pojedynczego skilla Claude Code ma też claude plugin eval (od tygodnia 37, 2026-09-07), które działa na folderach pluginów i skilli i dodaje ramię bazowe bez pluginu. claude plugin eval init --bare <name> uruchomione w folderze skilla zapisuje szablon przypadku: evals/<name>/prompt.md i evals/<name>/graders/criteria.md (katalog ewaluacji to domyślnie evals/, zmienisz go przez --eval-dir; sprawdzone w claude plugin eval init --help 2.1.283, kanał latest na 2026-09-26). Domyślnym modelem oceniającym jest Haiku, więc wyniki oceniane przez LLM traktuj jako wskazówkę, dopóki ich nie skalibrujesz.

Bramka czyta evals/out/results.tsv i stosuje dwie reguły. Przypadek must-pass, który zawiedzie w którymkolwiek przebiegu, blokuje merge. Ogólny pass rate nie może spaść o więcej niż pięć punktów poniżej evals/baseline-rate, jednolinijkowego pliku, który tech lead aktualizuje w zrecenzowanym pull requeście.

evals/gate.sh
#!/usr/bin/env bash
set -euo pipefail
res=evals/out/results.tsv; block=0
for id in $(awk -F'\t' '$3=="fail"{print $1}' "$res" | sort -u); do
if [ -f "evals/cases/$id/must-pass" ]; then echo "BLOCK: must-pass case $id failed a run"; block=1; fi
done
rate=$(awk -F'\t' '{t++; if ($3=="pass") p++} END {printf "%d", p*100/t}' "$res")
base=$(cat evals/baseline-rate)
echo "pass rate ${rate}% against baseline ${base}%"
if [ "$rate" -lt $((base - 5)) ]; then echo "BLOCK: pass rate fell more than 5 points"; block=1; fi
exit "$block"

Pięć punktów tolerancji to wartość startowa, nie prawo. Przy 30 przypadkach i trzech przebiegach jeden odwrócony przebieg przesuwa wynik o mniej więcej punkt, więc mniejsza tolerancja zamienia szum w zablokowane merge’e. Zaostrzaj ją, gdy zestaw rośnie. Baseline nigdy nie przesuwa się automatycznie: automatyczna aktualizacja po cichu akceptowałaby każdą regresję, którą zestaw ma wyłapywać.

.github/workflows/agent-evals.yml
name: Agent evals
on:
pull_request:
paths:
- '**/CLAUDE.md'
- '**/AGENTS.md'
- '.claude/**'
- '.agents/**'
- '.codex/**'
- '.cursor/**'
- 'evals/**'
- '.github/workflows/agent-evals.yml'
schedule:
- cron: '17 4 * * 1' # Mondays: catches model and service drift with no commit
workflow_dispatch:
permissions:
contents: read
concurrency:
group: agent-evals-${{ github.ref }}
cancel-in-progress: true
jobs:
evals:
# Fork pull requests get no secrets; never evaluate a fork's harness with your key.
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
timeout-minutes: 120
env:
CLAUDE_CODE_VERSION: 2.1.283 # bump in a pull request: the bump runs the suite
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # cases pin base commits from history
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: 22
- run: npm install -g "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}"
- name: Run the suite
env:
ANTHROPIC_API_KEY: ${{ secrets.EVALS_ANTHROPIC_API_KEY }}
EVAL_AGENT: claude
EVAL_RUNS: 3
run: bash evals/run.sh
- name: Gate
run: bash evals/gate.sh
- name: Keep the logs
if: always()
uses: actions/upload-artifact@v7
with:
name: agent-eval-results
path: evals/out/

Dla Codeksa zainstaluj zamiast tego @openai/codex@0.157.1, zaloguj się przez printenv OPENAI_API_KEY | codex login --with-api-key w kroku, który ma OPENAI_API_KEY z sekretu, i ustaw EVAL_AGENT: codex. Ustaw zadanie evals jako wymagany status check na main, żeby zablokowana bramka blokowała merge.

Incydent, któremu harness mógł zapobiec, staje się przypadkiem, zanim zamknie się postmortem. Pisze go zespół, który był właścicielem incydentu, bo wie, co agent powinien był zrobić.

  1. Nazwij kontrolę, która zawiodła. Zabrakło reguły, agent ją zignorował, skill się nie załadował, a może hook przepuścił polecenie? Postmortem opisany w gdy incydent wywoła agent to odnotowuje.

  2. Odtwórz zadanie, nie wynik. Ustaw base-commit na commit sprzed złej zmiany, a w prompt.md wpisz zadanie, które agent faktycznie dostał.

  3. Napisz sprawdzenie, które nie przechodzi na diffie z incydentu. Uruchom check.sh na commicie, który spowodował incydent, i potwierdź, że zwraca kod różny od zera. Sprawdzenie, które przepuszcza znaną złą zmianę, nie złapie następnej.

  4. Popraw harness w tym samym pull requeście. Nowy przypadek nie przechodzi na starym harnessie i przechodzi na poprawionym; zadanie ewaluacji pokazuje oba wyniki.

  5. Dodaj must-pass i zmerguj razem z postmortemem. Zgłoszenie incydentu zamyka się, gdy przypadek jest na main.

BAD_SHA to commit, który spowodował incydent.

Zestaw, który zawsze przechodzi, wygląda jak bezpieczeństwo, a niczego nie dowodzi. Cztery sprawdzenia utrzymują go w uczciwości i żadne nie wymaga czytania diffów agenta:

  • Kanarek przechodzi w każdym przebiegu. Jeśli nie, harness się nie załadował; napraw runner, zanim spojrzysz na jakikolwiek inny wynik.
  • Znany zły harness nie przechodzi przez bramkę. Raz na kwartał otwórz jednorazowy pull request, który usuwa z CLAUDE.md linie o testach i bezpieczeństwie. Bramka musi go zablokować. Jeśli nie blokuje, sprawdzenia są za słabe; siła wyroczni pokazuje, jak to zmierzyć.
  • Każdy przypadek z incydentu nie przechodzi na commicie incydentu. Krok 3 procedury incydentowej dowodzi tego raz; ta zasada nie wpuszcza do zestawu słabych przypadków.
  • Nasycone przypadki są wycofywane. Przypadek, który od miesięcy przechodzi każdy wariant harnessu, niczego już nie rozróżnia. Zastąp go nowszym zadaniem, najlepiej takim, które agent zrobił źle.

Odpowiedzialność jest podzielona według ról. Deweloper, który zmienia harness, dołącza artefakt z wynikami ewaluacji do pull requesta jako część jego pakietu dowodów. Tech lead jest właścicielem evals/, evals/baseline-rate i .github/workflows/agent-evals.yml przez CODEOWNERS, z wymaganą akceptacją właściciela kodu w ochronie gałęzi albo w rulesecie, recenzuje wyniki poszczególnych przypadków zamiast prozy nowych reguł i jako jedyny przesuwa baseline. Ten podział sprawia, że ani agent, ani osoba proponująca zmianę nie edytują sprawdzeń, które ją oceniają.

Co psuje się w ciągłych ewaluacjach i jak to naprawić

Dział zatytułowany „Co psuje się w ciągłych ewaluacjach i jak to naprawić”
ObjawPrzyczynaNaprawa
Kanarek nie przechodzi, a wyniki wyglądają, jakby zmiana harnessu nic nie zmieniłaHarness się nie załadował: --bare w wywołaniu Claude Code albo niezaufany worktree w CodeksieUsuń --bare; dodaj wpis trust_level; kanarek musi nie przechodzić przed poprawką i przechodzić po niej
Stara reguła nadal wpływa na wynikiZagnieżdżony CLAUDE.md lub AGENTS.md w podkatalogu commita bazowego przetrwał nakładanieRozszerz listę plików w run.sh o zagnieżdżone pliki albo usuń je przed kopiowaniem
Zmiany agenta przechodzą sprawdzenie, które powinny oblaćTesty skryptu oceniającego były widoczne w worktree, więc agent pisał pod testTrzymaj ukryte testy w katalogu przypadku; run.sh usuwa evals/ z worktree
Pass rate skacze między identycznymi przebiegamiJeden przebieg na przypadek albo za mało przypadkówCo najmniej trzy przebiegi; decyduj na podstawie przypadków, które zawodzą w większości przebiegów; rozbuduj zestaw
Zadanie blokuje merge bez zmiany harnessuPoniedziałkowy przebieg trafił na zmianę modelu lub usługiUruchom ponownie; jeśli wynik się utrzyma, otwórz zgłoszenie, porównaj z planem oceny nowego modelu i popraw harness albo zaakceptuj nowy baseline w zrecenzowanym pull requeście
Jeden przypadek przepala budżetPętla nieudanych wywołań narzędzi--max-budget-usd na przebieg, timeout 900 na przypadek, timeout-minutes na zadanie
Pull requesty z forków mają pominięte zadanie ewaluacjiForki nie dostają sekretów, a warunek zadania celowo je pomijaNiech maintainer odtworzy zmianę na gałęzi w repozytorium
Sprawdzenie zakresu oblewa pliki, których agent nie ruszałNałożony harness pojawił się w diffierun.sh commituje harness przed uruchomieniem agenta; nie usuwaj tego kroku

Przed tą stroną: harness, który testujesz, czyli zwięzły kontekst repozytorium, skille agenta i hooki jako bariery ochronne. Po niej: weryfikacja w przeglądarce prowadzona przez agenta, kolejne pytanie z etapu testów.

Najczęstsze pytania

Kiedy powinny uruchamiać się ciągłe ewaluacje (`continuous evals`) harnessu agenta?

Ciągłe ewaluacje uruchamiają prawdziwe zadania z repozytorium w trybie headless w CI przy każdej zmianie CLAUDE.md, AGENTS.md, reguł, skilli, hooków, przypiętej wersji CLI lub modelu, a także raz w tygodniu na gałęzi main.

Jak ciągłe ewaluacje chronią przed regresją konfiguracji agenta?

Deterministyczne sprawdzenia oceniają każdy przebieg, blokują merge po spadku pass rate z powtórzeń, a każdy rzeczywisty incydent jest dopisywany jako nowe zadanie w zestawie regresyjnym.