Skip to main content
Case/trial interfaces are available since 0.18.0; dependency contracts and provider capture ship in 0.19.0. These interfaces retain evidence for workflow-specific validation. See the implementation program for unfinished work.

Preserve the task definition

Use Hugging Face Datasets for loading, revision pinning, transforms, streaming and storage. Multivon’s CaseManifest is a small snapshot of the selected evaluation cases; it does not replace your dataset pipeline. For grouped random splits, use established splitters such as GroupShuffleSplit and supply the resulting assignments here for leakage validation. Two prompts can be identical while their source documents, expected answers, or permitted actions differ. case_id names a case across edits; its content digest detects edits. Without an explicit ID, the digest supplies the ID. revision is an optional human label. Changing a label without updating revision still changes the digest.
This is a synthetic decision-string fixture. It does not test document reading, ledger writes, or real customer requirements. CaseManifest detaches cases from mutable input objects, rejects duplicate IDs, and requires every case to belong to exactly one split when splits are supplied. Cases with the same source_id cannot cross split boundaries. It cannot infer that two differently named sources are related: supply source groups from your data pipeline. Group paraphrases, page variants, and repeated sessions before selecting held-out data.

Bring a Hugging Face dataset

Install multivon-eval[datasets] from the development checkout. Load or transform your dataset with the upstream library, then adapt a bounded evaluation set:
For Hub data, use datasets.load_dataset(..., revision=COMMIT_SHA) and record the repository and commit in provenance. A branch name can move. The bridge preserves upstream cache fingerprints, but they are not semantic case IDs. It accepts native split dictionaries and iterable datasets, and raises when a selection exceeds max_cases (10,000 by default). Select or sample upstream; the bridge will not silently truncate a dataset or decide your sampling policy. load_jsonl and save_jsonl preserve tool expectations, traces, reference outputs, IDs, revisions, and source groups. Unknown fields are rejected with a line number. Metadata must be portable JSON; nonfinite numbers and lossy non-string object keys are rejected. Materialize callable reference_output values before exporting. Validation-only references do not enter the execution case digest; the full dataset manifest still preserves string references.

Compare compatible cases

New reports pair unique case IDs only when content digests match. Reordering cases is safe. Changing context, expected answers, metadata, tags, tool expectations, or revisions makes the case unpaired. Explicit duplicate IDs are ambiguous and cannot support a paired test. Old reports without identity remain readable and show diagnostic prompt pairs. They do not produce a McNemar p-value or pass --fail-on-regression by default. If you have independently verified their case definitions, use --allow-legacy-identity to explicitly trust the old prompt pairing. The warning remains in the comparison artifact. Prefer rerunning both versions on a frozen dataset.

Grader dependency contracts (development)

The 0.19.0 dependency protocol extends suite locks beyond case identity. It records portable public settings and digests of private grader settings, inherited judge settings, the local Python source digest, Python/platform details and installed distribution names/versions using Python’s standard library. Judge extra settings are hashed rather than copied verbatim; common credential-like configuration names are also hashed. Tuple/regex type markers prevent lossy JSON coercion from hiding configuration changes. Reports still contain task data and public configuration. This is deliberately conservative: even an unrelated package version change makes the environment differ. Custom graders and opaque fields such as callbacks or Pydantic validator classes need an explicit declaration before comparisons can be verified:
Use immutable artifact revisions/digests where available, and update the contract when hidden behavior changes. An empty dependencies={} explicitly declares none. Pass files={"schema": "schemas/invoice.json"} to bind an existing local artifact. Named files are rehashed from their bytes for each snapshot; local file paths do not enter the report. Missing files remain explicit issues. A declaration is your assertion of completeness, not automatic discovery or validation of its contents. For external model artifacts, declare the actual model revision as well as its loader package. Pydantic JSON Schema alone does not capture custom validators. Synchronous, parallel, asynchronous, imported-output and regrading runs retain a pre-run lock and check it again afterward. Live runners prepare graders first. For imported-output grading and regrading, explicitly call any required grader.prepare() before the run to freeze generated criteria. These paths do not add preparation calls, including when saved model-error trials need no grading. Observed drift blocks verified comparisons and retains the post-run lock for inspection. Unknown dependencies, missing locks, legacy locks without this protocol or a digest mismatch also make comparison gates indeterminate. The legacy identity override applies only to reports with no retained identity/trials and no lock; it cannot bypass recorded dependency drift. Re-run both sides with the new protocol when migrating old locks. These snapshots cannot observe changes restored between snapshots. Source hashes cover on-disk Multivon Python files, not loaded bytecode or all third-party/native files. Installed package metadata is not a verified build manifest. Remote model aliases can change without their names changing, and callbacks can read undeclared globals or services. Run from an immutable checkout/environment and retain native provider logs. Declared compatibility is not complete experiment reproducibility. cases_hash retains its legacy input/expected/context subset; strict case pairing uses the full execution case digest separately.

Target and runner configuration (development)

