This file records changes to the contracts other projects depend on: the generation protocol, the
evaluation protocol, the dataset format, the release format, and the published JSON Schemas in
schemas/protocol/. Internal refactoring is not listed here; read the commit
history for that.
The format follows Keep a Changelog. exgen-bench itself is pre-alpha and has no released version yet, so every entry below is unreleased. Protocol versions are independent of any future project version.
The evaluation protocol is unchanged. The release format now admits more than one evaluator per attempt, which the tiered evaluation design requires.
- Added
release-manifest.jsonfieldauthoritative_evaluator_id: the evaluator whosestrict_successdefines the primary estimand. A run that carries several evaluators must declare it (exgen release create --authoritative-evaluator <id>); a release that carries several and does not declare one is refused rather than resolved by a default. - Added
data/evaluation-index.jsonlanddata/evaluation-index.csv: one row per(attempt, evaluator), flagging the authoritative one. Adding an evaluator adds rows here rather than a column block to the attempt table. - Added column
evaluator_idtodata/scores.csvanddata/scores.jsonl. Two evaluators may emit a metric of the same name; scores are attributable and aggregated per evaluator. - Added
counts.evaluation_recordstorelease-manifest.json.counts.evaluatedremains the authoritative evaluator's coverage, andrates.evaluation_coverage_over_candidatesdivides it bycounts.candidates. - Added
authoritative_evaluator_id,evaluators[], and per-metricevaluator_id,not_applicableandapplicable_denominatortoanalysis/summary.json. A metric's denominator is now the cases where it applies, and "not applicable" is reported apart from "missing". - Changed
exgen release create --journalto be repeatable, once per evaluator. The merge rejects an evaluation ID that appears in two journals. - Changed the one-evaluator-per-release restriction to a one-identity-per-evaluator restriction. Several evaluators in one analysis is the design; two versions of the same evaluator is still an error, because then a metric's identity no longer determines how it was measured.
Schema 1 configurations are rejected. Update schema_version to "2", add
systems[].attestation.deployment_deviations, and declare target.parameters.language before
running them.
-
Changed
budget.max_cost.amountto allow zero. An explicitly unbilled deployment can now declare a real zero-cost bound instead of either inventing a positive amount or making strict budget compliance unverifiable. -
Changed generation identity to include
systems[].factors,systems[].parameters, andtarget.parameters. Adapter parameters are opaque to the harness and can select a deployment, model, or generation limit, so changing them must mint new attempt identities. Factors remain the explicit treatment description used for contrasts. -
Added
controlto every factor. A factor is written either as a bare scalar, still accepted and read as{value, control: "declared"}, or as{value, control}wherecontrolisrequested(the harness sends it, the system applies it, the response echoes it),observed(the system fixes it, the adapter reports what it saw, the declared value must match), ordeclared(an unverifiable label, released as unverified). Arequestedorobservedfactor the response contradicts, or does not report, fails the attempt as an infrastructure failure. -
Added two rules a multi-arm configuration must satisfy: at least one non-
declaredfactor must distinguish the arms, and a name cannot be both a fixedtarget.parameterskey and an arm-varying factor. -
Added a factor-name rule. A factor name is a bare lowercase snake_case token of at most 64 characters —
effort_profile, notartemis.effort_profileand noteffortProfile. The same name is written by a configuration, sent on a request, declared in a capability and echoed by a response, and a namespace separator would let those four spellings drift apart. It applies tosystems[].factors, requestfactors,execution.observed_factorsand the newcapabilities.controls/capabilities.observes. -
Changed
target.parametersfrom an unread free-form object to a typed one requiringlanguage, acceptingbuild_systemand any target-specific key, and accepting acase_overridesmap so a dataset can vary the artifact per case. The plan folds the per-case value before writing the request, so an override re-mints only the affected case's attempts. -
Added
budget.enforcement, a partial map from budget dimension toharnessorsystem. Declaringharnessfor a dimension the adapter does not list incapabilities.budget_dimensionsrefuses the run before any attempt starts. -
Added a required
systems[].attestation.deployment_deviations. It may be an empty list, but it cannot be omitted, and a release refuses to export a system without one. A deployment that raised a token budget or enabled sandbox slots is a deviation and now has to be recorded in the evidence rather than in a pull-request description. -
Added
analysis.design, required whenever the estimand is comparative. It declares the smallest meaningful effect and the assumptions behind the power calculation. Planning computes the minimum detectable effect, records it with its assumptions and coverage limitation on the plan, and refuses a comparative study that cannot detect its own declared effect.
-
Added optional OpenRouter reference pricing. A run snapshots the public model and endpoint responses, retains their HTTP and local retrieval times and SHA-256 digests, calculates with exact decimal rates, and publishes the estimate, rate basis, assumptions, active-route range, and quote digest. Reference cost remains separate from provider-reported
costand cannot satisfy a budget. -
Added cached-input and reasoning-token columns to analysis-ready attempt records. These token classes were already part of generation protocol 2 but were not carried into public tabular data.
-
Changed
budget.status.compliantnow requires evidence: a dimension with no declared limit, or no reported usage, isunverifiable, and a dimension whose system-reported ceiling is at or below the declared limit isnon_bindingrather than compliant, because nobody could have violated it. The attempt-level status is the worst dimension. Anon_bindingdimension still satisfies the budget leg of strict success; anunverifiableone does not. -
Added
budget_dimensionsandsystem_configurationto the attempt observation: the per-dimension verdict with its declared limit, observed value, system-reported ceiling, the ceiling'ssystem_limit_source, and declared enforcement, plus the system-reported parameters, limits and factors for that attempt. -
Added
factor_controlsandattestationto published system metadata, so a reader can tell a verified factor from an unverified label, andanalysis.inference_limitationsto the release manifest, recording that the percentile case-clustered bootstrap has no asymptotic refinement and under-covers below roughly forty clusters, with the literature it is measured against.
- Added a
boundedcontent-capture policy alongsiderequiredandforbidden. It demands input and output messages on every counted model span exactly asrequireddoes, and accepts a span without them only when the system declared, on that span, that it bounded the span's content and the span still retained some. Undeclared absence, and a declaration that retained nothing, are both rejected. The declaring attribute is named by the adapter, because the GenAI semantic conventions permit an instrumentation to truncate messages but define no attribute reporting it. - Changed the normalized trace evidence profile from
exgen.otel.genai.v3toexgen.otel.genai.v4.content_truncationis required, so a reader cannot be handed two shapes under one profile name. Evidence already written staysv3and stays readable asv3. - Added
content_truncationto the captured trace evidence, recording the declaring attribute, which model spans declared bounded content, and the retained byte counts for those spans and for the trace. Discarded bytes are not observable from a trace and are not reported.
Adapters must be updated. The runner rejects a response declaring protocol_version: "1".
- Added
recoveras a third subcommand alongsidedescribeandgenerate. An adapter that advertises crash recovery uses it to stop durable remote work before the runner classifies an uncertain attempt as interrupted. An adapter that does not advertise recovery does not need it. - Added
diagnostics: bounded, content-digested files such as event journals and stage checkpoints. Diagnostics are operational evidence, never candidate content. Each declares avisibility, and event journals must be UTF-8 newline-delimited JSON with a verified record count. - Added
capture.completenessso an adapter can report that it captured some but not all of a terminal workspace, instead of choosing between a complete claim and nothing. - Changed usage reporting to distinguish an unobserved quantity from zero. A field that the
adapter could not measure is absent; it is never reported as
0. - Added
factorsto the generation request, carrying every factor the configuration declared asrequested. An adapter must apply them and echo them back. - Added
execution.observed_factorsandexecution.effective_limitsto the generation response.observed_factorsis what the system reports it actually ran with, and is checked against everyrequestedandobservedfactor.effective_limitsis the system's own ceiling per budget dimension, and is what makes a declared limitnon_bindinginstead ofcompliant. Successful responses establish a baseline for the same system, adapter parameters, requested factors, and resolved target. A later successful response in that scope whose effective model, parameters, or limits change fails as configuration drift. Failed, abstained and infrastructure responses may carry partial execution data, so they remain subject to factor and seed attestation but neither establish nor contradict that complete baseline. - Added
capabilities.budget_dimensions, the budget dimensions the adapter actually enforces. Absent means the adapter predates the field and enforces nothing. - Added
capabilities.controlsandcapabilities.observes, the factor names an adapter can apply to a run and can attest afterwards. Preflight refuses a configuration whoserequestedfactor is absent fromcontrols, or whoseobservedfactor is absent fromobserves, so a study naming a factor the system cannot apply or cannot attest is rejected before any network round trip. Both are optional; absent means the adapter declares nothing and therefore supports no non-declaredfactor, matching howbudget_dimensionsis read. The two lists differ in practice: a system may accept a control it never echoes back, and a factor that is applied but not attested rests on the adapter's word alone, which is not enough to ground a contrast. - Changed
execution.effective_limitsso each limit may carry its source. A limit is written either as a bare number, still accepted and read as unsourced, or as{value, source}wheresourceissystem_reportedorsystem_configured;costtakes the samesourcealongsideamountandcurrency. Onlysystem_reportedcan support anon_bindingverdict, because that verdict claims the system's own guard bound before the declared limit did, and only the system can support that claim. An unsourced orsystem_configuredvalue is still recorded, with its source, on the attempt'sbudget_dimensions, but leaves the dimensioncompliantorunverifiableon its own merits. An adapter that reports bare numbers keeps parsing and only losesnon_binding, which is the safe direction for a schema change. - Changed the capability handshake to run with the system's declared environment, so an adapter that derives its identity from its environment can identify itself. Without this a second arm of the same adapter binary could never pass preflight.
- Changed
parameters_schemafrom published-and-ignored to enforced. The runner validatessystems[].parametersagainst it afterdescribe, so a mistyped parameter is a preflight error rather than a crash inside the first attempt. - Added a cross-check between
capabilities.seedandexecution.seed_status. An adapter that declaresunsupportedand reportshonored, or declaresdeterministicand reports anything else, fails the attempt.
- Added the process-evaluator configuration
(
schemas/protocol/process-evaluator-config.schema.json), which binds an evaluator's identity, suite, execution limits, and environment references to every request and records its digest in the evaluation identity. See the guide. - Changed which attempts are evaluated. The rule is now has a candidate, not succeeded: an
attempt whose lifecycle is
completedand which declared artifacts is offered for evaluation whatever the system decided about it. Lifecycle still gates the rule, so an infrastructure failure, a timeout, a cancellation and an attestation mismatch remain unevaluable. - Added an optional
candidate.capture_completeness(completeorpartial) to the evaluation request, so an evaluator scoring a truncated artifact set can tell. It is optional on the request and the response, andprotocol_versionstays"1", so an evaluator validating against the published v1 schema keeps accepting requests. It is not part of the candidate identity, and an evaluator that echoes a value disagreeing with the request is rejected as a protocol error. - Changed strict success to require generation success and evaluator acceptance and budget compliance. A scored candidate from a generation the system did not accept is an evaluator acceptance and never a strict success.
- Changed the evaluation-coverage denominator from generated candidates to candidates:
rates.evaluation_coverage_over_generatedis renamedrates.evaluation_coverage_over_candidates, and the per-systemevaluation_coveragedivides by a new per-systemcandidates. Acandidatesdenominator is published alongsidegenerated_candidates. Coverage over generated candidates could exceed one once a failed generation became evaluable. - Changed
rates.conditional_strict_success_over_quality_outcomes, the per-systemconditional_strict_success_rate, andpairs[].quality_outcome_available_a/_bto condition on accepted, budget-compliant generations, so the denominator holds only attempts the numerator can be drawn from.rates.conditional_evaluator_acceptance_over_quality_outcomesstill divides by every quality outcome. - Changed
missingness.generated_not_evaluatedto count the accepted generations carrying no evaluation record. It was the difference between two totals, which clamps to zero as soon as evaluations outnumber accepted generations. - Changed the
evaluate bundleandevaluate processJSON keystrict_successestoevaluator_strict_successes. It has always counted evaluator acceptances;strict_successnow names only the end-to-end gated quantity insummary.denominators.strict_successes. - Changed the run loader to accept a stored manifest whose
systems[].attestationis missing. Such a run loads, warns on the command line, and can be evaluated;release createstill refuses it, andsystemSchemastill requires the field when planning a run. - Removed the Artemis-specific evaluator API. Target verification belongs to the system under test; independent evaluation goes through the process-evaluator protocol.
Schema 1 datasets are rejected. Update schema_version to "2" and add the provenance fields
required below where applicable.
- Added conditional provenance requirements: a case whose
origin.kindisadaptedorcollectedmust carrysource_uriandcitation, and a case declared publicly exposed must carryfirst_public_at.
- Fixed a divergence between the Zod contracts and the published schemas. Schemas were generated with the output projection, so every field carrying a default was marked required — a document exgen accepted could be rejected by a third-party validator reading our own schema. Schemas are now classified by who authors the documents they validate. Documents written outside exgen (dataset, benchmark config, generator descriptor, generation response, evaluation response, process-evaluator config, metric cards) no longer require a defaulted field.
- Fixed two rules that differed rather than being merely weaker: URL fields now apply the same rule in both the contract and the schema, and RFC 3339 timestamps require seconds in both.
- The proposed Artemis benchmark API (
benchmark-api.openapi.yaml) and its client. Artemis is now driven through the same production endpoints the instructor interface uses; see the integration design.