Discover → Call benchmark
This directory contains the reproducible evaluation harness tracked by issue #163. It measures whether an agent can use the public QVeris workflow correctly, without assuming that one catalog tool ID is the only valid answer.
New runs use benchmark methodology discover-call-v2. Version 2 checks the
payload inside full execution-result wrappers and uses exact ordered composite
constraint matching. Historical v1 artifacts remain verifiable but must not be
mixed with v2 in one summary.
Scope boundary#
This harness deliberately stays at the contract level: deterministic scoring of the public discover → inspect → call workflow, cheap enough to re-run per release and reproducible by anyone with an API key. It is the public instrument for published routing-layer numbers.
Domain-level evaluation — long-horizon professional tasks, judged scoring,
with/without-QVeris product comparisons — is a different instrument and is out
of scope here. Do not grow this directory in that direction; new tasks belong
in a versioned task-set revision (tasks/v2.jsonl, …) only if they still score
deterministically at the contract level.
What is measured#
Each task runs the same sequence:
discoverwith a fixed natural-language query.- Ask a model adapter to select one returned tool.
inspectthe selected tool.- Ask the adapter to construct parameters from the inspected schema.
- Optionally execute the billed
call.
The runner treats both grounding checks as execution gates: it stops before
inspect when selection is outside the discovery results, and stops before
parameterization or call when inspection does not return the exact selected
tool. It validates the complete task set, including unique task and constraint
identifiers, before the first external request so malformed input cannot leave
a partial billed run.
The scorer reports these metrics separately:
- selection grounded: the selected tool was returned by
discover; - inspection grounded:
inspectreturned the selected tool; - required-parameter accuracy: fraction of required schema parameters supplied;
- constraint accuracy: fraction of task facts represented through accepted parameter aliases;
- call success: successful real executions among attempted calls;
- result non-empty: successful calls returned a structurally non-empty
result.data(or non-empty truncated-content evidence); this does not assert semantic correctness; - workflow success: all preceding checks equal 100%, the real call succeeds, and its result is non-empty.
Discover and inspect retry 429 and 503 responses, transient network
failures, and response-body timeouts. Execute retries only an explicit 429;
it does not retry 503, network errors, or timeouts because the first request
may already have produced third-party side effects or a charge. Calls explicitly
request respond_with: "full" so structural result scoring does not depend on
a changing server default. The summary assigns each observed strict failure to
its earliest failed gate and reports normalized stage/reason counts; all
exhausted API failures remain in the strict workflow-success denominator.
Legacy successful calls without result-non-emptiness evidence remain
unreportable rather than being relabeled as failures.
The summary includes a deterministic 95% task-cluster bootstrap interval for workflow success. Tasks, rather than individual trials, are resampled so three trials of one task are not treated as three independent task draws. Dry runs never count as workflow success. A successful legacy call with no recorded result-content observation is reported as unknown; it is never inferred to have a non-empty result or included in a strict workflow point estimate.
Task sets#
Task sets are versioned and immutable once referenced by a published result:
tasks/v1.jsonl— 6 tasks (initial smoke-scale set).tasks/v2.jsonl— 18 tasks (supersedes v1 as the reference set): the v1 six unchanged, plus geocoding, IP geolocation, crypto price, web search, air quality, market news, domain intelligence, image search, market holidays, earthquake catalog, company fundamentals, and sports standings. Every added task was verified against the live catalog: itsdiscover_queryreturns multiple parameterizable capabilities within the default discovery limit, and constraint aliases were taken from the actual parameter names of those capabilities.tasks/v3.jsonl— a diagnostic comparison set. It added deterministic reference candidates and task-specific normalization, but its crypto constraint did not recognize CoinMarketCap's provider-specific Bitcoin identifierid=1.tasks/v4.jsonl— the current immutable quality-baseline contract. It copies v3 and explicitly acceptsid=1only for the crypto task'sidalias. This task-versioned mapping fixes the known v3 scoring false negative without retroactively changing v3. Provider-specificalias_valuesuse exact matching even when the human-readable constraint usescontains.
New tasks land as a new tasks/vN.jsonl revision, never by editing an
existing file, and must still score deterministically at the contract level.
Select the set with --tasks; the scorer refuses to aggregate records across
different task-set hashes.
Run#
Set QVERIS_API_KEY, then provide an adapter executable and a model identifier.
Use --lane pinned-model only when the provider exposes a verifiable immutable
model revision and record it with --model-revision. Otherwise use
configured-model or current-model and record unreported.
The adapter command is spawned directly without shell parsing.
The harness removes every QVERIS_* variable from the adapter environment;
the bundled adapters repeat that boundary before starting their model CLI.
Adapters must use separately named credentials for their model provider.
The runner requires an explicit immutable task file and comparison lane. It
records a commit-shaped toolkit revision and refuses to run from a checkout
with tracked changes, including when --toolkit-revision is supplied.
node src/run.mjs \
--model provider/model-version \
--lane configured-model \
--tasks tasks/v4.jsonl \
--adapter node \
--adapter-arg /absolute/path/to/model-adapter.mjs \
--adapter-revision adapter-git-sha-or-config-hash \
--trials 3 \
--execute \
--output runs/model-version.jsonl--execute performs billed calls. Omit it while validating an adapter, but do
not publish dry-run records as benchmark results. Each trial atomically
checkpoints the private output file so an interrupted paid run retains all
records completed before the interruption; a partial checkpoint still fails
official publication completeness checks.
Within a trial, Discover, Inspect, and Execute share an independent private
session_id, and Execute also records the model identifier in the API request.
The session id is not derived from the public run id, sent to the model adapter,
or retained in public artifacts. The model identifier remains part of the
benchmark's public reproducibility metadata.
Comparison lanes#
Use the lanes together; none is a substitute for the others:
referenceis a curated reference route for the fixed discovery query. The deterministic adapter selects only a configured candidate that appears in the observed Top 10 and supplies fixed parameters. It represents only those curated candidates; a task with no suitable returned candidate remains a strict failure.configured-modelmeasures a named model plus recorded CLI, reasoning, adapter, and task-set configuration when an immutable provider snapshot is unavailable.pinned-modelmeasures longitudinal changes with one immutable model, provider revision, reasoning configuration, CLI, adapter revision, and task set. The runner rejects this lane without--model-revision.current-modelmeasures the currently recommended model under the same task contract.modelis retained for backward-compatible records that predate explicit lanes; the v2 runner does not accept it for new runs.
The strict benchmark gap is
curated reference route workflow success - model workflow success.
Compare component metrics and failure reasons alongside it so discovery
coverage, model routing, parameter construction, execution, and result non-emptiness
are not collapsed into one diagnosis. Here, result non-emptiness is structural,
not a semantic-quality judgment.
Run the curated reference route with the same task file supplied both to the harness and adapter:
node src/run.mjs \
--model reference-v1 \
--model-revision deterministic-reference-v1 \
--lane reference \
--adapter node \
--adapter-arg "$PWD/adapters/reference.mjs" \
--adapter-arg "$PWD/tasks/v4.jsonl" \
--adapter-revision toolkit-git-sha/reference-v1 \
--tasks tasks/v4.jsonl \
--trials 3 \
--execute \
--output runs/reference-v1.raw.jsonlGenerate public artifacts from raw records:
npm run publish -- \
--tasks tasks/v4.jsonl \
--runs runs/model-version.raw.jsonl \
--output-runs results/model-version-v4.runs.jsonl \
--output-summary results/model-version-v4.summary.jsonKeep raw records outside the public repository. Publication removes operational identifiers and the ordered discovery catalog, adds count/digest attestations, hashes selected tools that are not explicitly approved, and replaces raw parameter values with required-parameter and constraint-accuracy attestations. Inspected parameter names are omitted so a hashed tool cannot leak schema details.
Validate the checked-in task set, fixtures, and scorer:
npm test
npm run validateAdapter protocol#
The harness invokes the adapter once per decision stage. It writes one JSON
object to stdin and expects exactly one JSON object on stdout. Each request
contains the immutable task_set_sha256, canonical messages, and a
response_schema; model adapters must send the messages and schema unchanged
so model comparisons use the same prompt and output contract. The bundled
reference adapter rejects a task-file digest mismatch. Ground-truth scoring
constraints are deliberately not exposed to model adapters.
Selection input uses stage: "select"; input contains the user prompt and
compact routing cards. The operational search_id and non-routing discovery
metadata are not sent to the model adapter. Return:
{"tool_id":"selected.tool.id"}Parameterization input uses stage: "parameterize"; input contains the same
user prompt and a least-privilege projection of the inspected selected_tool
(tool_id, descriptive fields, parameters, one-of requirements, and examples).
The discovery id, billing metadata, runtime statistics, and prior-execution
records are not sent to the model adapter. Return:
{"parameters":{"city":"London"}}For parameterization, the harness derives a strict response schema from the
inspected tool's parameter metadata. Every known parameter is represented;
optional parameters are nullable because strict structured-output providers
require every declared property to appear. The harness removes top-level null
values before scoring and execution, so omitted optional parameters retain
their normal API semantics. Because the public inspection contract does not
describe array items or object properties, an opaque optional array/object is
forced to null, while an opaque required array/object fails before model
parameterization or a billed call.
When multiple constraints share a composite alias such as symbol or pair,
the scorer accepts only an exact, ordered composite representation (for
example, USD/EUR, USD-EUR, or USDEUR). Reversed pairs and substring
matches do not satisfy the constraints.
Adapters may call any model provider, but must not alter canonical messages or print prompts, keys, tokens, or provider error bodies to stdout. A first-result heuristic adapter is included only as a transport example; it does not construct parameters and is not an official model result.
Codex CLI adapter#
adapters/codex-cli.mjs supports authenticated Codex CLI installations. It
maps the canonical system message to developer_instructions, sends the
canonical user message unchanged on stdin, and writes the unchanged response
schema to a temporary file for --output-schema. The adapter uses an ephemeral
session in an isolated temporary directory, ignores user configuration and
rules, uses a read-only sandbox, and rejects runs that emit tool-use events.
Reasoning effort is fixed at medium; record the exact Codex CLI version
alongside the adapter commit in --adapter-revision:
node src/run.mjs \
--model gpt-5.6-sol \
--lane configured-model \
--adapter node \
--adapter-arg "$PWD/adapters/codex-cli.mjs" \
--adapter-revision adapter-git-sha/codex-cli-0.144.1/medium \
--tasks tasks/v2.jsonl \
--trials 3 \
--execute \
--output runs/gpt-5.6-sol.jsonlThe adapter reuses the CLI's existing authentication and never receives
QVERIS_API_KEY (both the harness and adapter remove it from the model
subprocess environment).
Claude CLI adapter#
adapters/claude-cli.mjs remains available for authenticated Claude CLI
installations. It passes the canonical system message as --system-prompt, the
canonical user message on stdin, and the unchanged response schema through
--json-schema. Safe mode disables project customizations, tools are disabled,
and sessions are not persisted, so the run measures only the supplied benchmark
messages. Use a full model identifier rather than an alias:
node src/run.mjs \
--model claude-sonnet-5 \
--lane configured-model \
--adapter node \
--adapter-arg "$PWD/adapters/claude-cli.mjs" \
--adapter-revision adapter-git-sha \
--tasks tasks/v2.jsonl \
--trials 3 \
--execute \
--output runs/claude-sonnet-5.jsonlThe adapter uses the CLI's existing authentication and never receives
QVERIS_API_KEY (the harness strips it from the adapter environment).
Publication rules#
The publication command stages and synchronizes the public runs and summary before replacing either destination. If any replacement fails, it restores the previous pair before returning an error. Repository validation also rejects missing or orphaned counterparts, so an interrupted process cannot silently leave a publishable mixed generation.
An official result must include:
- sanitized public JSONL records and generated summary; raw records remain private;
- the toolkit commit SHA and the exact, unchanged task-set file used (e.g.
tasks/v2.jsonl); - the model identifier and provider revision (or explicit
unreported), adapter source revision, runtime, trial count, and date; - the recorded API base URL, observed API revision, catalog revision when reported, catalog-observation SHA-256, discovery limit, and task-set SHA-256;
- at least three trials per task;
--executerecords for every reported workflow-success denominator;- failures retained in the denominator; no selective reruns or task removal;
- no API keys, access tokens, parameter values, private prompts, raw provider error bodies, execution/search/connection identifiers, or unfiltered catalog results.
The scorer rejects missing tasks, unequal trial counts, duplicate trials, and non-consecutive trial numbering for every model. It also rejects duplicate run IDs and aggregation across different adapter, toolkit, task-set, endpoint, discovery-limit, or execution settings. The checked-in-artifact validator also verifies the exact task-set digest, requires real execution and at least three trials per task, and rejects fields outside the public artifact schema.
The catalog and artifact visibility boundary is defined in
PUBLICATION_POLICY.md. Until discovery reports
per-tool visibility, bulk catalog content defaults to private; only approved
selected tool IDs may appear in public benchmark records. Other selected tools
are represented only by a digest.
The checked-in fixture validates the scorer only. It is synthetic and must not be presented as product performance.
Per-release cadence#
cadence.json is the reviewed, immutable input for coordinated client-release
sampling. It fixes the task version, three-trial denominator, Top-K limit,
reference route, configured model, reasoning effort, adapter paths, and exact
CLI version. With the current 18-task, two-lane configuration, the protected
workflow permits at most 108 tool calls.
After all four coordinated publish workflows succeed,
release-client-packages.mjs dispatches
.github/workflows/discover-call-cadence.yml for their shared commit. The job
waits for explicit approval in the benchmark-production environment before
it can access credentials or make paid calls. Raw records remain in the
ephemeral runner. Successful runs are sanitized and validated, then proposed
in a draft PR; the workflow never commits directly to main.
An orphaned result branch is treated as a recoverable publication failure and
blocks another paid run until that branch has been inspected and opened as a
draft PR.
The generated PR remains a candidate until a reviewer confirms failure
classification, catalog/API comparability, model-revision wording, and the
matching English/Chinese headline documentation. A provider revision of
unreported must remain a configured-model result rather than a pinned-model
claim.