Judge
The rubric judge is the LLM that grades each answer against the hidden
rubric criteria. It is part of the grader, not of the agent under test: it
runs in the separate verifier container (or wherever tdb grade runs), and
its configuration and credentials are independent of the agent's. See
Grade for what the judge sees and how its verdicts
are scored.
Judges
There is one judge per model provider, and one rule names everything about
it: the provider's identifier, which is the same <provider>/ prefix that
tdb run --model uses. For a provider p:
- the judge model is
p/<model>(--judge-model,TDB_JUDGE_MODEL); - the backend name is
p(tdb grade --judge, andjudge.nameingrade.json); - the class is
pin PascalCase plusJudge, each hyphen-separated segment capitalized; - the provider's official SDK, when the judge uses one, is installed by the
extra
judge-p; thejudgeextra installs them all; - the API key variable and the egress host are the provider's.
The table mirrors trialdesignbench.judge.API_JUDGES; a test keeps them,
the extras, and this page in sync.
| Judge | Class | Default model | API | SDK (extra) | Key | Host |
|---|---|---|---|---|---|---|
anthropic |
AnthropicJudge |
anthropic/claude-opus-5-5 |
Messages API | anthropic (judge-anthropic) |
ANTHROPIC_API_KEY |
api.anthropic.com |
openai |
OpenaiJudge |
openai/gpt-6-astra |
Responses API | openai (judge-openai) |
OPENAI_API_KEY |
api.openai.com |
xai |
XaiJudge |
xai/grok-4.7 |
gRPC chat API | xai-sdk (judge-xai) |
XAI_API_KEY |
api.x.ai |
opencode-go |
OpencodeGoJudge |
opencode-go/grok-4.7 |
OpenAI-compatible gateway | none (standard library) | OPENCODE_API_KEY |
opencode.ai |
Every provider a supported agent can call has a judge, so a run
can be graded by a judge from the agent's own provider or from another one.
All judges share the same prompt, response contract (one structured JSON
result per criterion), majority voting, retries with backoff, and judge log
format; they differ only in the API call. A question whose judge call still
fails after the retries marks all its criteria error, which zeroes the
trial's reward.
Configuration
| Setting | Default | Inside Harbor (tdb run, tdb regrade) |
Standalone (tdb grade) |
|---|---|---|---|
| Judge | the provider of the judge model (anthropic) |
the provider of the built judge model | --judge anthropic\|openai\|xai\|opencode-go\|fake (default: the provider of the model) |
| Model | anthropic/claude-opus-5-5 |
fixed per task at build time: tdb build --judge-model <provider>/<model> writes TDB_JUDGE_MODEL into [verifier.env] |
--judge-model <provider>/<model>, else TDB_JUDGE_MODEL, else the judge's default |
| Votes | 1 | 1 | --judge-votes K (majority of K calls; ties are unclear) |
The judge model is always <provider>/<model>. A model id without a
provider, or with a provider that has no judge, is refused by tdb build and
tdb grade, so a typo never sends the rubrics to the wrong API. The build
writes the provider's key variable and API host into the task (see
Authentication and Network), so nothing else
changes between judges.
The judge model is recorded in tdb-build.json, tdb-run.json, and every
grade.json, together with the judge prompt hash and the SDK version. To
grade with a different model inside Harbor, rebuild the tasks with
--judge-model and use tdb regrade; no agent is re-run.
anthropic
AnthropicJudge uses the Anthropic Messages API with structured JSON output
(output_config.format) through the official anthropic package. It omits
temperature for the claude-fable, claude-mythos, claude-opus,
claude-sonnet, and claude-haiku families for forward compatibility,
including older versions that still accept it, such as Haiku 4.5. These
models use the API's default sampling behavior; other models receive
temperature=0. No requests send top_p or top_k.
openai
OpenaiJudge uses the OpenAI Responses API with strict JSON schema output
(text.format) through the official openai package. Requests set
store=false, so OpenAI keeps no copy of the exchange. No sampling
parameters are sent: reasoning models reject them, and the others use the
API defaults. max_output_tokens (16000) bounds reasoning and answer tokens
together; a response that stops as incomplete is retried.
xai
XaiJudge uses the official xai-sdk, which speaks gRPC to api.x.ai on
port 443, the same host as xAI's REST API, so the verifier allowlist is the
same. The result schema is sent as a JSON schema response format; no sampling
parameters are sent. The SDK's own transport retries are turned off so that
every attempt appears in the judge log; a reply truncated at max_tokens is
retried. The log records the gRPC finish reason and the token usage,
including reasoning tokens.
opencode-go
OpencodeGoJudge grades over the OpenCode Go
gateway, an OpenAI-compatible API, with structured JSON output and the
standard library only, so no extra is needed. Judge models are
opencode-go/<id> for the ids in the gateway's model list. The gateway
serves each model over exactly one protocol, Chat Completions for most and
the Responses API for the GPT, Grok, and Muse Spark families; the judge
starts with Chat Completions and switches once when the gateway answers
ModelProtocolUnsupported. Each question's judge log records the protocol
used. No sampling parameters are sent. Every request carries a stable
x-opencode-session header, which the gateway requires, and a
trialdesignbench/<version> user agent, because generic HTTP-library user
agents are blocked.
OpenCode Go models that train on request data
Some OpenCode Go models, for example muse-spark-1.3-contributor,
train on request data. The gateway refuses them with HTTP 400
(This Go model trains on request data) unless the workspace's privacy
settings allow such endpoints. The judge sends the hidden rubrics,
so leave that setting off and use a model the gateway serves without it;
the default opencode-go/grok-4.7 and opencode-go/kimi-k3 are such models.
A refused model marks every criterion of the question error, which zeroes
the trial, so a wrong choice fails loudly rather than leaking rubrics.
Authentication
The judge needs the API key of its provider:
| Judge | Key | From |
|---|---|---|
anthropic |
ANTHROPIC_API_KEY |
Claude Console |
openai |
OPENAI_API_KEY |
OpenAI Platform |
xai |
XAI_API_KEY |
xAI Console |
opencode-go |
OPENCODE_API_KEY |
OpenCode Console (Go subscription) |
Agent subscription logins (CLAUDE_CODE_OAUTH_TOKEN, Codex's auth.json)
cannot be used for the judge.
tdb runandtdb regrade. Export the key of the built judge model's provider in the shell that runs them, for exampleOPENAI_API_KEYfor tasks built with--judge-model openai/<model>. Each task's[verifier.env]maps the key as"${...}", so Harbor copies the host value into the verifier container only. Harbor asks before passing host variables into a task;tdb run --yesskips that prompt.tdb runfails before launching when the variable is missing, whatever agent or--authmode is used.tdb grade. Export the matching key in the environment and install the judge's SDK:uv add "trialdesignbench[judge-openai]"for one judge, oruv add "trialdesignbench[judge]"for all of them (the shared image includes them all). The OpenCode Go judge needs no extra.
export ANTHROPIC_API_KEY=...
uv run tdb grade \
path/to/submission \
--rubrics "tmp/dataset/<task_id>/rubrics.json" \
--out "graded/<task_id>" \
--trajectory path/to/trajectory.json \
--judge-model anthropic/claude-opus-5-5 \
--judge-votes 3
When the agent and the judge use the same provider with --auth api, for
example claude-code with the default judge, they share the key. With
claude-code --auth subscription, the key stays exported for the judge and
Harbor keeps it away from the agent (see
Agents).
Network
The verifier container has its own allowlist with only the judge's API host
(the Host column above; plus PyPI hosts with tdb build --grader-source
pypi). Only the judge key and TDB_JUDGE_MODEL are passed to it, so custom
base URLs (ANTHROPIC_BASE_URL, OPENAI_BASE_URL) do not apply inside
Harbor. Standalone tdb grade uses each SDK's defaults, which honor those
variables; the xAI judge always dials api.x.ai:443 and the OpenCode Go
judge always calls https://opencode.ai/zen/go/v1.
Dry runs without a key
FakeJudge gives deterministic verdicts and needs no credentials, for tests
and pipeline checks: tdb grade ... --judge fake --fake-verdict pass.
Adding a judge
Everything lives in src/trialdesignbench/judge.py and follows the naming
rule above:
- Add the provider to
trialdesignbench.providers.PROVIDERSif it is missing: name, title, key variable, and exact API host. - Add an
ApiJudgesubclass named<Provider>Judge, withbackend = JudgeBackend(PROVIDERS[name], default_model, sdk=...), implementing_request(the request recorded in the judge log) and_call_once(one API call that raises a retryable error for transient failures). Import the provider's official SDK lazily, and add the class toAPI_JUDGES. - Add the extra
judge-<provider>topyproject.toml, include it in thejudgeextra, and add the SDK to thedevgroup so the stub-client tests run in CI. - Add the row to the table above and a changelog entry; tests check the class name, the extras, and the table.
- Run the live test (
TDB_LIVE_JUDGE_TESTS=1 uv run pytest tests/test_judge.py -k live) and grade a submission end to end withtdb grade --judge-model <provider>/<model>.