EvalShift is two packages that meet on disk: the capture SDK records what your agent actually does in production, and the CLI replays that recorded behaviour against a candidate model and tells you what regressed. Using them together is the recommended way to work — your golden suite is built from real traffic, not hand-authored guesses, and it stays current as your agent evolves. The third piece, the GitHub Action, is optional but completes the loop for teams: it runs the same suite on every pull request and blocks merges that regress it. SDK + CLI + Action is the full setup we recommend.
You can absolutely hand-write a golden.jsonl and use the CLI alone — see Golden suite. But hand-written suites go stale, and writing realistic expected_tools ground truth by hand is tedious. Captures derive it from behaviour you have already shipped.
## The loop
| Step | Where | What happens |
|---|---|---|
| 1 · instrument | evalshift-sdk | Decorate your agent; it records model calls, tool calls, and outputs to disk when EVALSHIFT_CAPTURE=1. |
| 2 · capture | your agent | Run real (or staging) traffic. Each invocation lands as a JSON capture file under .evalshift/captures/. |
| 3 · promote | evalshift CLI | capture sync turns captures into golden-suite examples: inputs, expected tools, expected output, history. |
| 4 · evaluate | evalshift CLI | all --suite-name … --to <candidate> replays the suite on both models and reports what regressed, with statistics. |
| 5 · repeat | both | Keep capturing; re-sync as your agent evolves. Optionally push runs to hosted for team review. |
| 6 · gate | GitHub Action | Optional: run the suite on every PR and fail the check on regressions — the same statistics, as a required merge gate. |
## 1–2. Instrument and capture
Install the SDK inside your agent’s environment and decorate the entry point. Capture is off by default — with the gate unset the decorators are zero-overhead pass-throughs, so they are safe to leave in production code:
# in your agent's environment uv add evalshift-sdk
from evalshift import capture
@capture.agent(suite="support_agent")
def handle_ticket(query: str) -> str:
... # your agent: model calls, tools, retrieval
# nothing is recorded unless the gate is on
# EVALSHIFT_CAPTURE=1 python run_agent.py
# -> .evalshift/captures/support_agent/cap_<id>.jsonevalshift-sdk) and the CLI (package evalshift) share the top-level import name evalshift — keep them in separate virtual environments. The SDK lives in your agent’s env; the CLI in its own. Disk is the only interface between the two.See Instrumenting agents for tools, model calls, and async, and Redaction for masking PII before anything hits disk.
## 3. Promote captures into a golden suite
In your project directory (with evalshift init already run), turn the recorded captures into suite examples:
# in a separate environment with the CLI installed evalshift capture list # see what was recorded evalshift capture sync # captures -> golden suite + wired config
capture sync builds one example per capture — first model input becomes inputs, recorded tool calls become expected_tools, the final output becomes expected, and multi-turn conversations recover their history. It also de-duplicates (duplicate examples would corrupt the paired statistics) and rewrites the managed suites: block in evalshift.yaml. Strictness knobs (--strict-args, --names-only, --tool-count) control how exacting the derived ground truth is. Details in Captures → golden cases.
## 4. Evaluate a candidate model
evalshift all --suite-name support_agent --to <candidate-model> --open
The CLI replays every example on your current model and the candidate, scores both sides with the configured evaluators, runs paired statistics over the deltas, and renders a single-file HTML report. The canonical agent-migration failure this catches: the candidate silently stops calling a security-critical tool — tool_selection with severity_floor: high turns that into an unmissable red row.
## 5–6. Close the loop in CI
Everything so far runs on one machine with no account. When you want the suite enforced instead of remembered, three escalating options:
- +CI gating, local only —
evalshift all --gate critical,highor--policy-gateexit non-zero on regressions; no account needed. See Command reference. - +Team review — push runs to hosted EvalShift for shared reports, baselines, and diffs. Opt-in, and provider keys are never uploaded. See Hosted setup.
- +PR gating — the GitHub Action runs the suite on every pull request, diffs it against the latest base-branch run, posts one self-updating PR comment, and fails the check on regression. This is the recommended end state: the suite your captures built becomes a required check nobody has to remember to run.