report.execution records target settings before and after execution, plus runner repeat, retry, concurrency and gate settings. Each retained trial has its own trial.data["execution"]. With a provider journal, capture-start events persist the corresponding execution snapshot before the target runs. Reports saved by save_json retain the same snapshot as the returned report. Built-in adapter snapshots include model, system prompt, sampling temperature, output limit, portable request extras and selected routing settings. Supplied native SDK clients expose selected public endpoint/timeout/retry settings; custom client properties are not invoked for inspection. SDK defaults are not resolved by constructing another client. Use the native HTTP events for actual serialized requests and observed attempts. Runner settings record the tracer’s class, not its internal configuration; declare external tracing dependencies when they affect target behavior. Declare a custom target’s implementation, configuration and external artifacts:
This is a two-case local demonstration, not a validated invoice decision policy. If the business requirement changes, revise the case definitions instead of claiming an improvement on the old task. File paths stay local; named file content hashes enter the report. Structured credential fields and credential-bearing URLs are redacted/bound by digest. System prompts and other application content remain private report data. declare_target returns a wrapper and preserves synchronous/asynchronous and case-aware calling conventions. It does not change the callable’s behavior or discover closure state, runtime model weights, global services or custom client hooks. Missing files and unrecordable configuration remain explicit issues. The snapshot cannot detect a change that is restored between observations. Comparison output lists execution_changes and execution_notes. A different target is an expected experimental intervention, so it does not invalidate paired case/grader statistics by itself. Unknown target configuration produces a note; it is not an invented identity or proof of controlled causality. An observed within-run target/file change invalidates a controlled comparison and suppresses McNemar inference. Grader/case compatibility retains its separate strict rules. Regrading labels the new execution as saved-output grading and preserves original target snapshots in inherited_execution; it makes no new target calls. For native adapters, adapter.with_system_prompt("new prompt") now returns a copy of the same adapter with the replacement prompt and intact context handling. The SDK client remains shared. Custom adapters must implement their own prompt semantics; the old generic wrapper silently ignored the supplied prompt.

Inspect and regrade every attempt

Each new execution stores immutable TrialRecord snapshots with case input, evaluation-time case data, output, trace, per-grader reasons and metadata, latency, status, retry attempt, and repeated-run position. Aggregates remain available, but no longer replace individual outputs. All retry attempts are retained, including failed attempts before a successful retry.
regrade makes no target calls. Configured LLM graders can still make paid judge calls. The result contains one row per original trial, including retry attempts, with a parent_trial digest. These correlated rows are not an independent sample for confidence intervals. Regrading with NotEmpty in this example tests only whether text exists; it cannot establish decision accuracy. Model errors remain model errors during regrading. Imported outputs without measured target latency skip latency checks. Pass latencies_ms to run_on_cases when those measurements are available. Legacy reports never invent missing trials or individual scores from aggregate means.

Current evidence boundaries

  • Development provider capture retains observed native requests, retries and usage on supported transports (see below). Unsupported/custom transports and missing responses remain explicit gaps; snapshots alone cannot establish full request or billing coverage.
  • Arbitrary user callbacks can mutate inputs or external state. Snapshots do not provide process isolation or undo side effects. Existing case-level retries rerun the target; use them only when that is safe.
  • Nonportable custom metadata leaves evaluation results available but sets evidence_error. Those reports cannot establish verified identity or be regraded through this API.
  • Hashes detect accidental changes; they are not trusted signatures.
  • Artifacts contain prompts, outputs, traces, and metadata. Keep confidential application data in your own controlled storage.
Agent environments, multimodal artifact identity, durable execution, and world-model experiments build on these interfaces and remain separate work.

Retain provider attempts (development)

Built-in OpenAI and Anthropic clients use their SDK’s HTTP client factories and native retries. HTTPX hooks retain each serialized request, response body and unmodified native usage object. Trial evidence includes case identity, attempt, repeat position and target/judge role; preparation and reliability calls outside trials belong to the run capture. An arbitrary custom callback’s role may remain unknown. Evidence hashes detect edits; they are not third-party attestations.
Every retained trial has a provider_evidence snapshot. Report-level evidence is snapshotted before reporting and gates, so its state remains open; the journal records the later run closure or exception. The saved and returned report use the same snapshot. Regrading preserves parent evidence separately from new requests. For a supplied SDK HTTPX client, attach provider_http_hooks() (or provider_http_hooks(asynchronous=True)) as its event_hooks. Keep SDK factory defaults and your existing TLS/authentication settings. Google supports an explicit httpx_async_client in native HttpOptions; the library leaves its default async transport selection unchanged. Do not treat an empty capture as proof that a custom callback made no API calls. Missing responses or usage remain unknown, including retries that returned an HTTP error without usage. The default report.costs tracker remains a partial successful-text-judge estimate. Use the separate provider accounting workflow to reconcile a closed journal with upstream pricing and an explicit coverage declaration. Native cache/reasoning/tool fields remain available; these estimates are not invoices. The SQLite journal reuses native transactions with WAL and full synchronous writes. One actual child-process SIGKILL test retained the pre-dispatch request after reopening the journal. This does not test machine power loss, prove remote execution or authorize automatic replay of side effects. Continue to use an established runner such as Inspect for recovery scheduling. Authentication headers and credential URL values are excluded, but prompts, outputs, inline media and provider error content may contain sensitive data. Treat reports and journals as private evaluation artifacts. Capture is currently in memory unless a journal is supplied; it has no retention or size quota. A frozen live smoke study retained requests and native usage for 4/4 Anthropic calls; regrading two saved targets made zero requests. Both strict target checks failed on punctuation and remain failed in the artifacts. This validates a narrow capture path, not judge accuracy or complete accounting.