Run an Eval
You changed an agent profile, a tool allowlist, or a system prompt.
Now you need to know whether things got better or worse.
gemba-harness supervise runs a
judge agent next to a
target agent on a shared orchestration loop. The
judge sends Ask questions, the target replies with
Answer, and the judge calls Conclude with
a verdict when it is satisfied. GitHub Actions treats the exit code
(0 pass, 1 fail) like any other check, and
the NDJSON trace captures every turn so that you can inspect what
happened with gemba-trace.
Prerequisites
- Node.js 22+
ANTHROPIC_API_KEYset in the environment-
The
gemba-*commands, which includegemba-harnessandgemba-trace. Install them globally withnpm install -g @forwardimpact/gemba, or run one in CI withnpx --yes gemba-harness ...and no global install
Write the task
A task file is a plain markdown prompt that says what the target agent should do. Keep it specific and measurable.
<!-- evals/refactor-utils/task.md -->
Refactor `src/utils/format.js` so that `formatDate` and `formatCurrency`
share a single locale-resolution helper. Do not change the public API of
either function. Add unit tests covering the en-US, en-GB, and de-DE
locales. Run the test suite and confirm it passes before finishing.
Write the judge profile
The judge is an agent profile at
.claude/agents/<name>.md. The runtime appends an
orchestration trailer that explains the available tools, so your
profile only needs to define what good looks like.
<!-- .claude/agents/refactor-judge.md -->
---
name: refactor-judge
description: Judge a refactor of shared formatting utilities.
---
You are evaluating a refactor of `src/utils/format.js`. Watch the agent's
work and call `Conclude` when the session is finished.
Pass criteria. All of them must hold:
- `formatDate` and `formatCurrency` share a single locale-resolution helper.
- The public signatures of both functions are unchanged.
- New tests exist for en-US, en-GB, and de-DE.
- The full test suite passes on the agent's final run.
If the agent strays, send a fresh `Ask` to redirect it. Each `Ask` gets a
new `askId`, so a follow-up question coexists with any in-flight ones. If
it claims to be done, verify the criteria yourself with `Read` and `Bash`
before calling `Conclude`. Conclude with `verdict: "failure"` if any
criterion fails. Include a one-paragraph summary of the gap.
Give the judge read-only tools with
--supervisor-allowed-tools (typically
Read,Grep,Bash). A judge with Edit access
can rewrite the target's work and hide failures.
Run the eval locally
npx gemba-harness supervise \
--task-file=evals/refactor-utils/task.md \
--lead-profile=refactor-judge \
--supervisor-cwd=. \
--supervisor-allowed-tools=Read,Grep,Bash \
--agent-cwd=/tmp/refactor-sandbox \
--max-turns=200 \
--output=trace--default.raw.ndjson
--agent-cwd should be a sandbox copy of your
repository, because the target agent edits files there. When you
omit it, gemba-harness creates a temporary directory.
The judge stays in --supervisor-cwd, where it inspects
the target's work without writing to it.
--max-turns is the turn budget for each runner (default
200). A separate internal cap on lead turns bounds the
orchestration loop between the judge and the agent.
--max-turns=0 removes the cap on each runner.
Exit code 0 means the judge concluded with
success: true. Exit code 1 means the judge
concluded with success: false, the run reached the turn
limit, or an error occurred.
Run the eval in GitHub Actions
A two-step workflow is enough. Run the eval, then split and upload the trace.
# .github/workflows/eval.yml
name: Agent eval
on:
push:
branches: [main]
pull_request:
jobs:
refactor-utils:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
- name: Run eval
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
mkdir -p /tmp/sandbox /tmp/trace
cp -r . /tmp/sandbox
npx --yes gemba-harness supervise \
--task-file=evals/refactor-utils/task.md \
--lead-profile=refactor-judge \
--supervisor-cwd=. \
--supervisor-allowed-tools=Read,Grep,Bash \
--agent-cwd=/tmp/sandbox \
--max-turns=200 \
--output=/tmp/trace/trace--default.raw.ndjson
- name: Split trace
if: always()
run: |
npx --yes gemba-trace split \
/tmp/trace/trace--default.raw.ndjson \
--mode=supervise \
--case=default \
--output-dir=/tmp/trace
- name: Upload trace
if: always()
uses: actions/upload-artifact@v4
with:
name: trace--default
path: /tmp/trace/trace--*.ndjson
if: always() on the split and upload steps preserves
the trace even when the eval fails, which is when you need it most.
split --mode=supervise --case=default produces
trace--default--agent.agent.ndjson and
trace--default--supervisor.supervisor.ndjson next to
the original trace--default.raw.ndjson.
Read the results
When an eval fails, download the artifact. Start with
overview and timeline to get oriented,
then drill into the verdict. The download extracts the
artifact's .ndjson members. Here that is the raw
trace plus the two split lanes, and every verb reads them directly.
npx gemba-trace runs # find the failed run
npx gemba-trace download <run-id> # extracts the .ndjson members
npx gemba-trace overview --file trace--default--agent.agent.ndjson
npx gemba-trace timeline --file trace--default--agent.agent.ndjson
npx gemba-trace tool trace--default--supervisor.supervisor.ndjson Conclude
Cross-trace verbs (overview, timeline, …)
take their file through --file and print text by
default. tool works on a single trace, so it takes a
positional argument. Add --format json to any verb for
the machine-parseable shape. The download produces a
structured.json only when the artifact has a single
.ndjson member. The verbs read multi-member bundles
like this one as they are.
The Conclude tool call holds the judge's verdict
and summary. From there, follow the timeline backwards to find the
turn where the agent went wrong.
Run npx gemba-trace --help for the full command
surface.
Benchmark-driven evals
A workflow that calls the reusable benchmark workflow
(forwardimpact/benchmark/.github/workflows/benchmark.yml)
mints trace--* artifacts on every shard. The caller
adds no steps, and it needs no manual split or upload step like the
harness-driven example above. Each cell preserves its traces under
runs/<taskId>/<runIndex>/. The cell holds
the raw combined trace
(trace--<case>.raw.ndjson), the agent and
supervisor lanes, and a judge lane on judged cells. The
<case> value is
<taskId>-r<runIndex>. Download and analyze
the files with the same runs / find /
download flow. See the
trace analysis guide.
Scale to a suite
Each eval is a task.md plus a judge profile. Add a
matrix to run them all:
strategy:
fail-fast: false
matrix:
eval:
- { task: refactor-utils, judge: refactor-judge }
- { task: fix-flaky-test, judge: test-judge }
- { task: add-rate-limiter, judge: ratelimit-judge }
fail-fast: false makes sure that every eval runs and
produces a trace, so the run does not stop at the first failure.
Tips
-
--max-turns=0removes the turn cap on each runner. The orchestration loop's internal cap on lead turns still applies. Use it for exploratory local runs, and always set a real budget in CI. -
--task-amendappends extra text to the task and leaves the task file unchanged. This helps you parameterize the same task across a matrix. - The judge profile is a system prompt. It steers the judge but does not bind it. Treat an eval verdict like a code review from a strong but fallible reviewer. It gives a useful signal, but you cannot treat it as ground truth.
What's next
Prove Agent Changes
Reproducible evidence that agent changes improved outcomes, from the eval session through the trace analysis.
Run a Benchmark
Prove a skill-pack change improved coding outcomes. Run a task family across N runs, grade with hidden tests, and report pass@k.
Analyze Traces
See exactly what an agent did and why. Download traces, query turns, filter by tool or error, and measure token cost.